diff --git a/.gitignore b/.gitignore index ff44af7a..0c14eafc 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ shairport-sync.core /mpris-interface.* /mpris-player-interface.* /dbus-diagnostics-interface.* +/dbus-basic-remote-control-interface.* # Some eclipse project files .cproject diff --git a/Makefile.am b/Makefile.am index aa347904..3627d54c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -111,7 +111,17 @@ dbus-diagnostics-interface.h dbus-diagnostics-interface.c: org.gnome.ShairportS gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-diagnostics-interface org.gnome.ShairportSync.Diagnostics.xml endif +if USE_DBUS_BASIC_REMOTE_CONTROL +shairport_sync_SOURCES += dbus-basic-remote-control.c dbus-basic-remote-control-interface.c +BUILT_SOURCES += dbus-basic-remote-control-interface.h dbus-basic-remote-control-interface.c +# We don't want to install this header +noinst_HEADERS += $(BUILT_SOURCES) +# Correctly clean the generated headers, but keep the xml description +CLEANFILES += $(BUILT_SOURCES) +dbus-basic-remote-control-interface.h dbus-basic-remote-control-interface.c: org.gnome.ShairportSync.Basic.Remote.Control.xml + gdbus-codegen --interface-prefix org.gnome --generate-c-code dbus-basic-remote-control-interface org.gnome.ShairportSync.Basic.Remote.Control.xml +endif if USE_MPRIS shairport_sync_SOURCES += mpris-service.c mpris-interface.c mpris-player-interface.c diff --git a/configure.ac b/configure.ac index 62d9d761..88a9efcd 100644 --- a/configure.ac +++ b/configure.ac @@ -280,11 +280,14 @@ AC_ARG_WITH(dbus, [ --with-dbus-interface = include support for a native Shairp HAS_DBUS=1 AC_DEFINE([HAVE_DBUS], 1, [Needed by the compiler.]) AC_DEFINE([HAVE_DBUS_DIAGNOSTICS], 1, [Needed by the compiler.]) + AC_DEFINE([HAVE_DBUS_BASIC_REMOTE_CONTROL], 1, [Needed by the compiler.]) PKG_CHECK_MODULES([GIO_UNIX], [gio-unix-2.0 >= 2.30.0],[CFLAGS="${GIO_UNIX_CFLAGS} ${CFLAGS}" LIBS="${GIO_UNIX_LIBS} ${LIBS}"],[AC_MSG_ERROR(dbus messaging support requires the glib 2.0 library -- libglib2.0-dev suggested!)]) ], ) AM_CONDITIONAL([USE_DBUS], [test "x$HAS_DBUS" = "x1"]) # Automatically include the diagnostics interface AM_CONDITIONAL([USE_DBUS_DIAGNOSTICS], [test "x$HAS_DBUS" = "x1"]) +# Automatically include the basic remote control interface +AM_CONDITIONAL([USE_DBUS_BASIC_REMOTE_CONTROL], [test "x$HAS_DBUS" = "x1"]) # Look for dbus test client flag AC_ARG_WITH(dbus-test-client, [ --with-dbus-test-client = compile dbus test client], [ diff --git a/dbus-basic-remote-control.c b/dbus-basic-remote-control.c new file mode 100644 index 00000000..ded1457a --- /dev/null +++ b/dbus-basic-remote-control.c @@ -0,0 +1,233 @@ +#include +#include + +#include "config.h" + +#include "common.h" +#include "player.h" +#include "rtsp.h" + +#include "rtp.h" + +#include "dacp.h" +#include "metadata_hub.h" + +#include "dbus-basic-remote-control.h" +#include "dbus-basic-remote-control-interface.h" + +ShairportSyncBasicRemoteControl *shairportSyncBasicRemoteControlSkeleton; + +static gboolean on_handle_fast_forward(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("beginff"); + shairport_sync_basic_remote_control_complete_fast_forward(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_rewind(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("beginrew"); + shairport_sync_basic_remote_control_complete_rewind(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_toggle_mute(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("mutetoggle"); + shairport_sync_basic_remote_control_complete_toggle_mute(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_next(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("nextitem"); + shairport_sync_basic_remote_control_complete_next(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_previous(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("previtem"); + shairport_sync_basic_remote_control_complete_previous(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_pause(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("pause"); + shairport_sync_basic_remote_control_complete_pause(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_play_pause(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("playpause"); + shairport_sync_basic_remote_control_complete_play_pause(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_play(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("play"); + shairport_sync_basic_remote_control_complete_play(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_stop(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("stop"); + shairport_sync_basic_remote_control_complete_stop(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_resume(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("playresume"); + shairport_sync_basic_remote_control_complete_resume(skeleton, invocation); + return TRUE; +} + +/* +static gboolean on_handle_shuffle_songs(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("shuffle_songs"); + shairport_sync_basic_remote_control_complete_shuffle_songs(skeleton, invocation); + return TRUE; +} +*/ +static gboolean on_handle_volume_up(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("volumeup"); + shairport_sync_basic_remote_control_complete_volume_up(skeleton, invocation); + return TRUE; +} + +static gboolean on_handle_volume_down(ShairportSyncBasicRemoteControl *skeleton, GDBusMethodInvocation *invocation, + __attribute__((unused)) gpointer user_data) { + send_simple_dacp_command("volumedown"); + shairport_sync_basic_remote_control_complete_volume_down(skeleton, invocation); + return TRUE; +} + + +/* +gboolean notify_elapsed_time_callback(ShairportSyncDiagnostics *skeleton, + __attribute__((unused)) gpointer user_data) { + // debug(1, "\"notify_elapsed_time_callback\" called."); + if (shairport_sync_diagnostics_get_elapsed_time(skeleton)) { + config.debugger_show_elapsed_time = 1; + debug(1, ">> start including elapsed time in logs"); + } else { + config.debugger_show_elapsed_time = 0; + debug(1, ">> stop including elapsed time in logs"); + } + return TRUE; +} + +gboolean notify_delta_time_callback(ShairportSyncDiagnostics *skeleton, + __attribute__((unused)) gpointer user_data) { + // debug(1, "\"notify_delta_time_callback\" called."); + if (shairport_sync_diagnostics_get_delta_time(skeleton)) { + config.debugger_show_relative_time = 1; + debug(1, ">> start including delta time in logs"); + } else { + config.debugger_show_relative_time = 0; + debug(1, ">> stop including delta time in logs"); + } + return TRUE; +} + +gboolean notify_statistics_callback(ShairportSyncDiagnostics *skeleton, + __attribute__((unused)) gpointer user_data) { + // debug(1, "\"notify_statistics_callback\" called."); + if (shairport_sync_diagnostics_get_statistics(skeleton)) { + debug(1, ">> start logging statistics"); + config.statistics_requested = 1; + } else { + debug(1, ">> stop logging statistics"); + config.statistics_requested = 0; + } + return TRUE; +} + +gboolean notify_verbosity_callback(ShairportSyncDiagnostics *skeleton, + __attribute__((unused)) gpointer user_data) { + gint th = shairport_sync_diagnostics_get_verbosity(skeleton); + if ((th >= 0) && (th <= 3)) { + if (th==0) + debug(1, ">> log verbosity set to %d.", th); + debuglev = th; + debug(1, ">> log verbosity set to %d.", th); + } else { + debug(1, ">> invalid log verbosity: %d. Ignored.", th); + } + return TRUE; +} +*/ + +void dbus_basic_remote_control_on_dbus_name_acquired(GDBusConnection *connection, + __attribute__((unused)) const gchar *name, + __attribute__((unused)) gpointer user_data) { + debug(1,"dbus_basic_remote_control_on_dbus_name_acquired"); + shairportSyncBasicRemoteControlSkeleton = shairport_sync_basic_remote_control_skeleton_new(); + g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(shairportSyncBasicRemoteControlSkeleton), connection, + "/org/gnome/ShairportSync", NULL); + + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-fast-forward", G_CALLBACK(on_handle_fast_forward), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-rewind", G_CALLBACK(on_handle_rewind), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-toggle-mute", G_CALLBACK(on_handle_toggle_mute), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-next", G_CALLBACK(on_handle_next), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-previous", G_CALLBACK(on_handle_previous), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-pause", G_CALLBACK(on_handle_pause), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-play-pause", G_CALLBACK(on_handle_play_pause), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-play", G_CALLBACK(on_handle_play), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-stop", G_CALLBACK(on_handle_stop), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-resume", G_CALLBACK(on_handle_resume), NULL); +// g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-shuffle-songs", G_CALLBACK(on_handle_shuffle_songs), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-volume-up", G_CALLBACK(on_handle_volume_up), NULL); + g_signal_connect(shairportSyncBasicRemoteControlSkeleton, "handle-volume-down", G_CALLBACK(on_handle_volume_down), NULL); + + /* + shairport_sync_diagnostics_set_verbosity(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), + debuglev); + + // debug(2,">> log verbosity is %d.",debuglev); + + if (config.statistics_requested == 0) { + shairport_sync_diagnostics_set_statistics(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), FALSE); + // debug(1, ">> statistics logging is off"); + } else { + shairport_sync_diagnostics_set_statistics(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), TRUE); + // debug(1, ">> statistics logging is on"); + } + + if (config.debugger_show_elapsed_time == 0) { + shairport_sync_diagnostics_set_elapsed_time(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), FALSE); + // debug(1, ">> elapsed time is included in log entries"); + } else { + shairport_sync_diagnostics_set_elapsed_time(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), TRUE); + // debug(1, ">> elapsed time is not included in log entries"); + } + + if (config.debugger_show_relative_time == 0) { + shairport_sync_diagnostics_set_delta_time(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), FALSE); + // debug(1, ">> delta time is included in log entries"); + } else { + shairport_sync_diagnostics_set_delta_time(SHAIRPORT_SYNC_DIAGNOSTICS(shairportSyncDiagnosticsSkeleton), TRUE); + // debug(1, ">> delta time is not included in log entries"); + } + + g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::verbosity", + G_CALLBACK(notify_verbosity_callback), NULL); + + g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::statistics", + G_CALLBACK(notify_statistics_callback), NULL); + + g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::elapsed-time", + G_CALLBACK(notify_elapsed_time_callback), NULL); + + g_signal_connect(shairportSyncDiagnosticsSkeleton, "notify::delta-time", + G_CALLBACK(notify_delta_time_callback), NULL); + */ +} + diff --git a/dbus-basic-remote-control.h b/dbus-basic-remote-control.h new file mode 100644 index 00000000..e96c90f7 --- /dev/null +++ b/dbus-basic-remote-control.h @@ -0,0 +1,10 @@ + +#ifndef DBUS_BASIC_REMOTE_CONTROL_H +#define DBUS_BASIC_REMOTE_CONTROL_H + +#include "dbus-basic-remote-control-interface.h" +ShairportSyncBasicRemoteControl *shairportSyncBasicRemoteControlSkeleton; + +void dbus_basic_remote_control_on_dbus_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data); + +#endif /* #ifndef DBUS_BASIC_REMOTE_CONTROL_ */ diff --git a/dbus-service.c b/dbus-service.c index d6fb011e..8a497a65 100644 --- a/dbus-service.c +++ b/dbus-service.c @@ -18,6 +18,9 @@ #include "dbus-diagnostics.h" #endif +#ifdef HAVE_DBUS_BASIC_REMOTE_CONTROL + #include "dbus-basic-remote-control.h" +#endif void dbus_metadata_watcher(struct metadata_bundle *argc, __attribute__((unused)) void *userdata) { // debug(1, "DBUS metadata watcher called"); @@ -103,6 +106,10 @@ static void on_dbus_name_acquired(GDBusConnection *connection, const gchar *name dbus_diagnostics_on_dbus_name_acquired(connection,name,user_data); #endif +#ifdef HAVE_DBUS_BASIC_REMOTE_CONTROL + dbus_basic_remote_control_on_dbus_name_acquired(connection,name,user_data); +#endif + debug(1, "Shairport Sync native D-Bus service started at \"%s\" on the %s bus.", name, (config.dbus_service_bus_type == DBT_session) ? "session" : "system"); } diff --git a/metadata_hub.h b/metadata_hub.h index 8c3606b9..4a7e8c6d 100644 --- a/metadata_hub.h +++ b/metadata_hub.h @@ -89,6 +89,8 @@ typedef struct metadata_bundle { // speaker volume control // int previous_speaker_volume; // this is needed to prevent a loop + int airplay_volume; + metadata_watcher watchers[number_of_watchers]; // functions to call if the metadata is changed. void *watchers_data[number_of_watchers]; // their individual data diff --git a/org.gnome.ShairportSync.Basic.Remote.Control.xml b/org.gnome.ShairportSync.Basic.Remote.Control.xml new file mode 100644 index 00000000..fd048844 --- /dev/null +++ b/org.gnome.ShairportSync.Basic.Remote.Control.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + +