From 026715693fae70295ca1cd6da0c53df05725103e Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 10 Nov 2010 21:04:56 +0000 Subject: [PATCH] [enjoy] Add MPRIS support. MPRIS stands for Media Player Remote Interfacing Specification. It is a DBUS interface that lets other programs to control a media player. This include going to the next or previous songs, obtaining the track list, being notified whenever a song starts to play, etc. There are a couple of clients out there, but standard programs like dbus-send and dbus-monitor can be used also. Spec 1.0 (http://www.mpris.org/1.0/spec.html) has been implemented, since most clients only support it. However, porting the current implementation to the new spec shouldn't be too difficult. The implementation is partial, though, since more things should be implemented in Enjoy before MPRIS support can make use of it. SVN revision: 54439 --- AUTHORS | 1 + TODO | 1 - configure.ac | 1 + data/themes/default.edc | 9 +- src/bin/Makefile.am | 11 +- src/bin/db.c | 54 +++- src/bin/list.c | 16 ++ src/bin/main.c | 4 +- src/bin/mpris.c | 590 ++++++++++++++++++++++++++++++++++++++++ src/bin/mpris.h | 26 ++ src/bin/page.c | 45 ++- src/bin/private.h | 36 +++ src/bin/win.c | 223 ++++++++++++++- 13 files changed, 998 insertions(+), 19 deletions(-) create mode 100644 src/bin/mpris.c create mode 100644 src/bin/mpris.h diff --git a/AUTHORS b/AUTHORS index d2b1c05..0b650be 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Gustavo Sverzut Barbieri Rafael Antognolli +Leandro Pereira diff --git a/TODO b/TODO index 480cffc..038263b 100644 --- a/TODO +++ b/TODO @@ -92,7 +92,6 @@ More Misc ---- * single instance using dbus - * export mpris control interface (mini mode could be e17 control using it) * support for e_notify (libnotify) * read cover from id3? requires id3 image loader in evas * fetch covers from webservices (amazon, last.fm) diff --git a/configure.ac b/configure.ac index f7e42ef..17971d6 100644 --- a/configure.ac +++ b/configure.ac @@ -59,6 +59,7 @@ AC_SUBST(ALL_LINGUAS) AM_GNU_GETTEXT_VERSION([0.12.1]) AM_GNU_GETTEXT([external]) +PKG_CHECK_MODULES([EDBUS], [edbus]) PKG_CHECK_MODULES([ELEMENTARY], [elementary ecore-file]) PKG_CHECK_MODULES([EFREET], [efreet]) PKG_CHECK_MODULES([EMOTION], [emotion]) diff --git a/data/themes/default.edc b/data/themes/default.edc index 5159349..917704e 100644 --- a/data/themes/default.edc +++ b/data/themes/default.edc @@ -7,6 +7,8 @@ #define MSG_POSITION 2 #define MSG_RATING 3 #define MSG_MUTE 4 +#define MSG_LOOP 5 +#define MSG_SHUFFLE 6 /* * TOOLBAR_POSITION() macro below only works with an odd number of items on a toolbar. @@ -1603,8 +1605,7 @@ collections { snprintf(time_str, 6, "%d:%d", round(time) / 60, round(time) % 60); } public message(Msg_Type:type, id, ...){ - if (type == MSG_FLOAT_SET && id == MSG_POSITION) - { + if (type == MSG_FLOAT_SET && id == MSG_POSITION) { new Float:position = getfarg(2); new Float:length = getfarg(3); if (length > 0) @@ -1618,6 +1619,10 @@ collections { get_time_str(length, time_str); set_text(PART:"ejy.text.total_time", time_str); set_float(cur_length, length); + } else if (type == MSG_INT && id == MSG_SHUFFLE) { + external_param_set_bool(PART:"buttons.shuffle", "state", getarg(2)); + } else if (type == MSG_INT && id == MSG_LOOP) { + external_param_set_bool(PART:"buttons.repeat", "state", getarg(2)); } } } diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index bc031c0..ad95d89 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -10,15 +10,16 @@ INCLUDES = \ @EMOTION_CFLAGS@ \ @LMS_CFLAGS@ \ @SQLITE3_CFLAGS@ \ -@EFREET_CFLAGS@ +@EFREET_CFLAGS@ \ +@EDBUS_CFLAGS@ bin_PROGRAMS = enjoy if BUILD_QUICKLAUNCH bin_PROGRAMS += enjoy_ql endif -enjoy_LDADD = @ELEMENTARY_LIBS@ @EMOTION_LIBS@ @LMS_LIBS@ @SQLITE3_LIBS@ @EFREET_LIBS@ -enjoy_SOURCES = main.c win.c db.c list.c page.c cover.c nowplaying.c libmanager.c +enjoy_LDADD = @ELEMENTARY_LIBS@ @EMOTION_LIBS@ @LMS_LIBS@ @SQLITE3_LIBS@ @EFREET_LIBS@ @EDBUS_LIBS@ +enjoy_SOURCES = main.c win.c db.c list.c page.c cover.c nowplaying.c libmanager.c mpris.c if BUILD_QUICKLAUNCH ############################################################################ @@ -29,8 +30,8 @@ if BUILD_QUICKLAUNCH ############################################################################ enjoy_qldir = $(quicklauncher_libdir) enjoy_ql_LTLIBRARIES = enjoy_ql.la -enjoy_ql_la_SOURCES = main.c win.c db.c list.c page.c cover.c libmanager.c -enjoy_ql_la_LIBADD = @ELEMENTARY_LIBS@ @EMOTION_LIBS@ @LMS_LIBS@ @SQLITE3_LIBS@ @EFREET_LIBS@ +enjoy_ql_la_SOURCES = main.c win.c db.c list.c page.c cover.c libmanager.c mpris.c +enjoy_ql_la_LIBADD = @ELEMENTARY_LIBS@ @EMOTION_LIBS@ @LMS_LIBS@ @SQLITE3_LIBS@ @EFREET_LIBS@ @EDBUS_LIBS@ enjoy_ql_la_CFLAGS = enjoy_ql_la_LDFLAGS = -module -avoid-version -no-undefined enjoy_ql_SOURCES = main.c diff --git a/src/bin/db.c b/src/bin/db.c index 4b7179a..8285ff2 100644 --- a/src/bin/db.c +++ b/src/bin/db.c @@ -192,6 +192,7 @@ Song *db_song_copy(const Song *orig) void db_song_free(Song *song) { + if (!song) return; eina_stringshare_del(song->path); eina_stringshare_del(song->title); eina_stringshare_del(song->album); @@ -199,6 +200,58 @@ db_song_free(Song *song) free(song); } +Song * +db_song_get(DB *db, int64_t id) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(db, NULL); + sqlite3_stmt *song_get; + Song *song; + + song_get = _db_stmt_compile(db, "song_get", + "SELECT files.id, files.path, files.size, " + " audios.title, audios.album_id, audios.artist_id, audios.genre_id, " + " audios.trackno, audios.rating, audios.playcnt, audios.length " + "FROM audios, files " + "WHERE " + " files.id = audios.id " + "AND audios.id = ?"); + if (!song_get) return NULL; + if (!_db_stmt_bind_int64(song_get, 1, id)) goto cleanup; + if (sqlite3_step(song_get) != SQLITE_ROW) goto cleanup; + song = calloc(1, sizeof(*song)); + if (!song) goto cleanup; + +#define ID(m, c) \ + song->m = sqlite3_column_int64(song_get, c); +#define INT(m, c) \ + song->m = sqlite3_column_int(song_get, c); +#define STR(m, c) \ + song->m = (const char *)sqlite3_column_text(song_get, c); \ + song->m = eina_stringshare_add(song->m); \ + song->len.m = sqlite3_column_bytes(song_get, c) + + ID(id, 0); + STR(path, 1); + INT(size, 2); + STR(title, 3); + ID(album_id, 4); + ID(artist_id, 5); + ID(genre_id, 6); + INT(trackno, 7); + INT(rating, 8); + INT(playcnt, 9); + INT(length, 10); + +#undef STR +#undef INT +#undef ID + + return song; +cleanup: + _db_stmt_finalize(song_get, "song_get"); + return NULL; +} + struct DB_Iterator { Eina_Iterator base; @@ -1017,7 +1070,6 @@ void song_free(Song *); void album_free(Album *); void artist_free(Artist *); -Song *db_song_get(DB *db, id); // ret song Eina_Bool db_song_rating_set(DB *db, Song *song, int rating); Eina_Iterator *db_songs_get(DB *db); // ret song diff --git a/src/bin/list.c b/src/bin/list.c index c51805e..0bb2cab 100644 --- a/src/bin/list.c +++ b/src/bin/list.c @@ -243,6 +243,22 @@ list_next_go(Evas_Object *obj) return NULL; } +int32_t +list_song_selected_n_get(Evas_Object *obj) +{ + LIST_GET_OR_RETURN(list, obj, 0); + if (list->page.songs) return page_songs_selected_n_get(list->page.songs); + return 0; +} + +Song * +list_song_nth_get(Evas_Object *obj, int32_t n) +{ + LIST_GET_OR_RETURN(list, obj, NULL); + if (list->page.songs) return page_songs_nth_get(list->page.songs, n); + return NULL; +} + Song * list_random_go(Evas_Object *obj) { diff --git a/src/bin/main.c b/src/bin/main.c index d3279b3..213a60e 100644 --- a/src/bin/main.c +++ b/src/bin/main.c @@ -5,6 +5,7 @@ #ifndef ELM_LIB_QUICKLAUNCH #include "private.h" +#include "mpris.h" #include #include @@ -103,7 +104,7 @@ elm_main(int argc, char **argv) app.win = win_new(&app); if (!app.win) goto end; - + mpris_init(); elm_run(); // don't del win - autodel is set. choose. either use autodel and then set win @@ -119,6 +120,7 @@ elm_main(int argc, char **argv) _log_domain = -1; elm_shutdown(); efreet_mime_shutdown(); + mpris_shutdown(); return r; } diff --git a/src/bin/mpris.c b/src/bin/mpris.c new file mode 100644 index 0000000..520355a --- /dev/null +++ b/src/bin/mpris.c @@ -0,0 +1,590 @@ +#include "private.h" +#include "mpris.h" + +typedef struct _MPRIS_Method MPRIS_Method; +typedef struct _MPRIS_Signal MPRIS_Signal; + + +#define APPLICATION_NAME "org.mpris.enjoy" +#define PLAYER_INTERFACE_NAME "org.freedesktop.MediaPlayer" +#define ROOT_NAME "/Root" /* should really be "/", but this doesn't work correctly :( */ +#define TRACKLIST_NAME "/TrackList" +#define PLAYER_NAME "/Player" + + +static void _mpris_signals_add(const char *root, const MPRIS_Signal *signals); +static void _mpris_methods_add(const char *root, const MPRIS_Method *methods); +static void _mpris_append_dict_entry(DBusMessageIter *dict_iter, const char *key, + int value_type, void *value); +static void _mpris_signal_emit(const char *root, const char *signal_name, int arg_type, + void *arg_value); +static DBusMessage *_mpris_player_next(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_previous(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_pause(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_stop(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_play(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_seek(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_root_identity(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_root_quit(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_root_version(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_caps_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_volume_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_volume_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_repeat_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_status_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_position_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_player_position_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_song_metadata_reply(DBusMessage *msg, Song *song); +static DBusMessage *_mpris_player_metadata_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_tracklist_current_track_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_tracklist_metadata_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); +static DBusMessage *_mpris_tracklist_shuffle_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg); + +static void _cb_dbus_request_name(void *data, DBusMessage *msg __UNUSED__, DBusError *err); + +struct _MPRIS_Method { + const char *name; + const char *param_sig; + const char *return_sig; + void *callback; +}; + +struct _MPRIS_Signal { + const char *name; + const char *sig; +}; + + +E_DBus_Connection *conn = NULL; +Eina_Hash *interface_list = NULL; + + +static const char *object_list[] = { ROOT_NAME, TRACKLIST_NAME, PLAYER_NAME, NULL }; +static const MPRIS_Signal mpris_player_signals[] = { + /* Emitted whenever a new song is played; gives the song metadata */ + { "TrackChange", "a{sv}" }, + /* Emitted whenever player's status changes */ + { "StatusChange", "(iiii)" }, + /* Emitted whenever player's capabilities changes */ + { "CapsChange", "i" }, + { NULL, NULL } +}; +static const MPRIS_Signal mpris_tracklist_signals[] = { + /* Emitted whenever the tracklist changes; gives the number of items */ + { "TrackListChange", "i" }, + { NULL, NULL } +}; +static const MPRIS_Method mpris_root_methods[] = { + /* Returns a string representing the player name */ + { "Identity", "", "s", _mpris_root_identity }, + /* Quits the player */ + { "Quit", "", "", _mpris_root_quit }, + /* Returns a tuple containing the version of MPRIS protocol implemented */ + { "MprisVersion", "", "(qq)", _mpris_root_version }, + { NULL, NULL, NULL, NULL } +}; +static const MPRIS_Method mpris_player_methods[] = { + /* Goes to the next song */ + { "Next", "", "", _mpris_player_next }, + /* Goes to the previous song */ + { "Prev", "", "", _mpris_player_previous }, + /* Pauses the song */ + { "Pause", "", "", _mpris_player_pause }, + /* Stops the song */ + { "Stop", "", "", _mpris_player_stop }, + /* If playing, rewind to the beginning of the current track; else, start playing */ + { "Play", "", "", _mpris_player_play }, + /* Seek the current song by given miliseconds */ + { "Seek", "x", "", _mpris_player_seek }, + /* Toggle the current track repeat */ + { "Repeat", "b", "", _mpris_player_repeat_set }, + /* Return the status of the media player */ + { "GetStatus", "", "(iiii)", _mpris_player_status_get }, + /* Gets all the metadata for the currently played element */ + { "GetMetadata", "", "a{sv}", _mpris_player_metadata_get }, + /* Returns the media player's current capabilities */ + { "GetCaps", "", "i", _mpris_player_caps_get }, + /* Sets the volume */ + { "VolumeSet", "i", "", _mpris_player_volume_set }, + /* Gets the current volume */ + { "VolumeGet", "", "i", _mpris_player_volume_get }, + /* Sets the playing position (in ms) */ + { "PositionSet", "i", "", _mpris_player_position_set }, + /* Gets the playing position (in ms) */ + { "PositionGet", "", "i", _mpris_player_position_get }, + { NULL, NULL, NULL, NULL } +}; +static const MPRIS_Method mpris_tracklist_methods[] = { + /* Gives all the metadata available at the given position in the track list */ + { "GetMetadata", "i", "a{sv}", _mpris_tracklist_metadata_get }, + /* Returns the position of the current URI in the track list */ + { "GetCurrentTrack", "", "i", _mpris_tracklist_current_track_get }, + /* Returns the number of elements in the track list */ + { "GetLength", "", "i", NULL }, + /* Appends an URI to the track list */ + { "AddTrack", "sb", "i", NULL }, + /* Removes an URL from the track list */ + { "DelTrack", "i", "", NULL }, + /* Toggle playlist loop */ + { "SetLoop", "b", "", NULL }, + /* Toggle playlist shuffle/random */ + { "SetRandom", "b", "", _mpris_tracklist_shuffle_set }, + { NULL, NULL, NULL, NULL } +}; + + + +void +mpris_init(void) +{ + if (conn) return; + e_dbus_init(); + conn = e_dbus_bus_get(DBUS_BUS_SESSION); + if (conn) + e_dbus_request_name(conn, APPLICATION_NAME, 0, _cb_dbus_request_name, NULL); +} + +void +mpris_shutdown(void) +{ + if (!conn) return; + e_dbus_shutdown(); + eina_hash_free(interface_list); + conn = NULL; + interface_list = NULL; +} + +static void +_cb_dbus_request_name(void *data, DBusMessage *msg __UNUSED__, DBusError *err) +{ + DBusError new_err; + dbus_uint32_t msgtype; + + if (dbus_error_is_set(err)) + { + dbus_error_free(err); + return; + } + + dbus_error_init(&new_err); + dbus_message_get_args(msg, &new_err, DBUS_TYPE_UINT32, &msgtype, DBUS_TYPE_INVALID); + + if (msgtype == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER || + msgtype == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) + { + int i; + + interface_list = eina_hash_string_small_new((Eina_Free_Cb)e_dbus_interface_unref); + + for (i = 0; object_list[i]; i++) + { + E_DBus_Object *object = e_dbus_object_add(conn, object_list[i], NULL); + E_DBus_Interface *interface = e_dbus_interface_new(PLAYER_INTERFACE_NAME); + e_dbus_object_interface_attach(object, interface); + + eina_hash_add(interface_list, object_list[i], interface); + } + + _mpris_signals_add(PLAYER_NAME, mpris_player_signals); + _mpris_signals_add(TRACKLIST_NAME, mpris_tracklist_signals); + + _mpris_methods_add(ROOT_NAME, mpris_root_methods); + _mpris_methods_add(PLAYER_NAME, mpris_player_methods); + _mpris_methods_add(TRACKLIST_NAME, mpris_tracklist_methods); + } +} + +static void +_mpris_signals_add(const char *root, const MPRIS_Signal *signals) +{ + E_DBus_Interface *interface; + int i; + + if (!conn) return; + + interface = eina_hash_find(interface_list, root); + if (!interface) return; + + for (i = 0; signals[i].name; i++) + e_dbus_interface_signal_add(interface, signals[i].name, signals[i].sig); +} + +static void +_mpris_methods_add(const char *root, const MPRIS_Method *methods) +{ + E_DBus_Interface *interface; + int i; + + if (!conn) return; + + interface = eina_hash_find(interface_list, root); + if (!interface) return; + + for (i = 0; methods[i].name; i++) + e_dbus_interface_method_add(interface, methods[i].name, methods[i].param_sig, + methods[i].return_sig, methods[i].callback); +} + +static void +_mpris_append_dict_entry(DBusMessageIter *dict_iter, const char *key, + int value_type, void *value) +{ + DBusMessageIter entry_iter, value_iter; + const char *signature; + + switch (value_type) { + case DBUS_TYPE_BOOLEAN: + signature = DBUS_TYPE_BOOLEAN_AS_STRING; + break; + case DBUS_TYPE_STRING: + signature = DBUS_TYPE_STRING_AS_STRING; + break; + case DBUS_TYPE_BYTE: + signature = DBUS_TYPE_BYTE_AS_STRING; + break; + case DBUS_TYPE_UINT16: + signature = DBUS_TYPE_UINT16_AS_STRING; + break; + case DBUS_TYPE_UINT32: + signature = DBUS_TYPE_UINT32_AS_STRING; + break; + case DBUS_TYPE_INT16: + signature = DBUS_TYPE_INT16_AS_STRING; + break; + case DBUS_TYPE_INT32: + signature = DBUS_TYPE_INT32_AS_STRING; + break; + case DBUS_TYPE_OBJECT_PATH: + signature = DBUS_TYPE_OBJECT_PATH_AS_STRING; + break; + default: + signature = DBUS_TYPE_VARIANT_AS_STRING; + } + + dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &entry_iter); + + dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &key); + + dbus_message_iter_open_container(&entry_iter, DBUS_TYPE_VARIANT, signature, &value_iter); + dbus_message_iter_append_basic(&value_iter, value_type, value); + dbus_message_iter_close_container(&entry_iter, &value_iter); + + dbus_message_iter_close_container(dict_iter, &entry_iter); +} + +static void +_mpris_signal_emit(const char *root, const char *signal_name, int arg_type, void *arg_value) +{ + DBusMessage *sig = dbus_message_new_signal(root, PLAYER_INTERFACE_NAME, signal_name); + if (arg_type != DBUS_TYPE_INVALID) + dbus_message_append_args(sig, arg_type, arg_value, DBUS_TYPE_INVALID); + e_dbus_message_send(conn, sig, NULL, -1, NULL); + dbus_message_unref(sig); +} + +static void +_mpris_message_fill_song_metadata(DBusMessage *msg, Song *song) +{ + DBusMessageIter iter, dict; + + if (!song) return; + + /* + Other possible metadata: + location s time u + mtime u comment s + rating u year u + date u arturl s + genre s mpris:length u + trackno s + */ + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + if (song->title) + _mpris_append_dict_entry(&dict, "title", DBUS_TYPE_STRING, &song->title); + if (song->flags.fetched_album && song->album) + _mpris_append_dict_entry(&dict, "album", DBUS_TYPE_STRING, &song->album); + if (song->flags.fetched_artist && song->artist) + _mpris_append_dict_entry(&dict, "artist", DBUS_TYPE_STRING, &song->artist); + if (song->flags.fetched_genre && song->genre) + _mpris_append_dict_entry(&dict, "genre", DBUS_TYPE_STRING, &song->genre); + _mpris_append_dict_entry(&dict, "rating", DBUS_TYPE_UINT32, &song->rating); + _mpris_append_dict_entry(&dict, "length", DBUS_TYPE_UINT32, &song->length); + _mpris_append_dict_entry(&dict, "enjoy:playcount", DBUS_TYPE_INT32, &song->playcnt); + _mpris_append_dict_entry(&dict, "enjoy:filesize", DBUS_TYPE_INT32, &song->size); + + dbus_message_iter_close_container(&iter, &dict); +} + +void +mpris_signal_player_caps_change(int caps) +{ + static int old_caps = 0; + if (caps != old_caps) + { + _mpris_signal_emit(PLAYER_NAME, "CapsChange", DBUS_TYPE_INT32, &caps); + old_caps = caps; + } +} + +void +mpris_signal_player_status_change(int playback, int shuffle, int repeat, int endless) +{ + DBusMessage *sig; + DBusMessageIter iter, siter; + static int old_playback = 0, old_shuffle = 0, old_repeat = 0, old_endless = 0; + + if (old_playback == playback && old_shuffle == shuffle && + old_repeat == repeat && old_endless == endless) return; + old_playback = playback; + old_shuffle = shuffle; + old_repeat = repeat; + old_endless = endless; + + sig = dbus_message_new_signal(PLAYER_NAME, PLAYER_INTERFACE_NAME, "StatusChange"); + if (!sig) return; + dbus_message_iter_init_append(sig, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &siter); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &playback); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &shuffle); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &repeat); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &endless); + dbus_message_iter_close_container(&iter, &siter); + e_dbus_message_send(conn, sig, NULL, -1, NULL); + dbus_message_unref(sig); +} + +void +mpris_signal_player_track_change(Song *song) +{ + static Song *old_song = NULL; + if (old_song != song) + { + DBusMessage *sig = dbus_message_new_signal(PLAYER_NAME, PLAYER_INTERFACE_NAME, "TrackChange"); + if (!sig) return; + _mpris_message_fill_song_metadata(sig, song); + e_dbus_message_send(conn, sig, NULL, -1, NULL); + dbus_message_unref(sig); + old_song = song; + } +} + +void +mpris_signal_tracklist_tracklist_change(int size) +{ + _mpris_signal_emit(TRACKLIST_NAME, "TrackListChange", DBUS_TYPE_INT32, &size); +} + +static DBusMessage * +_mpris_player_next(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + enjoy_control_next(); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_previous(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + enjoy_control_previous(); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_pause(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + enjoy_control_pause(); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_stop(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + enjoy_control_stop(); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_play(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + int playback; + enjoy_status_get(&playback, NULL, NULL, NULL); + if (!playback) + enjoy_position_set(0); + enjoy_control_play(); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_seek(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + dbus_uint64_t position; + dbus_message_get_args(msg, NULL, DBUS_TYPE_INT64, &position, DBUS_TYPE_INVALID); + enjoy_control_seek(position); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_root_identity(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + const char *identity = PACKAGE_STRING; + DBusMessageIter iter; + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &identity); + return reply; +} + +static DBusMessage * +_mpris_root_quit(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + enjoy_quit(); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_root_version(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter, siter; + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &siter); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT16, (int[]) { 1 }); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT16, (int[]) { 0 }); + dbus_message_iter_close_container(&iter, &siter); + return reply; +} + +static DBusMessage * +_mpris_player_caps_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, (int[]) { enjoy_caps_get() }); + return reply; +} + +static DBusMessage * +_mpris_player_volume_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + dbus_int32_t volume; + + dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &volume, DBUS_TYPE_INVALID); + if (volume > 100) + volume = 100; + else if (volume < 0) + volume = 0; + enjoy_volume_set(volume); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_volume_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, (int[]) { enjoy_volume_get() }); + return reply; +} + +static DBusMessage * +_mpris_player_repeat_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + dbus_bool_t repeat; + dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &repeat, DBUS_TYPE_INVALID); + enjoy_repeat_set(repeat); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_status_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter, siter; + int playback, shuffle, repeat, endless; + + enjoy_status_get(&playback, &shuffle, &repeat, &endless); + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &siter); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &playback); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &shuffle); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &repeat); + dbus_message_iter_append_basic(&siter, DBUS_TYPE_UINT32, &endless); + dbus_message_iter_close_container(&iter, &siter); + return reply; +} + +static DBusMessage * +_mpris_player_position_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + dbus_int32_t position; + dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &position, DBUS_TYPE_INVALID); + enjoy_position_set(position); + return dbus_message_new_method_return(msg); +} + +static DBusMessage * +_mpris_player_position_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, (int[]) { enjoy_position_get() }); + return reply; +} + +static DBusMessage * +_mpris_song_metadata_reply(DBusMessage *msg, Song *song) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + _mpris_message_fill_song_metadata(reply, song); + return reply; +} + +static DBusMessage * +_mpris_player_metadata_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + Song *song = enjoy_song_current_get(); + return _mpris_song_metadata_reply(msg, song); +} + +static DBusMessage * +_mpris_tracklist_current_track_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, (int[]) { enjoy_playlist_current_position_get() }); + return reply; +} + +static DBusMessage * +_mpris_tracklist_metadata_get(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply; + Song *song; + dbus_int32_t position; + dbus_message_get_args(msg, NULL, DBUS_TYPE_INT32, &position, DBUS_TYPE_INVALID); + song = enjoy_playlist_song_position_get(position); + reply = _mpris_song_metadata_reply(msg, song); + return reply; +} + +static DBusMessage * +_mpris_tracklist_shuffle_set(E_DBus_Object *obj __UNUSED__, DBusMessage *msg) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_bool_t param; + dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, ¶m, DBUS_TYPE_INVALID); + enjoy_control_shuffle_set(param); + return reply; +} diff --git a/src/bin/mpris.h b/src/bin/mpris.h new file mode 100644 index 0000000..56eb108 --- /dev/null +++ b/src/bin/mpris.h @@ -0,0 +1,26 @@ +#ifndef ENJOY_MPRIS_H +#define ENJOY_MPRIS_H + +#include +#include "private.h" + +typedef enum { + CAN_GO_NEXT = 1 << 0, + CAN_GO_PREV = 1 << 1, + CAN_PAUSE = 1 << 2, + CAN_PLAY = 1 << 3, + CAN_SEEK = 1 << 4, + CAN_PROVIDE_METADATA = 1 << 5, + CAN_HAS_TRACKLIST = 1 << 6 +} MPRIS_Capabilities; + +void mpris_init(void); +void mpris_shutdown(void); + +void mpris_signal_player_caps_change(int caps); +void mpris_signal_player_status_change(int playback, int shuffle, int repeat, int endless); +void mpris_signal_player_track_change(Song *song); +void mpris_signal_tracklist_tracklist_change(int size); +void mpris_signal_emit(const char *root, const char *signal_name, int arg_type, void *arg_value); + +#endif /* ENJOY_MPRIS_H */ diff --git a/src/bin/page.c b/src/bin/page.c index f2a1968..aca3a69 100644 --- a/src/bin/page.c +++ b/src/bin/page.c @@ -1,4 +1,5 @@ #include "private.h" +#include "mpris.h" #include #include @@ -95,7 +96,8 @@ typedef struct _Page_Class const char *name; const char *key; const char *layout; - Eina_Bool (*init)(Page *page); /* optional extra initializer */ + Eina_Bool (*init)(Page *page); /* optional extra initializer */ + void (*after_populate)(Page *page); /* called after page has been populated */ Evas_Smart_Cb selected; const Elm_Genlist_Item_Class *item_cls; size_t populate_iteration_count; @@ -194,6 +196,9 @@ _page_populate(void *data) return EINA_TRUE; end: + if (cls->after_populate) + cls->after_populate(page); + page->populate = NULL; return EINA_FALSE; } @@ -547,6 +552,12 @@ _song_item_selected(void *data, Evas_Object *list __UNUSED__, void *event_info) if (song) evas_object_smart_callback_call(page->layout, "song", song); } +static void +_page_songs_after_populate(Page *page) +{ + mpris_signal_tracklist_tracklist_change(page->num_elements); +} + static Evas_Object * _page_songs_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const char *title) { @@ -564,6 +575,7 @@ _page_songs_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const ch "_enjoy_page_songs", "page/songs", NULL, + _page_songs_after_populate, _song_item_selected, &song_item_cls, PAGE_SONGS_POPULATE_ITERATION_COUNT, @@ -648,6 +660,7 @@ _page_album_songs_add(Evas_Object *parent, Album *album) "_enjoy_page_songs", "page/songs-album", _song_album_init, + _page_songs_after_populate, _song_item_selected, &song_item_cls, PAGE_SONGS_POPULATE_ITERATION_COUNT, @@ -706,9 +719,32 @@ page_songs_next_go(Evas_Object *obj) page->selected = it; elm_genlist_item_selected_set(it, EINA_TRUE); elm_genlist_item_bring_in(it); + mpris_signal_player_track_change(song); return song; } +int32_t +page_songs_selected_n_get(Evas_Object *obj) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, 0); + Elm_Genlist_Item *it; + int n; + for (n = 0, it = page->first; + it && it != page->selected; + n++, it = elm_genlist_item_next_get(it)); + return (it) ? n : 0; +} + +Song * +page_songs_nth_get(Evas_Object *obj, int32_t n) +{ + PAGE_SONGS_GET_OR_RETURN(page, obj, NULL); + Elm_Genlist_Item *it = page->first; + while (it && n--) it = elm_genlist_item_next_get(it); + if (!it) return NULL; + return elm_genlist_item_data_get(it); +} + Song * page_songs_random_go(Evas_Object *obj) { @@ -726,6 +762,7 @@ page_songs_random_go(Evas_Object *obj) page->selected = it; elm_genlist_item_selected_set(it, EINA_TRUE); elm_genlist_item_bring_in(it); + mpris_signal_player_track_change(song); return song; } @@ -752,6 +789,7 @@ page_songs_prev_go(Evas_Object *obj) page->selected = it; elm_genlist_item_selected_set(it, EINA_TRUE); elm_genlist_item_bring_in(it); + mpris_signal_player_track_change(song); return song; } @@ -822,6 +860,7 @@ _page_albums_artist_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, "_enjoy_page_albums", "page/albums", NULL, + NULL, _album_item_selected, &album_item_cls, PAGE_FOLDERS_POPULATE_ITERATION_COUNT, @@ -868,6 +907,7 @@ _page_albums_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const c "_enjoy_page_albums", "page/albums", NULL, + NULL, _album_item_selected, &album_item_cls, PAGE_FOLDERS_POPULATE_ITERATION_COUNT, @@ -995,6 +1035,7 @@ _page_artists_add(Evas_Object *parent, NameID *nameid, Eina_Iterator *it, const "_enjoy_page_artists", "page/nameids", NULL, + NULL, _artist_item_selected, &nameid_item_cls, PAGE_FOLDERS_POPULATE_ITERATION_COUNT, @@ -1064,6 +1105,7 @@ _page_genres_add(Evas_Object *parent, Eina_Iterator *it, const char *title) "_enjoy_page_genres", "page/nameids", NULL, + NULL, _genre_item_selected, &nameid_item_cls, PAGE_FOLDERS_POPULATE_ITERATION_COUNT, @@ -1151,6 +1193,7 @@ page_root_add(Evas_Object *parent) "_enjoy_page_roots", "page/roots", NULL, + NULL, _static_item_selected, &root_item_cls, PAGE_FOLDERS_POPULATE_ITERATION_COUNT, diff --git a/src/bin/private.h b/src/bin/private.h index 5fa3c9f..262cece 100644 --- a/src/bin/private.h +++ b/src/bin/private.h @@ -28,6 +28,13 @@ extern int _log_domain; #define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__) #define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__) +#define MSG_VOLUME 1 +#define MSG_POSITION 2 +#define MSG_RATING 3 +#define MSG_MUTE 4 +#define MSG_LOOP 5 +#define MSG_SHUFFLE 6 + struct _App { Eina_List *add_dirs; @@ -36,8 +43,31 @@ struct _App Evas_Object *win; }; + Evas_Object *win_new(App *app); +Eina_Bool enjoy_repeat_get(void); +int32_t enjoy_position_get(void); +int32_t enjoy_volume_get(void); +int enjoy_caps_get(void); +int32_t enjoy_playlist_current_position_get(void); +Song *enjoy_playlist_song_position_get(int32_t position); +Song *enjoy_song_current_get(void); +Song *enjoy_song_position_get(int32_t position); +void enjoy_control_loop_set(Eina_Bool param); +void enjoy_control_next(void); +void enjoy_control_pause(void); +void enjoy_control_play(void); +void enjoy_control_previous(void); +void enjoy_control_seek(uint64_t position); +void enjoy_control_shuffle_set(Eina_Bool param); +void enjoy_control_stop(void); +void enjoy_position_set(int32_t position); +void enjoy_quit(void); +void enjoy_repeat_set(Eina_Bool repeat); +void enjoy_status_get(int *playback, int *shuffle, int *repeat, int *endless); +void enjoy_volume_set(int32_t volume); + Libmgr *libmgr_new(const char *dbpath); Eina_Bool libmgr_scanpath_add(Libmgr *mgr, const char *path); Eina_Bool libmgr_scan_start(Libmgr *mgr, void (*func_end)(void *, Eina_Bool), void *data); @@ -56,6 +86,8 @@ Song *list_prev_go(Evas_Object *list); DB *list_db_get(const Evas_Object *obj); void list_freeze(Evas_Object *obj); void list_thaw(Evas_Object *obj); +Song *list_song_nth_get(Evas_Object *obj, int32_t n); +int32_t list_song_selected_n_get(Evas_Object *obj); const char *page_title_get(const Evas_Object *obj); void page_songs_exists_changed(Evas_Object *obj, Eina_Bool exists); @@ -70,6 +102,8 @@ Song *page_songs_next_go(Evas_Object *obj); Song *page_songs_random_go(Evas_Object *obj); Eina_Bool page_songs_prev_exists(const Evas_Object *obj); Song *page_songs_prev_go(Evas_Object *obj); +Song *page_songs_nth_get(Evas_Object *obj, int32_t n); +int32_t page_songs_selected_n_get(Evas_Object *obj); Evas_Object *cover_allsongs_fetch(Evas_Object *parent, unsigned short size); Evas_Object *cover_album_fetch(Evas_Object *parent, DB *db, Album *album, unsigned short size); @@ -153,6 +187,8 @@ Eina_Bool db_song_album_fetch(DB *db, Song *song); Eina_Bool db_song_artist_fetch(DB *db, Song *song); Eina_Bool db_song_genre_fetch(DB *db, Song *song); +Song *db_song_get(DB *db, int64_t id); + /* walks over 'const Song*' */ Eina_Iterator *db_album_songs_get(DB *db, int64_t album_id); Eina_Iterator *db_artist_songs_get(DB *db, int64_t artist_id); diff --git a/src/bin/win.c b/src/bin/win.c index db86979..3cffdfc 100644 --- a/src/bin/win.c +++ b/src/bin/win.c @@ -1,11 +1,7 @@ #include "private.h" +#include "mpris.h" #include -#define MSG_VOLUME 1 -#define MSG_POSITION 2 -#define MSG_RATING 3 -#define MSG_MUTE 4 - typedef struct Win { Evas_Object *win; @@ -142,11 +138,18 @@ _win_toolbar_eval(Win *w) edje_object_signal_emit(w->edje, "ejy,mode,list,disable", "ejy"); edje_object_signal_emit(w->edje, "ejy,mode,nowplaying,disable", "ejy"); } + + mpris_signal_player_caps_change(enjoy_caps_get()); } static void _win_play_pause_toggle(Win *w) { + int playback, shuffle, repeat, endless; + enjoy_status_get(&playback, &shuffle, &repeat, &endless); + mpris_signal_player_status_change(playback, shuffle, repeat, endless); + mpris_signal_player_caps_change(enjoy_caps_get()); + if (w->play.playing) { edje_object_signal_emit(w->edje, "ejy,action,play,hide", "ejy"); @@ -157,7 +160,6 @@ _win_play_pause_toggle(Win *w) edje_object_signal_emit(w->edje, "ejy,action,pause,hide", "ejy"); edje_object_signal_emit(w->edje, "ejy,action,play,show", "ejy"); } - } static void _win_play_eval(Win *w) @@ -183,6 +185,8 @@ _win_play_eval(Win *w) if (w->play.playing_last == w->play.playing) return; w->play.playing_last = !w->play.playing; _win_play_pause_toggle(w); + + mpris_signal_player_caps_change(enjoy_caps_get()); } static Eina_Bool @@ -255,7 +259,7 @@ _win_song_set(Win *w, Song *s) emotion_object_play_set(w->emotion, EINA_TRUE); emotion_object_audio_volume_set(w->emotion, w->play.volume); - end: +end: if ((!w->play.playing) && (w->timer.play_eval)) { ecore_timer_del(w->timer.play_eval); @@ -268,6 +272,9 @@ _win_song_set(Win *w, Song *s) _win_nowplaying_update(w); _win_play_eval(w); _win_toolbar_eval(w); + + mpris_signal_player_caps_change(enjoy_caps_get()); + mpris_signal_player_track_change(s); } static void @@ -349,7 +356,7 @@ _win_play_end(void *data, Evas_Object *o __UNUSED__, void *event_info __UNUSED__ _win_song_set(w, s); } else - _win_next(data, NULL, NULL, NULL); + _win_next(data, NULL, NULL, NULL); } static void @@ -361,6 +368,8 @@ _win_action_play(void *data, Evas_Object *o __UNUSED__, const char *emission __U emotion_object_play_set(w->emotion, EINA_TRUE); _win_play_pause_toggle(w); _win_play_eval(w); + + mpris_signal_player_caps_change(enjoy_caps_get()); } static void @@ -372,6 +381,8 @@ _win_action_pause(void *data, Evas_Object *o __UNUSED__, const char *emission __ emotion_object_play_set(w->emotion, EINA_FALSE); _win_play_pause_toggle(w); _win_play_eval(w); + + mpris_signal_player_caps_change(enjoy_caps_get()); } static void @@ -515,6 +526,202 @@ _win_edje_msg(void *data, Evas_Object *o __UNUSED__, Edje_Message_Type type, int } } +void +enjoy_control_next(void) +{ + Win *w = &_win; + _win_next(w, NULL, NULL, NULL); +} + +void +enjoy_control_previous(void) +{ + Win *w = &_win; + _win_prev(w, NULL, NULL, NULL); +} + +void +enjoy_control_pause(void) +{ + Win *w = &_win; + _win_action_pause(w, NULL, NULL, NULL); +} + +void +enjoy_control_stop(void) +{ + Win *w = &_win; + _win_action_pause(&_win, NULL, NULL, NULL); + w->play.position = 0.0; + emotion_object_position_set(w->emotion, w->play.position); +} + +void +enjoy_control_play(void) +{ + Win *w = &_win; + _win_action_play(w, NULL, NULL, NULL); +} + +void +enjoy_control_seek(uint64_t position) +{ + Win *w = &_win; + double seek_to; + + seek_to = w->play.position + w->play.length / ((double)position / 1e6); + if (seek_to <= 0.0) + seek_to = 0.0; + else if (seek_to >= 1.0) + seek_to = 1.0; + + w->play.position = seek_to; + emotion_object_position_set(w->emotion, w->play.position); +} + +void +enjoy_quit(void) +{ + ecore_main_loop_quit(); +} + +int +enjoy_caps_get(void) +{ + Win *w = &_win; + int caps = 0; + + if (list_prev_exists(w->list)) caps |= CAN_GO_PREV; + if ((w->play.shuffle) || (list_next_exists(w->list))) caps |= CAN_GO_NEXT; + if (w->song) + { + caps |= CAN_PAUSE; + caps |= CAN_PLAY; + if (emotion_object_seekable_get(w->emotion)) + caps |= CAN_SEEK; + caps |= CAN_PROVIDE_METADATA; + caps |= CAN_HAS_TRACKLIST; + } + + return caps; +} + +void +enjoy_status_get(int *playback, int *shuffle, int *repeat, int *endless) +{ + Win *w = &_win; + if (playback) + { + if (w->play.playing) + *playback = 0; + else if (w->play.position == 0.0) + *playback = 2; + else + *playback = 1; + } + + if (shuffle) *shuffle = !!w->play.shuffle; + if (repeat) *repeat = !!w->play.repeat; + if (endless) *endless = 0; +} + +void +enjoy_repeat_set(Eina_Bool repeat) +{ + Win *w = &_win; + w->play.repeat = !!repeat; +} + +Eina_Bool +enjoy_repeat_get(void) +{ + Win *w = &_win; + return w->play.repeat; +} + +void +enjoy_position_set(int32_t position) +{ + Win *w = &_win; + + w->play.position = w->play.length / ((double)position / 1e6); + if (w->play.position < 0.0) + w->play.position = 0.0; + else if (w->play.position > 1.0) + w->play.position = 1.0; + + emotion_object_position_set(w->emotion, w->play.position); +} + +int32_t +enjoy_position_get(void) +{ + Win *w = &_win; + return w->play.position * w->play.length; +} + +int32_t +enjoy_volume_get(void) +{ + Win *w = &_win; + return w->play.volume * 100; +} + +void +enjoy_volume_set(int32_t volume) +{ + Win *w = &_win; + w->play.volume = (double)volume / 100.0; + emotion_object_audio_volume_set(w->emotion, w->play.volume); +} + +Song * +enjoy_song_current_get(void) +{ + Win *w = &_win; + return w->song; +} + +Song * +enjoy_song_position_get(int32_t position) +{ + Win *w = &_win; + return db_song_get(w->db, position); +} + +void +enjoy_control_loop_set(Eina_Bool param) +{ + Win *w = &_win; + param = !!param; + edje_object_message_send(elm_layout_edje_get(w->nowplaying), + EDJE_MESSAGE_INT, MSG_LOOP, ¶m); +} + +void +enjoy_control_shuffle_set(Eina_Bool param) +{ + Win *w = &_win; + param = !!param; + edje_object_message_send(elm_layout_edje_get(w->nowplaying), + EDJE_MESSAGE_INT, MSG_SHUFFLE, ¶m); +} + +int32_t +enjoy_playlist_current_position_get(void) +{ + Win *w = &_win; + if (!w->list) return 0; + return list_song_selected_n_get(w->list); +} + +Song * +enjoy_playlist_song_position_get(int32_t position) +{ + Win *w = &_win; + if (!w->list) return NULL; + return list_song_nth_get(w->list, position); +} Evas_Object * win_new(App *app)