From ec44a4391a70713d33311f90723a60deef6f60ec Mon Sep 17 00:00:00 2001 From: arne Date: Tue, 13 Apr 2021 18:15:53 +0200 Subject: [PATCH] avahi service publishing --- CMakeLists.txt | 5 ++ README.md | 2 +- sunshine/main.cpp | 2 + sunshine/nvhttp.cpp | 2 - sunshine/nvhttp.h | 2 + sunshine/publish.cpp | 134 +++++++++++++++++++++++++++++++++++++++++++ sunshine/publish.h | 16 ++++++ 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 sunshine/publish.cpp create mode 100644 sunshine/publish.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 01e933ff..a57321d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ else() list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11 REQUIRED) + pkg_check_modules(AVAHI REQUIRED avahi-client) set(PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp sunshine/platform/linux/input.cpp) @@ -98,10 +99,12 @@ else() evdev pulse pulse-simple + ${AVAHI_LIBRARIES} ) set(PLATFORM_INCLUDE_DIRS ${X11_INCLUDE_DIR} + ${AVAHI_INCLUDE_DIRS} /usr/include/libevdev-1.0) if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH) @@ -152,6 +155,8 @@ set(SUNSHINE_TARGET_FILES sunshine/thread_safe.h sunshine/sync.h sunshine/round_robin.h + sunshine/publish.h + sunshine/publish.cpp ${PLATFORM_TARGET_FILES}) include_directories( diff --git a/README.md b/README.md index fc414370..79053d2c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Sunshine is a Gamestream host for Moonlight ### Requirements: Ubuntu 20.04: - sudo apt install cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev + sudo apt install cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libavahi-client-dev ### Compilation: - `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` diff --git a/sunshine/main.cpp b/sunshine/main.cpp index c21f81fc..0620cde3 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -20,6 +20,7 @@ #include "rtsp.h" #include "config.h" #include "thread_pool.h" +#include "publish.h" #include "platform/common.h" extern "C" { @@ -147,6 +148,7 @@ int main(int argc, char *argv[]) { task_pool.start(1); std::thread httpThread { nvhttp::start, shutdown_event }; + std::thread publishThread { publish::start, shutdown_event }; stream::rtpThread(shutdown_event); httpThread.join(); diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 6ce58a7e..eb9003d1 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -29,8 +29,6 @@ namespace nvhttp { using namespace std::literals; -constexpr auto PORT_HTTP = 47989; -constexpr auto PORT_HTTPS = 47984; constexpr auto VERSION = "7.1.400.0"; constexpr auto GFE_VERSION = "3.12.0.1"; diff --git a/sunshine/nvhttp.h b/sunshine/nvhttp.h index eb90540a..0bba77f9 100644 --- a/sunshine/nvhttp.h +++ b/sunshine/nvhttp.h @@ -15,6 +15,8 @@ #define CERTIFICATE_FILE CA_DIR "/cacert.pem" namespace nvhttp { +constexpr auto PORT_HTTP = 47989; +constexpr auto PORT_HTTPS = 47984; void start(std::shared_ptr shutdown_event); } diff --git a/sunshine/publish.cpp b/sunshine/publish.cpp new file mode 100644 index 00000000..23a0a04f --- /dev/null +++ b/sunshine/publish.cpp @@ -0,0 +1,134 @@ + +// adapted from https://www.avahi.org/doxygen/html/client-publish-service_8c-example.html +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "publish.h" +#include "nvhttp.h" +#include "main.h" + +namespace publish { +AvahiEntryGroup *group = NULL; +AvahiSimplePoll *simple_poll = NULL; +char *name = NULL; +void create_services(AvahiClient *c); + +void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { + assert(g == group || group == NULL); + group = g; + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + BOOST_LOG(info) << "Avahi service " << name << " successfully established."; + break; + case AVAHI_ENTRY_GROUP_COLLISION: + char *n; + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; + BOOST_LOG(info) << "Avahi service name collision, renaming service to " << name; + create_services(avahi_entry_group_get_client(g)); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + BOOST_LOG(error) << "Avahi entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))); + avahi_simple_poll_quit(simple_poll); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + ; + } +} + +void create_services(AvahiClient *c) { + char *n; + int ret; + assert(c); + if (!group) { + if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) { + BOOST_LOG(error) << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c)); + goto fail; + } + } + if (avahi_entry_group_is_empty(group)) { + BOOST_LOG(info) << "Adding avahi service " << name; + + if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags(0), name, SERVICE_TYPE, NULL, NULL, nvhttp::PORT_HTTP, NULL)) < 0) { + if (ret == AVAHI_ERR_COLLISION) + goto collision; + BOOST_LOG(error) << "Failed to add " << SERVICE_TYPE << " service: " << avahi_strerror(ret); + goto fail; + } + if ((ret = avahi_entry_group_commit(group)) < 0) { + BOOST_LOG(error) << "Failed to commit entry group: " << avahi_strerror(ret); + goto fail; + } + } + return; +collision: + // A service name collision with a local service happened. Let's pick a new name + n = avahi_alternative_service_name(name); + avahi_free(name); + name = n; + BOOST_LOG(info) << "Service name collision, renaming service to " << name; + avahi_entry_group_reset(group); + create_services(c); + return; +fail: + avahi_simple_poll_quit(simple_poll); +} + +void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata) { + assert(c); + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + create_services(c); + break; + case AVAHI_CLIENT_FAILURE: + BOOST_LOG(error) << "Client failure: " << avahi_strerror(avahi_client_errno(c)); + avahi_simple_poll_quit(simple_poll); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + if (group) + avahi_entry_group_reset(group); + break; + case AVAHI_CLIENT_CONNECTING: + ; + } +} + +void start(std::shared_ptr shutdown_event) { + AvahiClient *client = NULL; + int error; + if (!(simple_poll = avahi_simple_poll_new())) { + fprintf(stderr, "Failed to create simple poll object.\n"); + return; + } + name = avahi_strdup(SERVICE_NAME); + client = avahi_client_new(avahi_simple_poll_get(simple_poll), AvahiClientFlags(0), client_callback, NULL, &error); + if (!client) { + fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error)); + avahi_simple_poll_free(simple_poll); + return; + } + + std::thread poll_thread { avahi_simple_poll_loop, simple_poll }; + + // Wait for any event + shutdown_event->view(); + + avahi_simple_poll_quit(simple_poll); + + poll_thread.join(); + + avahi_client_free(client); + avahi_simple_poll_free(simple_poll); + avahi_free(name); +} +}; // namespace publish \ No newline at end of file diff --git a/sunshine/publish.h b/sunshine/publish.h new file mode 100644 index 00000000..db29fdb6 --- /dev/null +++ b/sunshine/publish.h @@ -0,0 +1,16 @@ +#ifndef SUNSHINE_PUBLISH_H +#define SUNSHINE_PUBLISH_H + +#include +#include + +#include "thread_safe.h" + +#define SERVICE_NAME "Sunshine" +#define SERVICE_TYPE "_nvstream._tcp" + +namespace publish { +void start(std::shared_ptr shutdown_event); +} + +#endif //SUNSHINE_PUBLISH_H