[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
This commit is contained in:
Leandro Pereira 2010-11-10 21:04:56 +00:00
parent c0175e8dc9
commit 026715693f
13 changed files with 998 additions and 19 deletions

View File

@ -1,2 +1,3 @@
Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
Rafael Antognolli <antognolli@profusion.mobi>
Leandro Pereira <leandro@profusion.mobi>

1
TODO
View File

@ -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)

View File

@ -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])

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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)
{

View File

@ -5,6 +5,7 @@
#ifndef ELM_LIB_QUICKLAUNCH
#include "private.h"
#include "mpris.h"
#include <Ecore_Getopt.h>
#include <Ecore_File.h>
@ -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;
}

590
src/bin/mpris.c Normal file
View File

@ -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, &param, DBUS_TYPE_INVALID);
enjoy_control_shuffle_set(param);
return reply;
}

26
src/bin/mpris.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef ENJOY_MPRIS_H
#define ENJOY_MPRIS_H
#include <E_DBus.h>
#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 */

View File

@ -1,4 +1,5 @@
#include "private.h"
#include "mpris.h"
#include <ctype.h>
#include <stdlib.h>
@ -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,

View File

@ -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);

View File

@ -1,11 +1,7 @@
#include "private.h"
#include "mpris.h"
#include <Emotion.h>
#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, &param);
}
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, &param);
}
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)