diff --git a/Makefile b/Makefile index e2fb69b..a7fe872 100644 --- a/Makefile +++ b/Makefile @@ -3,9 +3,9 @@ TARGET=libairplay SRCDIR=src/ OBJDIR=obj/ -CC=clang++ -CFLAGS=-c -std=c++17 -Iinclude/ -g -O2 -Wall -Wno-unused-function -Wshadow -fno-rtti -fblocks -LDFLAGS=-stdlib=libc++ -lpthread -g +CC?=clang++ +CFLAGS?=-c -std=c++17 -Iinclude/ -g -O2 -Wall -Wno-unused-function -Wshadow -fno-rtti -fblocks +LDFLAGS?=-stdlib=libc++ -lpthread -g SRCS=$(wildcard $(SRCDIR)*.cpp) OBJS=$(addprefix $(OBJDIR),$(notdir $(SRCS:.cpp=.o))) @@ -17,7 +17,7 @@ $(OBJDIR): mkdir -p $(OBJDIR) $(TARGET): $(OBJS) - $(CC) $(LDFLAGS) $(OBJS) -o $(TARGET) + $(CC) $(LDFLAGS) $(OBJS) -o $(TARGET) $(LIBS) $(OBJDIR)%.o: $(SRCDIR)%.cpp $(CC) $(CFLAGS) $< -o $@ diff --git a/Makefile.linux b/Makefile.linux new file mode 100644 index 0000000..29b20ea --- /dev/null +++ b/Makefile.linux @@ -0,0 +1,6 @@ +# GNU make +CFLAGS=-c -std=c++17 -Iinclude/ -g -O2 -Wall -Wno-unused-function -Wshadow -fno-rtti -DAVAHI_COMPAT=1 +LDFLAGS=-lpthread -g +LIBS=-lstdc++ -ldns_sd + +include Makefile diff --git a/Makefile.osx b/Makefile.osx new file mode 100644 index 0000000..c79132e --- /dev/null +++ b/Makefile.osx @@ -0,0 +1,6 @@ +# Which is technically BSD-derived Makefile(5) +CC=clang++ +CFLAGS=-c -std=c++17 -Iinclude/ -g -O2 -Wall -Wno-unused-function -Wshadow -fno-rtti -fblocks +LDFLAGS=-stdlib=libc++ -lpthread -g + +include Makefile diff --git a/README.md b/README.md index dd22ab4..fe0de14 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,16 @@ A C++ library to stream photos and videos via Airplay. including but not limited to Apple TV's. - Straightforward options and actions (the [airplay-hangman](https://github.com/firebolt55439/airplay-hangman) repository uses this). -Be advised that this project makes use of system calls only available on macOS, and -will require some tweaking to make it compatible with Linux-based OS's. +# Building + +## MacOS +```sh +# Use Xcode/BSD-derived make: +make -f Makefile.osx +``` + +## Linux +```sh +# Needs Avahi/mDNS compatibility libraries installed. Use GNU make: +$ make -f Makefile.linux +``` diff --git a/include/safe_socket.hpp b/include/safe_socket.hpp index 0d7870c..8450c40 100644 --- a/include/safe_socket.hpp +++ b/include/safe_socket.hpp @@ -38,6 +38,11 @@ class safe_socket final { void connect(const address& remote_address); void send(const buffer& data); buffer recv(size_t bytes_to_read); +#ifdef AVAHI_COMPAT + // This is here, just because there's no better place for it now. + // Needed to fix the AVAHI quircks, that requires a non-blocking socket. + static int set_nonblocking(int socket); +#endif private: const auto_close_fd _fd; diff --git a/src/airplay_browser.cpp b/src/airplay_browser.cpp index 8c2aeec..6dd3734 100644 --- a/src/airplay_browser.cpp +++ b/src/airplay_browser.cpp @@ -8,6 +8,9 @@ #include "common.hpp" #include "airplay_browser.hpp" +#ifdef AVAHI_COMPAT +#include "safe_socket.hpp" +#endif const std::string airplay_browser::AIRPLAY_REGTYPE = "_airplay._tcp"; @@ -20,6 +23,9 @@ airplay_browser::airplay_browser() { reinterpret_cast(&browse_callback), this), "DNSServiceBrowse failed"); +#ifdef AVAHI_COMPAT + safe_socket::set_nonblocking(DNSServiceRefSockFD(_dns_browse_ref)); +#endif } airplay_browser::~airplay_browser() { @@ -50,6 +56,13 @@ void airplay_browser::browse_callback(DNSServiceRef sdRef, context->_current_resolved_service_name = std::string(serviceName); DNSServiceRef dns_resolve_ref = nullptr; + +#ifdef AVAHI_COMPAT + if (flags != kDNSServiceFlagsAdd) // Only support Add for now! + return; + flags = 0; // Otherwise the Avahi compatibility layer just chokes on MDNServiceResolve(). +#endif + if(kDNSServiceErr_NoError != DNSServiceResolve(&dns_resolve_ref, flags, interfaceIndex, @@ -62,7 +75,24 @@ void airplay_browser::browse_callback(DNSServiceRef sdRef, return; } +#ifdef AVAHI_COMPAT + int socket = DNSServiceRefSockFD(dns_resolve_ref); + safe_socket::set_nonblocking(socket); + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(socket, &read_fds); + + while (1) { + if(select(socket+1, &read_fds, NULL, NULL, NULL) < 0) { + perror("select"); + } + DNSServiceProcessResult(dns_resolve_ref); + if (!context->_devices.empty()) /* FIXME: this works only the first time! */ + break; + } +#else DNSServiceProcessResult(dns_resolve_ref); +#endif DNSServiceRefDeallocate(dns_resolve_ref); } @@ -90,6 +120,23 @@ void airplay_browser::resolve_callback(DNSServiceRef sdRef, } void airplay_browser::wait_for_devices() { +#ifdef AVAHI_COMPAT + int socket = DNSServiceRefSockFD(_dns_browse_ref); + + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(socket, &read_fds); + + while(1) { + if(select(socket+1, &read_fds, NULL, NULL, NULL) < 0) { + perror("select"); + } +#endif CHECK_AND_THROW(kDNSServiceErr_NoError == DNSServiceProcessResult(_dns_browse_ref), "Error processing mDNS daemon response") +#ifdef AVAHI_COMPAT + if (!_devices.empty()) // FIXME: this works only for the first time! + break; + } +#endif } diff --git a/src/main.cpp b/src/main.cpp index e3100e5..d0bfaf2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,14 +6,19 @@ #include "airplay_browser.hpp" __attribute__((weak)) int main() { +#ifdef AVAHI_COMPAT + setenv("AVAHI_COMPAT_NOWARN", "y", 0); // disable the annoying warnign! +#endif std::cout << "[+] Fetching AirPlay devices:" << std::endl; const auto devices = airplay_browser::get_devices(); for (auto device : devices) { std::cout << " > " << device.first << std::endl; + std::cout << " > " << device.second.get_printable_address() << std::endl; } std::cout << "[+] Connecting to device:" << std::endl; - airplay_device appletv(devices.at("Living Room Apple TV")); + airplay_device appletv(devices.begin()->second); +// airplay_device appletv(devices.at("Samsung Q50 Series LR")); std::cout << appletv.send_message(MessageType::GetServices) << std::endl; return 0; diff --git a/src/safe_socket.cpp b/src/safe_socket.cpp index 3e5a97c..46f2c30 100644 --- a/src/safe_socket.cpp +++ b/src/safe_socket.cpp @@ -5,6 +5,10 @@ // Copyright © 2018 LPWS. All rights reserved. // +#ifdef AVAHI_COMPAT +#include +#include +#endif #include #include #include @@ -50,3 +54,21 @@ buffer safe_socket::recv(size_t bytes_to_read) { result.resize(bytes_read); return result; } + +#ifdef AVAHI_COMPAT +/* Based upon https://stackoverflow.com/questions/7391079/avahi-dns-sd-compatibility-layer-fails-to-run-browse-callback */ +int safe_socket::set_nonblocking(int socket) { + int flags; + /* If they have O_NONBLOCK, use the Posix way to do it */ +#if defined(O_NONBLOCK) + /* FIXME: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */ + if (-1 == (flags = fcntl(socket, F_GETFL, 0))) + flags = 0; + return fcntl(socket, F_SETFL, flags | O_NONBLOCK); +#else + /* Otherwise, use the old way of doing it */ + flags = 1; + return ioctl(_fd, FIOBIO, &flags); +#endif +} +#endif