summaryrefslogtreecommitdiffstats
path: root/sd/source/ui/remotecontrol
diff options
context:
space:
mode:
Diffstat (limited to 'sd/source/ui/remotecontrol')
-rw-r--r--sd/source/ui/remotecontrol/AvahiNetworkService.cxx209
-rw-r--r--sd/source/ui/remotecontrol/AvahiNetworkService.hxx25
-rw-r--r--sd/source/ui/remotecontrol/BluetoothServer.cxx1521
-rw-r--r--sd/source/ui/remotecontrol/BluetoothServer.hxx61
-rw-r--r--sd/source/ui/remotecontrol/BluetoothServer.mm1
-rw-r--r--sd/source/ui/remotecontrol/BluetoothServiceRecord.hxx75
-rw-r--r--sd/source/ui/remotecontrol/BufferedStreamSocket.cxx130
-rw-r--r--sd/source/ui/remotecontrol/BufferedStreamSocket.hxx66
-rw-r--r--sd/source/ui/remotecontrol/Communicator.cxx154
-rw-r--r--sd/source/ui/remotecontrol/Communicator.hxx52
-rw-r--r--sd/source/ui/remotecontrol/DiscoveryService.cxx186
-rw-r--r--sd/source/ui/remotecontrol/DiscoveryService.hxx40
-rw-r--r--sd/source/ui/remotecontrol/DiscoveryService.mm1
-rw-r--r--sd/source/ui/remotecontrol/IBluetoothSocket.hxx42
-rw-r--r--sd/source/ui/remotecontrol/ImagePreparer.cxx255
-rw-r--r--sd/source/ui/remotecontrol/ImagePreparer.hxx45
-rw-r--r--sd/source/ui/remotecontrol/Listener.cxx133
-rw-r--r--sd/source/ui/remotecontrol/Listener.hxx62
-rw-r--r--sd/source/ui/remotecontrol/OSXBluetooth.h30
-rw-r--r--sd/source/ui/remotecontrol/OSXBluetooth.mm53
-rw-r--r--sd/source/ui/remotecontrol/OSXBluetoothWrapper.hxx38
-rw-r--r--sd/source/ui/remotecontrol/OSXNetworkService.h30
-rw-r--r--sd/source/ui/remotecontrol/OSXNetworkService.hxx43
-rw-r--r--sd/source/ui/remotecontrol/OSXNetworkService.mm43
-rw-r--r--sd/source/ui/remotecontrol/Receiver.cxx207
-rw-r--r--sd/source/ui/remotecontrol/Receiver.hxx37
-rw-r--r--sd/source/ui/remotecontrol/Server.cxx373
-rw-r--r--sd/source/ui/remotecontrol/Transmitter.cxx86
-rw-r--r--sd/source/ui/remotecontrol/Transmitter.hxx55
-rw-r--r--sd/source/ui/remotecontrol/WINNetworkService.cxx19
-rw-r--r--sd/source/ui/remotecontrol/WINNetworkService.hxx23
-rw-r--r--sd/source/ui/remotecontrol/ZeroconfService.hxx49
32 files changed, 4144 insertions, 0 deletions
diff --git a/sd/source/ui/remotecontrol/AvahiNetworkService.cxx b/sd/source/ui/remotecontrol/AvahiNetworkService.cxx
new file mode 100644
index 000000000..7708e6eb7
--- /dev/null
+++ b/sd/source/ui/remotecontrol/AvahiNetworkService.cxx
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <config_dbus.h>
+
+#include <iostream>
+#include <limits>
+#include <new>
+#include <assert.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <avahi-common/thread-watch.h>
+#include <comphelper/random.hxx>
+
+#if ENABLE_DBUS
+#include <dbus/dbus.h>
+#endif
+
+#include <sal/log.hxx>
+
+#include "AvahiNetworkService.hxx"
+#include "ZeroconfService.hxx"
+
+using namespace sd;
+
+static AvahiClient *client = nullptr;
+static AvahiThreadedPoll *threaded_poll = nullptr;
+static AvahiEntryGroup *group = nullptr;
+static AvahiNetworkService *avahiService = nullptr;
+
+static bool create_services(AvahiClient *c);
+
+static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) {
+ assert(g == group || group == nullptr);
+ group = g;
+
+ /* Called whenever the entry group state changes */
+
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED :
+ /* The entry group has been established successfully */
+ SAL_INFO( "sdremote.wifi", "Service '" << avahiService->getName() << "' successfully established." );
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION : {
+ char *n;
+
+ /* A service name collision with a remote service
+ * happened. Let's pick a new name */
+ n = avahi_alternative_service_name(avahiService->getName().c_str());
+ avahiService->setName(n);
+
+ SAL_INFO( "sdremote.wifi", "Service name collision, renaming service to '" << avahiService->getName() << "'");
+
+ /* And recreate the services */
+ create_services(avahi_entry_group_get_client(g));
+ break;
+ }
+
+ case AVAHI_ENTRY_GROUP_FAILURE :
+
+ SAL_WARN("sdremote.wifi", "Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+
+ /* Some kind of failure happened while we were registering our services */
+ avahi_threaded_poll_quit(threaded_poll);
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ ;
+ }
+}
+
+static bool create_services(AvahiClient *c) {
+ assert(c);
+
+ /* If this is the first time we're called, let's create a new
+ * entry group if necessary */
+ if(!client)
+ return false;
+
+ if (!group)
+ if (!(group = avahi_entry_group_new(c, entry_group_callback, nullptr))) {
+ SAL_WARN("sdremote.wifi", "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c)));
+ avahiService->clear();
+ return false;
+ }
+
+ /* If the group is empty (either because it was just created, or
+ * because it was reset previously, add our entries. */
+
+ if (avahi_entry_group_is_empty(group)) {
+ SAL_INFO("sdremote.wifi", "Adding service '" << avahiService->getName() << "'");
+ char r[128];
+ int nRandom = comphelper::rng::uniform_int_distribution(0, std::numeric_limits<int>::max());
+ snprintf(r, sizeof(r), "random=%i", nRandom);
+ int ret = avahi_entry_group_add_service(
+ group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, static_cast<AvahiPublishFlags>(0),
+ avahiService->getName().c_str(), kREG_TYPE, nullptr, nullptr, 1599, "local", r, nullptr
+ );
+ if (ret < 0) {
+
+ if (ret == AVAHI_ERR_COLLISION){
+ /* A service name collision with a local service happened. Let's
+ * pick a new name */
+ char *n = avahi_alternative_service_name(avahiService->getName().c_str());
+ avahiService->setName(n);
+
+ SAL_WARN("sdremote.wifi", "Service name collision, renaming service to '" << avahiService->getName() << "'");
+
+ avahi_entry_group_reset(group);
+
+ return create_services(c);
+ }
+
+ SAL_WARN("sdremote.wifi", "Failed to add _impressremote._tcp service: " << avahi_strerror(ret));
+ avahiService->clear();
+ return false;
+ }
+
+ /* Tell the server to register the service */
+ if ((ret = avahi_entry_group_commit(group)) < 0) {
+ SAL_WARN("sdremote.wifi", "Failed to commit entry group: " << avahi_strerror(ret));
+ avahiService->clear();
+ return false;
+ }
+ }
+
+ return true; //Services we're already created
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
+ assert(c);
+
+ /* Called whenever the client or server state changes */
+
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ create_services(c);
+ break;
+ case AVAHI_CLIENT_FAILURE:
+ SAL_WARN("sdremote.wifi", "Client failure: " << avahi_strerror(avahi_client_errno(c)));
+ avahiService->clear();
+ break;
+ case AVAHI_CLIENT_S_COLLISION:
+ case AVAHI_CLIENT_S_REGISTERING:
+ if (group)
+ avahi_entry_group_reset(group);
+ break;
+ case AVAHI_CLIENT_CONNECTING:
+ ;
+ }
+}
+
+void AvahiNetworkService::setup() {
+#if ENABLE_DBUS
+ // Sure, without ENABLE_DBUS it probably makes no sense to try to use this Avahi stuff either,
+ // but this is just a stop-gap measure to get this to even compile for now with the probably
+ // pointless combination of configurable options --enable-avahi --enable-dbus --disable-gui.
+
+ // Avahi internally uses D-Bus, which requires the following in order to be
+ // thread-safe (and we potentially access D-Bus from different threads in
+ // different places of the code base):
+ if (!dbus_threads_init_default()) {
+ throw std::bad_alloc();
+ }
+#endif
+
+ int error = 0;
+ avahiService = this;
+ if (!(threaded_poll = avahi_threaded_poll_new())) {
+ SAL_WARN("sdremote.wifi", "avahi_threaded_poll_new '" << avahiService->getName() << "' failed");
+ return;
+ }
+
+ if (!(client = avahi_client_new(avahi_threaded_poll_get(threaded_poll), static_cast<AvahiClientFlags>(0), client_callback, nullptr, &error))) {
+ SAL_WARN("sdremote.wifi", "avahi_client_new failed");
+ return;
+ }
+
+ if(!create_services(client))
+ return;
+
+ /* Finally, start the event loop thread */
+ if (avahi_threaded_poll_start(threaded_poll) < 0) {
+ SAL_WARN("sdremote.wifi", "avahi_threaded_poll_start failed");
+ return;
+ }
+}
+
+void AvahiNetworkService::clear() {
+ /* Call this when the app shuts down */
+ if(threaded_poll)
+ avahi_threaded_poll_stop(threaded_poll);
+ if(client)
+ avahi_client_free(client);
+ if(threaded_poll)
+ avahi_threaded_poll_free(threaded_poll);
+}
diff --git a/sd/source/ui/remotecontrol/AvahiNetworkService.hxx b/sd/source/ui/remotecontrol/AvahiNetworkService.hxx
new file mode 100644
index 000000000..374a27a3a
--- /dev/null
+++ b/sd/source/ui/remotecontrol/AvahiNetworkService.hxx
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <string>
+#include "ZeroconfService.hxx"
+
+namespace sd {
+
+ class AvahiNetworkService : public ZeroconfService
+ {
+ public:
+ AvahiNetworkService(const std::string& aname = "", unsigned int aport = 1599)
+ : ZeroconfService(aname, aport){}
+
+ void clear() override;
+ void setup() override;
+ };
+}
diff --git a/sd/source/ui/remotecontrol/BluetoothServer.cxx b/sd/source/ui/remotecontrol/BluetoothServer.cxx
new file mode 100644
index 000000000..fc3eeff54
--- /dev/null
+++ b/sd/source/ui/remotecontrol/BluetoothServer.cxx
@@ -0,0 +1,1521 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "BluetoothServer.hxx"
+
+#include <iostream>
+#include <memory>
+#include <new>
+#include <string_view>
+
+#include <sal/log.hxx>
+
+#ifdef LINUX_BLUETOOTH
+ #include <glib.h>
+ #include <dbus/dbus.h>
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <unistd.h>
+ #include <sys/socket.h>
+ #include <bluetooth/bluetooth.h>
+ #include <bluetooth/rfcomm.h>
+ #include "BluetoothServiceRecord.hxx"
+ #include "BufferedStreamSocket.hxx"
+#endif
+
+#ifdef _WIN32
+ // LO vs WinAPI conflict
+ #undef WB_LEFT
+ #undef WB_RIGHT
+ #include <winsock2.h>
+ #include <ws2bth.h>
+ #include "BufferedStreamSocket.hxx"
+#endif
+
+#ifdef MACOSX
+ #include <iomanip>
+ #include <osl/conditn.hxx>
+ #include <premac.h>
+ #import <CoreFoundation/CoreFoundation.h>
+ #import <IOBluetooth/IOBluetoothUtilities.h>
+ #import <IOBluetooth/objc/IOBluetoothSDPUUID.h>
+ #import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h>
+ #include <postmac.h>
+ #import "OSXBluetooth.h"
+ #include "OSXBluetoothWrapper.hxx"
+#endif
+
+#include "Communicator.hxx"
+
+using namespace sd;
+
+#ifdef LINUX_BLUETOOTH
+
+namespace {
+
+struct DBusObject {
+ OString maBusName;
+ OString maPath;
+ OString maInterface;
+
+ DBusObject() { }
+ DBusObject( const char *pBusName, const char *pPath, const char *pInterface )
+ : maBusName( pBusName ), maPath( pPath ), maInterface( pInterface ) { }
+
+ DBusMessage *getMethodCall( const char *pName )
+ {
+ return dbus_message_new_method_call( maBusName.getStr(), maPath.getStr(),
+ maInterface.getStr(), pName );
+ }
+ std::unique_ptr<DBusObject> cloneForInterface( const char *pInterface )
+ {
+ std::unique_ptr<DBusObject> pObject(new DBusObject());
+
+ pObject->maBusName = maBusName;
+ pObject->maPath = maPath;
+ pObject->maInterface = pInterface;
+
+ return pObject;
+ }
+};
+
+}
+
+static std::unique_ptr<DBusObject> getBluez5Adapter(DBusConnection *pConnection);
+
+struct sd::BluetoothServer::Impl {
+ // the glib mainloop running in the thread
+ GMainContext *mpContext;
+ DBusConnection *mpConnection;
+ std::unique_ptr<DBusObject> mpService;
+ enum class BluezVersion { BLUEZ4, BLUEZ5, UNKNOWN };
+ BluezVersion maBluezVersion;
+
+ Impl()
+ : mpContext( g_main_context_new() )
+ , mpConnection( nullptr )
+ , maBluezVersion( BluezVersion::UNKNOWN )
+ { }
+
+ std::unique_ptr<DBusObject> getAdapter()
+ {
+ if (mpService)
+ {
+ return mpService->cloneForInterface( "org.bluez.Adapter" );
+ }
+ else if (spServer->mpImpl->maBluezVersion == BluezVersion::BLUEZ5)
+ {
+ return getBluez5Adapter(mpConnection);
+ }
+ else
+ {
+ return nullptr;
+ }
+ }
+};
+
+static DBusConnection *
+dbusConnectToNameOnBus()
+{
+ DBusError aError;
+ DBusConnection *pConnection;
+
+ dbus_error_init( &aError );
+
+ pConnection = dbus_bus_get( DBUS_BUS_SYSTEM, &aError );
+ if( !pConnection || dbus_error_is_set( &aError ))
+ {
+ SAL_WARN( "sdremote.bluetooth", "failed to get dbus system bus: " << aError.message );
+ dbus_error_free( &aError );
+ return nullptr;
+ }
+
+ return pConnection;
+}
+
+static DBusMessage *
+sendUnrefAndWaitForReply( DBusConnection *pConnection, DBusMessage *pMsg )
+{
+ DBusPendingCall *pPending = nullptr;
+
+ if( !pMsg || !dbus_connection_send_with_reply( pConnection, pMsg, &pPending,
+ -1 /* default timeout */ ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "Memory allocation failed on message send" );
+ dbus_message_unref( pMsg );
+ return nullptr;
+ }
+ dbus_connection_flush( pConnection );
+ dbus_message_unref( pMsg );
+
+ dbus_pending_call_block( pPending ); // block for reply
+
+ pMsg = dbus_pending_call_steal_reply( pPending );
+ if( !pMsg )
+ SAL_WARN( "sdremote.bluetooth", "no valid reply / timeout" );
+
+ dbus_pending_call_unref( pPending );
+ return pMsg;
+}
+
+static bool
+isBluez5Available(DBusConnection *pConnection)
+{
+ DBusMessage *pMsg;
+
+ // Simplest ways to check whether we have Bluez 5+ is to check
+ // that we can obtain adapters using the new interfaces.
+ // The first two error checks however don't tell us anything as they should
+ // succeed as long as dbus is working correctly.
+ pMsg = DBusObject( "org.bluez", "/", "org.freedesktop.DBus.ObjectManager" ).getMethodCall( "GetManagedObjects" );
+ if (!pMsg)
+ {
+ SAL_INFO("sdremote.bluetooth", "No GetManagedObjects call created");
+ return false;
+ }
+
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+ if (!pMsg)
+ {
+ SAL_INFO("sdremote.bluetooth", "No reply received");
+ return false;
+ }
+
+ // If dbus is working correctly and we aren't on bluez 5 this is where we
+ // should actually get the error.
+ if (dbus_message_get_error_name( pMsg ))
+ {
+ SAL_INFO( "sdremote.bluetooth", "GetManagedObjects call failed with \""
+ << dbus_message_get_error_name( pMsg )
+ << "\" -- we don't seem to have Bluez 5 available");
+ return false;
+ }
+ SAL_INFO("sdremote.bluetooth", "GetManagedObjects call seems to have succeeded -- we must be on Bluez 5");
+ dbus_message_unref(pMsg);
+ return true;
+}
+
+static std::unique_ptr<DBusObject>
+getBluez5Adapter(DBusConnection *pConnection)
+{
+ DBusMessage *pMsg;
+ // This returns a list of objects where we need to find the first
+ // org.bluez.Adapter1 .
+ pMsg = DBusObject( "org.bluez", "/", "org.freedesktop.DBus.ObjectManager" ).getMethodCall( "GetManagedObjects" );
+ if (!pMsg)
+ return nullptr;
+
+ const gchar* const pInterfaceType = "org.bluez.Adapter1";
+
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ DBusMessageIter aObjectIterator;
+ if (pMsg && dbus_message_iter_init(pMsg, &aObjectIterator))
+ {
+ if (DBUS_TYPE_ARRAY == dbus_message_iter_get_arg_type(&aObjectIterator))
+ {
+ DBusMessageIter aObject;
+ dbus_message_iter_recurse(&aObjectIterator, &aObject);
+ do
+ {
+ if (DBUS_TYPE_DICT_ENTRY == dbus_message_iter_get_arg_type(&aObject))
+ {
+ DBusMessageIter aContainerIter;
+ dbus_message_iter_recurse(&aObject, &aContainerIter);
+ char *pPath = nullptr;
+ do
+ {
+ if (DBUS_TYPE_OBJECT_PATH == dbus_message_iter_get_arg_type(&aContainerIter))
+ {
+ dbus_message_iter_get_basic(&aContainerIter, &pPath);
+ SAL_INFO( "sdremote.bluetooth", "Something retrieved: '"
+ << pPath << "' '");
+ }
+ else if (DBUS_TYPE_ARRAY == dbus_message_iter_get_arg_type(&aContainerIter))
+ {
+ DBusMessageIter aInnerIter;
+ dbus_message_iter_recurse(&aContainerIter, &aInnerIter);
+ do
+ {
+ if (DBUS_TYPE_DICT_ENTRY == dbus_message_iter_get_arg_type(&aInnerIter))
+ {
+ DBusMessageIter aInnerInnerIter;
+ dbus_message_iter_recurse(&aInnerIter, &aInnerInnerIter);
+ do
+ {
+ if (DBUS_TYPE_STRING == dbus_message_iter_get_arg_type(&aInnerInnerIter))
+ {
+ char* pMessage;
+
+ dbus_message_iter_get_basic(&aInnerInnerIter, &pMessage);
+ if (pMessage == std::string_view("org.bluez.Adapter1"))
+ {
+ dbus_message_unref(pMsg);
+ if (pPath)
+ {
+ return std::make_unique<DBusObject>( "org.bluez", pPath, pInterfaceType );
+ }
+ assert(false); // We should already have pPath provided for us.
+ }
+ }
+ }
+ while (dbus_message_iter_next(&aInnerInnerIter));
+ }
+ }
+ while (dbus_message_iter_next(&aInnerIter));
+ }
+ }
+ while (dbus_message_iter_next(&aContainerIter));
+ }
+ }
+ while (dbus_message_iter_next(&aObject));
+ }
+ dbus_message_unref(pMsg);
+ }
+
+ return nullptr;
+}
+
+static DBusObject *
+bluez4GetDefaultService( DBusConnection *pConnection )
+{
+ DBusMessage *pMsg;
+ DBusMessageIter it;
+ const gchar* const pInterfaceType = "org.bluez.Service";
+
+ // org.bluez.manager only exists for bluez 4.
+ // getMethodCall should return NULL if there is any issue e.g. the
+ // if org.bluez.manager doesn't exist.
+ pMsg = DBusObject( "org.bluez", "/", "org.bluez.Manager" ).getMethodCall( "DefaultAdapter" );
+
+ if (!pMsg)
+ {
+ SAL_WARN("sdremote.bluetooth", "Couldn't retrieve DBusObject for DefaultAdapter");
+ return nullptr;
+ }
+
+ SAL_INFO("sdremote.bluetooth", "successfully retrieved org.bluez.Manager.DefaultAdapter, attempting to use.");
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ if(!pMsg || !dbus_message_iter_init( pMsg, &it ) )
+ {
+ return nullptr;
+ }
+
+ // This works for Bluez 4
+ if( DBUS_TYPE_OBJECT_PATH == dbus_message_iter_get_arg_type( &it ) )
+ {
+ const char *pObjectPath = nullptr;
+ dbus_message_iter_get_basic( &it, &pObjectPath );
+ SAL_INFO( "sdremote.bluetooth", "DefaultAdapter retrieved: '"
+ << pObjectPath << "' '" << pInterfaceType << "'" );
+ dbus_message_unref( pMsg );
+ return new DBusObject( "org.bluez", pObjectPath, pInterfaceType );
+ }
+ // Some form of error, e.g. if we have bluez 5 we get a message that
+ // this method doesn't exist.
+ else if ( DBUS_TYPE_STRING == dbus_message_iter_get_arg_type( &it ) )
+ {
+ const char *pMessage = nullptr;
+ dbus_message_iter_get_basic( &it, &pMessage );
+ SAL_INFO( "sdremote.bluetooth", "Error message: '"
+ << pMessage << "' '" << pInterfaceType << "'" );
+ }
+ else
+ {
+ SAL_INFO( "sdremote.bluetooth", "invalid type of reply to DefaultAdapter: '"
+ << static_cast<char>(dbus_message_iter_get_arg_type( &it )) << "'" );
+ }
+ dbus_message_unref(pMsg);
+ return nullptr;
+}
+
+static bool
+bluez4RegisterServiceRecord( DBusConnection *pConnection, DBusObject *pAdapter,
+ const char *pServiceRecord )
+{
+ DBusMessage *pMsg;
+ DBusMessageIter it;
+
+ pMsg = pAdapter->getMethodCall( "AddRecord" );
+ dbus_message_iter_init_append( pMsg, &it );
+ dbus_message_iter_append_basic( &it, DBUS_TYPE_STRING, &pServiceRecord );
+
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ if( !pMsg || !dbus_message_iter_init( pMsg, &it ) ||
+ dbus_message_iter_get_arg_type( &it ) != DBUS_TYPE_UINT32 )
+ {
+ SAL_WARN( "sdremote.bluetooth", "SDP registration failed" );
+ return false;
+ }
+
+ // We ignore the uint de-registration handle we get back:
+ // bluez will clean us up automatically on exit
+
+ return true;
+}
+
+static void
+bluezCreateAttachListeningSocket( GMainContext *pContext, GPollFD *pSocketFD )
+{
+ int nSocket;
+
+ pSocketFD->fd = -1;
+
+ if( ( nSocket = socket( AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM ) ) < 0 )
+ {
+ SAL_WARN( "sdremote.bluetooth", "failed to open bluetooth socket with error " << nSocket );
+ return;
+ }
+
+ sockaddr_rc aAddr;
+ // Initialize whole structure. Mainly to appease valgrind, which
+ // doesn't know about the padding at the end of sockaddr_rc which
+ // it will dutifully check for definedness. But also the standard
+ // definition of BDADDR_ANY is unusable in C++ code, so just use
+ // memset to set aAddr.rc_bdaddr to 0.
+ memset( &aAddr, 0, sizeof( aAddr ) );
+ aAddr.rc_family = AF_BLUETOOTH;
+ aAddr.rc_channel = 5;
+
+ int a;
+ if ( ( a = bind( nSocket, reinterpret_cast<sockaddr*>(&aAddr), sizeof(aAddr) ) ) < 0 ) {
+ SAL_WARN( "sdremote.bluetooth", "bind failed with error" << a );
+ close( nSocket );
+ return;
+ }
+
+ if ( ( a = listen( nSocket, 1 ) ) < 0 )
+ {
+ SAL_WARN( "sdremote.bluetooth", "listen failed with error" << a );
+ close( nSocket );
+ return;
+ }
+
+ // set non-blocking behaviour ...
+ if( fcntl( nSocket, F_SETFL, O_NONBLOCK) < 0 )
+ {
+ close( nSocket );
+ return;
+ }
+
+ pSocketFD->fd = nSocket;
+ pSocketFD->events = G_IO_IN | G_IO_PRI;
+ pSocketFD->revents = 0;
+
+ g_main_context_add_poll( pContext, pSocketFD, G_PRIORITY_DEFAULT );
+}
+
+static void
+bluezDetachCloseSocket( GMainContext *pContext, GPollFD *pSocketFD )
+{
+ if( pSocketFD->fd >= 0 )
+ {
+ close( pSocketFD->fd );
+ g_main_context_remove_poll( pContext, pSocketFD );
+ pSocketFD->fd = -1;
+ }
+}
+
+#endif // LINUX_BLUETOOTH
+
+#if defined(MACOSX)
+
+OSXBluetoothWrapper::OSXBluetoothWrapper( IOBluetoothRFCOMMChannel* channel ) :
+ mpChannel(channel),
+ mnMTU(0),
+ mHaveBytes(),
+ mMutex(),
+ mBuffer()
+{
+ // silly enough, can't write more than mnMTU bytes at once
+ mnMTU = [channel getMTU];
+
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::OSXBluetoothWrapper(): mnMTU=" << mnMTU );
+}
+
+sal_Int32 OSXBluetoothWrapper::readLine( OString& aLine )
+{
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::readLine()" );
+
+ while( true )
+ {
+ {
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::readLine: entering mutex" );
+ ::osl::MutexGuard aQueueGuard( mMutex );
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::readLine: entered mutex" );
+
+#ifdef SAL_LOG_INFO
+ // We should have in the sal logging some standard way to
+ // output char buffers with non-printables escaped.
+ std::ostringstream s;
+ if (mBuffer.size() > 0)
+ {
+ for (unsigned char *p = reinterpret_cast<unsigned char *>(mBuffer.data()); p != reinterpret_cast<unsigned char *>(mBuffer.data()) + mBuffer.size(); p++)
+ {
+ if (*p == '\n')
+ s << "\\n";
+ else if (*p < ' ' || *p >= 0x7F)
+ s << "\\0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(*p) << std::setfill(' ') << std::setw(1) << std::dec;
+ else
+ s << *p;
+ }
+ }
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::readLine mBuffer: \"" << s.str() << "\"" );
+#endif
+
+ // got enough bytes to return a line?
+ std::vector<char>::iterator aIt;
+ if ( (aIt = find( mBuffer.begin(), mBuffer.end(), '\n' ))
+ != mBuffer.end() )
+ {
+ sal_uInt64 aLocation = aIt - mBuffer.begin();
+
+ aLine = OString( &(*mBuffer.begin()), aLocation );
+
+ mBuffer.erase( mBuffer.begin(), aIt + 1 ); // Also delete the empty line
+
+ // yeps
+ SAL_INFO( "sdremote.bluetooth", " returning, got \"" << OStringToOUString( aLine, RTL_TEXTENCODING_UTF8 ) << "\"" );
+ return aLine.getLength() + 1;
+ }
+
+ // nope - wait some more (after releasing the mutex)
+ SAL_INFO( "sdremote.bluetooth", " resetting mHaveBytes" );
+ mHaveBytes.reset();
+ SAL_INFO( "sdremote.bluetooth", " leaving mutex" );
+ }
+
+ SAL_INFO( "sdremote.bluetooth", " waiting for mHaveBytes" );
+ mHaveBytes.wait();
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::readLine: got mHaveBytes" );
+ }
+}
+
+sal_Int32 OSXBluetoothWrapper::write( const void* pBuffer, sal_uInt32 n )
+{
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::write(" << pBuffer << ", " << n << ") mpChannel=" << mpChannel );
+
+ char const * ptr = static_cast<char const *>(pBuffer);
+ sal_uInt32 nBytesWritten = 0;
+
+ if (mpChannel == nil)
+ return 0;
+
+ while( nBytesWritten < n )
+ {
+ int toWrite = n - nBytesWritten;
+ toWrite = toWrite <= mnMTU ? toWrite : mnMTU;
+ if ( [mpChannel writeSync:const_cast<char *>(ptr) length:toWrite] != kIOReturnSuccess )
+ {
+ SAL_INFO( "sdremote.bluetooth", " [mpChannel writeSync:" << static_cast<void const *>(ptr) << " length:" << toWrite << "] returned error, total written " << nBytesWritten );
+ return nBytesWritten;
+ }
+ ptr += toWrite;
+ nBytesWritten += toWrite;
+ }
+ SAL_INFO( "sdremote.bluetooth", " total written " << nBytesWritten );
+ return nBytesWritten;
+}
+
+void OSXBluetoothWrapper::appendData(void* pBuffer, size_t len)
+{
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::appendData(" << pBuffer << ", " << len << ")" );
+
+ if( len )
+ {
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::appendData: entering mutex" );
+ ::osl::MutexGuard aQueueGuard( mMutex );
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::appendData: entered mutex" );
+ mBuffer.insert(mBuffer.begin()+mBuffer.size(),
+ static_cast<char*>(pBuffer), static_cast<char *>(pBuffer)+len);
+ SAL_INFO( "sdremote.bluetooth", " setting mHaveBytes" );
+ mHaveBytes.set();
+ SAL_INFO( "sdremote.bluetooth", " leaving mutex" );
+ }
+}
+
+void OSXBluetoothWrapper::channelClosed()
+{
+ SAL_INFO( "sdremote.bluetooth", "OSXBluetoothWrapper::channelClosed()" );
+
+ mpChannel = nil;
+}
+
+void incomingCallback( void *userRefCon,
+ IOBluetoothUserNotificationRef,
+ IOBluetoothObjectRef objectRef )
+{
+ SAL_INFO( "sdremote.bluetooth", "incomingCallback()" );
+
+ BluetoothServer* pServer = static_cast<BluetoothServer*>(userRefCon);
+
+ IOBluetoothRFCOMMChannel* channel = [IOBluetoothRFCOMMChannel withRFCOMMChannelRef:reinterpret_cast<IOBluetoothRFCOMMChannelRef>(objectRef)];
+
+ OSXBluetoothWrapper* socket = new OSXBluetoothWrapper( channel);
+ Communicator* pCommunicator = new Communicator( std::unique_ptr<IBluetoothSocket>(socket) );
+ pServer->addCommunicator( pCommunicator );
+
+ ChannelDelegate* delegate = [[ChannelDelegate alloc] initWithCommunicatorAndSocket: pCommunicator socket: socket];
+ [channel setDelegate: delegate];
+ [delegate retain];
+
+ pCommunicator->launch();
+}
+
+void BluetoothServer::addCommunicator( Communicator* pCommunicator )
+{
+ mpCommunicators->push_back( pCommunicator );
+}
+
+#endif // MACOSX
+
+#ifdef LINUX_BLUETOOTH
+
+extern "C" {
+ static gboolean ensureDiscoverable_cb(gpointer)
+ {
+ BluetoothServer::doEnsureDiscoverable();
+ return FALSE; // remove source
+ }
+ static gboolean restoreDiscoverable_cb(gpointer)
+ {
+ BluetoothServer::doRestoreDiscoverable();
+ return FALSE; // remove source
+ }
+}
+
+/*
+ * Bluez 4 uses custom methods for setting properties, whereas Bluez 5+
+ * implements properties using the generic "org.freedesktop.DBus.Properties"
+ * interface -- hence we have a specific Bluez 4 function to deal with the
+ * old style of reading properties.
+ */
+static bool
+getBluez4BooleanProperty( DBusConnection *pConnection, DBusObject *pAdapter,
+ const char *pPropertyName, bool *pBoolean )
+{
+ *pBoolean = false;
+
+ if( !pAdapter )
+ return false;
+
+ DBusMessage *pMsg;
+ pMsg = sendUnrefAndWaitForReply( pConnection,
+ pAdapter->getMethodCall( "GetProperties" ) );
+
+ DBusMessageIter it;
+ if( !pMsg || !dbus_message_iter_init( pMsg, &it ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "no valid reply / timeout" );
+ return false;
+ }
+
+ if( DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type( &it ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "no valid reply / timeout" );
+ return false;
+ }
+
+ DBusMessageIter arrayIt;
+ dbus_message_iter_recurse( &it, &arrayIt );
+
+ while( dbus_message_iter_get_arg_type( &arrayIt ) == DBUS_TYPE_DICT_ENTRY )
+ {
+ DBusMessageIter dictIt;
+ dbus_message_iter_recurse( &arrayIt, &dictIt );
+
+ const char *pName = nullptr;
+ if( dbus_message_iter_get_arg_type( &dictIt ) == DBUS_TYPE_STRING )
+ {
+ dbus_message_iter_get_basic( &dictIt, &pName );
+ if( pName != nullptr && !strcmp( pName, pPropertyName ) )
+ {
+ SAL_INFO( "sdremote.bluetooth", "hit " << pPropertyName << " property" );
+ dbus_message_iter_next( &dictIt );
+ dbus_bool_t bBool = false;
+
+ if( dbus_message_iter_get_arg_type( &dictIt ) == DBUS_TYPE_VARIANT )
+ {
+ DBusMessageIter variantIt;
+ dbus_message_iter_recurse( &dictIt, &variantIt );
+
+ if( dbus_message_iter_get_arg_type( &variantIt ) == DBUS_TYPE_BOOLEAN )
+ {
+ dbus_message_iter_get_basic( &variantIt, &bBool );
+ SAL_INFO( "sdremote.bluetooth", "" << pPropertyName << " is " << bBool );
+ *pBoolean = bBool;
+ return true;
+ }
+ else
+ SAL_WARN( "sdremote.bluetooth", "" << pPropertyName << " type " <<
+ dbus_message_iter_get_arg_type( &variantIt ) );
+ }
+ else
+ SAL_WARN( "sdremote.bluetooth", "variant type ? " <<
+ dbus_message_iter_get_arg_type( &dictIt ) );
+ }
+ else
+ {
+ const char *pStr = pName ? pName : "<null>";
+ SAL_INFO( "sdremote.bluetooth", "property '" << pStr << "'" );
+ }
+ }
+ else
+ SAL_WARN( "sdremote.bluetooth", "unexpected property key type "
+ << dbus_message_iter_get_arg_type( &dictIt ) );
+ dbus_message_iter_next( &arrayIt );
+ }
+ dbus_message_unref( pMsg );
+
+ return false;
+}
+
+/*
+ * This gets an org.freedesktop.DBus.Properties boolean
+ * (as opposed to the old Bluez 4 custom properties methods as visible above).
+ */
+static bool
+getDBusBooleanProperty( DBusConnection *pConnection, DBusObject *pAdapter,
+ const char *pPropertyName, bool *pBoolean )
+{
+ assert( pAdapter );
+
+ *pBoolean = false;
+ bool bRet = false;
+
+ std::unique_ptr< DBusObject > pProperties (
+ pAdapter->cloneForInterface( "org.freedesktop.DBus.Properties" ) );
+
+ DBusMessage *pMsg = pProperties->getMethodCall( "Get" );
+
+ DBusMessageIter itIn;
+ dbus_message_iter_init_append( pMsg, &itIn );
+ const char* pInterface = "org.bluez.Adapter1";
+ dbus_message_iter_append_basic( &itIn, DBUS_TYPE_STRING, &pInterface );
+ dbus_message_iter_append_basic( &itIn, DBUS_TYPE_STRING, &pPropertyName );
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ DBusMessageIter it;
+ if( !pMsg || !dbus_message_iter_init( pMsg, &it ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "no valid reply / timeout" );
+ return false;
+ }
+
+ if( DBUS_TYPE_VARIANT != dbus_message_iter_get_arg_type( &it ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "invalid return type" );
+ }
+ else
+ {
+ DBusMessageIter variantIt;
+ dbus_message_iter_recurse( &it, &variantIt );
+
+ if( dbus_message_iter_get_arg_type( &variantIt ) == DBUS_TYPE_BOOLEAN )
+ {
+ dbus_bool_t bBool = false;
+ dbus_message_iter_get_basic( &variantIt, &bBool );
+ SAL_INFO( "sdremote.bluetooth", "" << pPropertyName << " is " << bBool );
+ *pBoolean = bBool;
+ bRet = true;
+ }
+ else
+ {
+ SAL_WARN( "sdremote.bluetooth", "" << pPropertyName << " type " <<
+ dbus_message_iter_get_arg_type( &variantIt ) );
+ }
+
+ const char* pError = dbus_message_get_error_name( pMsg );
+ if ( pError )
+ {
+ SAL_WARN( "sdremote.bluetooth",
+ "Get failed for " << pPropertyName << " on " <<
+ pAdapter->maPath << " with error: " << pError );
+ }
+ }
+ dbus_message_unref( pMsg );
+
+ return bRet;
+}
+
+static void
+setDBusBooleanProperty( DBusConnection *pConnection, DBusObject *pAdapter,
+ const char *pPropertyName, bool bBoolean )
+{
+ assert( pAdapter );
+
+ std::unique_ptr< DBusObject > pProperties(
+ pAdapter->cloneForInterface( "org.freedesktop.DBus.Properties" ) );
+
+ DBusMessage *pMsg = pProperties->getMethodCall( "Set" );
+
+ DBusMessageIter itIn;
+ dbus_message_iter_init_append( pMsg, &itIn );
+ const char* pInterface = "org.bluez.Adapter1";
+ dbus_message_iter_append_basic( &itIn, DBUS_TYPE_STRING, &pInterface );
+ dbus_message_iter_append_basic( &itIn, DBUS_TYPE_STRING, &pPropertyName );
+
+ {
+ DBusMessageIter varIt;
+ dbus_message_iter_open_container( &itIn, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_BOOLEAN_AS_STRING, &varIt );
+ dbus_bool_t bDBusBoolean = bBoolean;
+ dbus_message_iter_append_basic( &varIt, DBUS_TYPE_BOOLEAN, &bDBusBoolean );
+ dbus_message_iter_close_container( &itIn, &varIt );
+ }
+
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ if( !pMsg )
+ {
+ SAL_WARN( "sdremote.bluetooth", "no valid reply / timeout" );
+ }
+ else
+ {
+ const char* pError = dbus_message_get_error_name( pMsg );
+ if ( pError )
+ {
+ SAL_WARN( "sdremote.bluetooth",
+ "Set failed for " << pPropertyName << " on " <<
+ pAdapter->maPath << " with error: " << pError );
+ }
+ dbus_message_unref( pMsg );
+ }
+}
+
+static bool
+getDiscoverable( DBusConnection *pConnection, DBusObject *pAdapter )
+{
+ if (pAdapter->maInterface == "org.bluez.Adapter") // Bluez 4
+ {
+ bool bDiscoverable;
+ if( getBluez4BooleanProperty(pConnection, pAdapter, "Discoverable", &bDiscoverable ) )
+ return bDiscoverable;
+ }
+ else if (pAdapter->maInterface == "org.bluez.Adapter1") // Bluez 5
+ {
+ bool bDiscoverable;
+ if ( getDBusBooleanProperty(pConnection, pAdapter, "Discoverable", &bDiscoverable ) )
+ return bDiscoverable;
+ }
+ return false;
+}
+
+static void
+setDiscoverable( DBusConnection *pConnection, DBusObject *pAdapter, bool bDiscoverable )
+{
+ SAL_INFO( "sdremote.bluetooth", "setDiscoverable to " << bDiscoverable );
+
+ if (pAdapter->maInterface == "org.bluez.Adapter") // Bluez 4
+ {
+ bool bPowered = false;
+ if( !getBluez4BooleanProperty( pConnection, pAdapter, "Powered", &bPowered ) || !bPowered )
+ return; // nothing to do
+
+ DBusMessage *pMsg;
+ DBusMessageIter it, varIt;
+
+ // set timeout to zero
+ pMsg = pAdapter->getMethodCall( "SetProperty" );
+ dbus_message_iter_init_append( pMsg, &it );
+ const char *pTimeoutStr = "DiscoverableTimeout";
+ dbus_message_iter_append_basic( &it, DBUS_TYPE_STRING, &pTimeoutStr );
+ dbus_message_iter_open_container( &it, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_UINT32_AS_STRING, &varIt );
+ dbus_uint32_t nTimeout = 0;
+ dbus_message_iter_append_basic( &varIt, DBUS_TYPE_UINT32, &nTimeout );
+ dbus_message_iter_close_container( &it, &varIt );
+ dbus_connection_send( pConnection, pMsg, nullptr ); // async send - why not ?
+ dbus_message_unref( pMsg );
+
+ // set discoverable value
+ pMsg = pAdapter->getMethodCall( "SetProperty" );
+ dbus_message_iter_init_append( pMsg, &it );
+ const char *pDiscoverableStr = "Discoverable";
+ dbus_message_iter_append_basic( &it, DBUS_TYPE_STRING, &pDiscoverableStr );
+ dbus_message_iter_open_container( &it, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_BOOLEAN_AS_STRING, &varIt );
+ dbus_bool_t bValue = bDiscoverable;
+ dbus_message_iter_append_basic( &varIt, DBUS_TYPE_BOOLEAN, &bValue );
+ dbus_message_iter_close_container( &it, &varIt ); // async send - why not ?
+ dbus_connection_send( pConnection, pMsg, nullptr );
+ dbus_message_unref( pMsg );
+ }
+ else if (pAdapter->maInterface == "org.bluez.Adapter1") // Bluez 5
+ {
+ setDBusBooleanProperty(pConnection, pAdapter, "Discoverable", bDiscoverable );
+ }
+}
+
+static std::unique_ptr<DBusObject>
+registerWithDefaultAdapter( DBusConnection *pConnection )
+{
+ std::unique_ptr<DBusObject> pService(bluez4GetDefaultService( pConnection ));
+ if( pService )
+ {
+ if( !bluez4RegisterServiceRecord( pConnection, pService.get(),
+ bluetooth_service_record ) )
+ {
+ return nullptr;
+ }
+ }
+
+ return pService;
+}
+
+static void ProfileUnregisterFunction
+(DBusConnection *, void *)
+{
+ // We specifically don't need to do anything here.
+}
+
+static DBusHandlerResult ProfileMessageFunction
+(DBusConnection *pConnection, DBusMessage *pMessage, void *user_data)
+{
+ SAL_INFO("sdremote.bluetooth", "ProfileMessageFunction||" << dbus_message_get_interface(pMessage) << "||" << dbus_message_get_member(pMessage));
+
+ if (dbus_message_get_interface(pMessage) == std::string_view("org.bluez.Profile1"))
+ {
+ if (dbus_message_get_member(pMessage) == std::string_view("Release"))
+ {
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ else if (dbus_message_get_member(pMessage) == std::string_view("NewConnection"))
+ {
+ if (!dbus_message_has_signature(pMessage, "oha{sv}"))
+ {
+ SAL_WARN("sdremote.bluetooth", "wrong signature for NewConnection");
+ }
+
+ DBusMessageIter it;
+ if (!dbus_message_iter_init(pMessage, &it))
+ SAL_WARN( "sdremote.bluetooth", "error init dbus" );
+ else
+ {
+ char* pPath;
+ dbus_message_iter_get_basic(&it, &pPath);
+ SAL_INFO("sdremote.bluetooth", "Adapter path:" << pPath);
+
+ if (!dbus_message_iter_next(&it))
+ SAL_WARN("sdremote.bluetooth", "not enough parameters passed");
+
+ // DBUS_TYPE_UNIX_FD == 'h' -- doesn't exist in older versions
+ // of dbus (< 1.3?) hence defined manually for now
+ if ('h' == dbus_message_iter_get_arg_type(&it))
+ {
+
+ int nDescriptor;
+ dbus_message_iter_get_basic(&it, &nDescriptor);
+ std::vector<Communicator*>* pCommunicators = static_cast<std::vector<Communicator*>*>(user_data);
+
+ // Bluez gives us non-blocking sockets, but our code relies
+ // on blocking behaviour.
+ (void)fcntl(nDescriptor, F_SETFL, fcntl(nDescriptor, F_GETFL) & ~O_NONBLOCK);
+
+ SAL_INFO( "sdremote.bluetooth", "connection accepted " << nDescriptor);
+ Communicator* pCommunicator = new Communicator( std::make_unique<BufferedStreamSocket>( nDescriptor ) );
+ pCommunicators->push_back( pCommunicator );
+ pCommunicator->launch();
+ }
+
+ // For some reason an (empty?) reply is expected.
+ DBusMessage* pRet = dbus_message_new_method_return(pMessage);
+ dbus_connection_send(pConnection, pRet, nullptr);
+ dbus_message_unref(pRet);
+
+ // We could read the remote profile version and features here
+ // (i.e. they are provided as part of the DBusMessage),
+ // however for us they are irrelevant (as our protocol handles
+ // equivalent functionality independently of whether we're on
+ // bluetooth or normal network connection).
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ }
+ else if (dbus_message_get_member(pMessage) == std::string_view("RequestDisconnection"))
+ {
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ }
+ SAL_WARN("sdremote.bluetooth", "Couldn't handle message correctly.");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+}
+
+static void
+setupBluez5Profile1(DBusConnection* pConnection, std::vector<Communicator*>* pCommunicators)
+{
+ bool bErr;
+
+ SAL_INFO("sdremote.bluetooth", "Attempting to register our org.bluez.Profile1");
+ static DBusObjectPathVTable aVTable;
+ aVTable.unregister_function = ProfileUnregisterFunction;
+ aVTable.message_function = ProfileMessageFunction;
+
+ // dbus_connection_try_register_object_path could be used but only exists for
+ // dbus >= 1.2 -- we really shouldn't be trying this twice in any case.
+ // (dbus_connection_try_register_object_path also returns an error with more
+ // information which could be useful for debugging purposes.)
+ bErr = !dbus_connection_register_object_path(pConnection, "/org/libreoffice/bluez/profile1", &aVTable, pCommunicators);
+
+ if (bErr)
+ {
+ SAL_WARN("sdremote.bluetooth", "Failed to register Bluez 5 Profile1 callback, bluetooth won't work.");
+ }
+
+ dbus_connection_flush( pConnection );
+}
+
+static void
+unregisterBluez5Profile(DBusConnection* pConnection)
+{
+ DBusMessage* pMsg = dbus_message_new_method_call("org.bluez", "/org/bluez",
+ "org.bluez.ProfileManager1", "UnregisterProfile");
+ DBusMessageIter it;
+ dbus_message_iter_init_append(pMsg, &it);
+
+ const char *pPath = "/org/libreoffice/bluez/profile1";
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_OBJECT_PATH, &pPath);
+
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ if (pMsg)
+ dbus_message_unref(pMsg);
+
+ dbus_connection_unregister_object_path( pConnection, "/org/libreoffice/bluez/profile1");
+
+ dbus_connection_flush(pConnection);
+}
+
+static bool
+registerBluez5Profile(DBusConnection* pConnection, std::vector<Communicator*>* pCommunicators)
+{
+ setupBluez5Profile1(pConnection, pCommunicators);
+
+ DBusMessage *pMsg;
+ DBusMessageIter it;
+
+ pMsg = dbus_message_new_method_call("org.bluez", "/org/bluez",
+ "org.bluez.ProfileManager1", "RegisterProfile");
+ dbus_message_iter_init_append(pMsg, &it);
+
+ const char *pPath = "/org/libreoffice/bluez/profile1";
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_OBJECT_PATH, &pPath);
+ const char *pUUID = "spp"; // Bluez translates this to 0x1101 for spp
+ dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &pUUID);
+
+ DBusMessageIter aOptionsIter;
+ dbus_message_iter_open_container(&it, DBUS_TYPE_ARRAY, "{sv}", &aOptionsIter);
+
+ DBusMessageIter aEntry;
+
+ {
+ dbus_message_iter_open_container(&aOptionsIter, DBUS_TYPE_DICT_ENTRY, nullptr, &aEntry);
+
+ const char *pString = "Name";
+ dbus_message_iter_append_basic(&aEntry, DBUS_TYPE_STRING, &pString);
+
+ const char *pValue = "LibreOffice Impress Remote";
+ DBusMessageIter aValue;
+ dbus_message_iter_open_container(&aEntry, DBUS_TYPE_VARIANT, "s", &aValue);
+ dbus_message_iter_append_basic(&aValue, DBUS_TYPE_STRING, &pValue);
+ dbus_message_iter_close_container(&aEntry, &aValue);
+ dbus_message_iter_close_container(&aOptionsIter, &aEntry);
+ }
+
+ dbus_message_iter_close_container(&it, &aOptionsIter);
+
+ // Other properties that we could set (but don't, since they appear
+ // to be useless for us):
+ // "Service": "0x1101" (not needed, but we used to have it in the manually defined profile).
+ // "Role": setting this to "server" breaks things, although we think we're a server?
+ // "Channel": seems to be dealt with automatically (but we used to use 5 in the manual profile).
+
+ bool bSuccess = true;
+
+ pMsg = sendUnrefAndWaitForReply( pConnection, pMsg );
+
+ DBusError aError;
+ dbus_error_init(&aError);
+ if (pMsg && dbus_set_error_from_message( &aError, pMsg ))
+ {
+ bSuccess = false;
+ SAL_WARN("sdremote.bluetooth",
+ "Failed to register our Profile1 with bluez ProfileManager "
+ << (aError.message ? aError.message : "<null>"));
+ }
+
+ dbus_error_free(&aError);
+ if (pMsg)
+ dbus_message_unref(pMsg);
+
+ dbus_connection_flush(pConnection);
+
+ return bSuccess;
+}
+
+#endif // LINUX_BLUETOOTH
+
+BluetoothServer::BluetoothServer( std::vector<Communicator*>* pCommunicators )
+ : meWasDiscoverable( UNKNOWN ),
+ mpCommunicators( pCommunicators )
+{
+#ifdef LINUX_BLUETOOTH
+ // D-Bus requires the following in order to be thread-safe (and we
+ // potentially access D-Bus from different threads in different places of
+ // the code base):
+ if (!dbus_threads_init_default()) {
+ throw std::bad_alloc();
+ }
+
+ mpImpl.reset(new BluetoothServer::Impl());
+#endif
+}
+
+BluetoothServer::~BluetoothServer()
+{
+}
+
+void BluetoothServer::ensureDiscoverable()
+{
+#ifdef LINUX_BLUETOOTH
+ // Push it all across into our mainloop
+ if( !spServer )
+ return;
+ GSource *pIdle = g_idle_source_new();
+ g_source_set_callback( pIdle, ensureDiscoverable_cb, nullptr, nullptr );
+ g_source_set_priority( pIdle, G_PRIORITY_DEFAULT );
+ g_source_attach( pIdle, spServer->mpImpl->mpContext );
+ g_source_unref( pIdle );
+#endif
+}
+
+void BluetoothServer::restoreDiscoverable()
+{
+#ifdef LINUX_BLUETOOTH
+ // Push it all across into our mainloop
+ if( !spServer )
+ return;
+ GSource *pIdle = g_idle_source_new();
+ g_source_set_callback( pIdle, restoreDiscoverable_cb, nullptr, nullptr );
+ g_source_set_priority( pIdle, G_PRIORITY_DEFAULT_IDLE );
+ g_source_attach( pIdle, spServer->mpImpl->mpContext );
+ g_source_unref( pIdle );
+#endif
+}
+
+void BluetoothServer::doEnsureDiscoverable()
+{
+#ifdef LINUX_BLUETOOTH
+ if (!spServer->mpImpl->mpConnection ||
+ spServer->meWasDiscoverable != UNKNOWN )
+ return;
+
+ // Find out if we are discoverable already ...
+ std::unique_ptr<DBusObject> pAdapter = spServer->mpImpl->getAdapter();
+ if( !pAdapter )
+ return;
+
+ bool bDiscoverable = getDiscoverable(spServer->mpImpl->mpConnection, pAdapter.get() );
+
+ spServer->meWasDiscoverable = bDiscoverable ? DISCOVERABLE : NOT_DISCOVERABLE;
+ if( !bDiscoverable )
+ setDiscoverable( spServer->mpImpl->mpConnection, pAdapter.get(), true );
+#endif
+}
+
+void BluetoothServer::doRestoreDiscoverable()
+{
+ if( spServer->meWasDiscoverable == NOT_DISCOVERABLE )
+ {
+#ifdef LINUX_BLUETOOTH
+ std::unique_ptr<DBusObject> pAdapter = spServer->mpImpl->getAdapter();
+ if( !pAdapter )
+ return;
+ setDiscoverable( spServer->mpImpl->mpConnection, pAdapter.get(), false );
+#endif
+ }
+ spServer->meWasDiscoverable = UNKNOWN;
+}
+
+// We have to have all our clients shut otherwise we can't
+// re-bind to the same port number it appears.
+void BluetoothServer::cleanupCommunicators()
+{
+ for (auto& rpCommunicator : *mpCommunicators)
+ rpCommunicator->forceClose();
+ // the hope is that all the threads then terminate cleanly and
+ // clean themselves up.
+}
+
+void SAL_CALL BluetoothServer::run()
+{
+ SAL_INFO( "sdremote.bluetooth", "BluetoothServer::run called" );
+ osl::Thread::setName("BluetoothServer");
+#ifdef LINUX_BLUETOOTH
+ DBusConnection *pConnection = dbusConnectToNameOnBus();
+ if( !pConnection )
+ return;
+
+ // For either implementation we need to poll the dbus fd
+ int fd = -1;
+ GPollFD aDBusFD;
+ if( dbus_connection_get_unix_fd( pConnection, &fd ) && fd >= 0 )
+ {
+ aDBusFD.fd = fd;
+ aDBusFD.events = G_IO_IN | G_IO_PRI;
+ g_main_context_add_poll( mpImpl->mpContext, &aDBusFD, G_PRIORITY_DEFAULT );
+ }
+ else
+ SAL_WARN( "sdremote.bluetooth", "failed to poll for incoming dbus signals" );
+
+ if (isBluez5Available(pConnection))
+ {
+ SAL_INFO("sdremote.bluetooth", "Using Bluez 5");
+ registerBluez5Profile(pConnection, mpCommunicators);
+ mpImpl->mpConnection = pConnection;
+ mpImpl->maBluezVersion = Impl::BluezVersion::BLUEZ5;
+
+ // We don't need to listen to adapter changes anymore -- profile
+ // registration is done globally for the entirety of bluez, so we only
+ // need adapters when setting discoverability, which can be done
+ // dynamically without the need to listen for changes.
+
+ // TODO: exit on SD deinit
+ // Probably best to do that in SdModule::~SdModule?
+ while (true)
+ {
+ aDBusFD.revents = 0;
+ g_main_context_iteration( mpImpl->mpContext, true );
+ if( aDBusFD.revents )
+ {
+ dbus_connection_read_write( pConnection, 0 );
+ while (DBUS_DISPATCH_DATA_REMAINS == dbus_connection_get_dispatch_status( pConnection ))
+ dbus_connection_dispatch( pConnection );
+ }
+ if ((false)) break;
+ // silence Clang -Wunreachable-code after loop (TODO: proper
+ // fix?)
+ }
+ unregisterBluez5Profile( pConnection );
+ g_main_context_unref( mpImpl->mpContext );
+ mpImpl->mpConnection = nullptr;
+ mpImpl->mpContext = nullptr;
+ return;
+ }
+
+ // Otherwise we could be on Bluez 4 and continue as usual.
+ mpImpl->maBluezVersion = Impl::BluezVersion::BLUEZ4;
+
+ // Try to setup the default adapter, otherwise wait for add/remove signal
+ mpImpl->mpService = registerWithDefaultAdapter( pConnection );
+ // listen for connection state and power changes - we need to close
+ // and re-create our socket code on suspend / resume, enable/disable
+ DBusError aError;
+ dbus_error_init( &aError );
+ dbus_bus_add_match( pConnection, "type='signal',interface='org.bluez.Manager'", &aError );
+ dbus_connection_flush( pConnection );
+
+ // Try to setup the default adapter, otherwise wait for add/remove signal
+ mpImpl->mpService = registerWithDefaultAdapter( pConnection );
+
+ // poll on our bluetooth socket - if we can.
+ GPollFD aSocketFD;
+ if( mpImpl->mpService )
+ bluezCreateAttachListeningSocket( mpImpl->mpContext, &aSocketFD );
+
+ mpImpl->mpConnection = pConnection;
+
+ while( true )
+ {
+ aDBusFD.revents = 0;
+ aSocketFD.revents = 0;
+ g_main_context_iteration( mpImpl->mpContext, true );
+
+ SAL_INFO( "sdremote.bluetooth", "main-loop spin "
+ << aDBusFD.revents << " " << aSocketFD.revents );
+ if( aDBusFD.revents )
+ {
+ dbus_connection_read_write( pConnection, 0 );
+ DBusMessage *pMsg = dbus_connection_pop_message( pConnection );
+ if( pMsg )
+ {
+ if( dbus_message_is_signal( pMsg, "org.bluez.Manager", "AdapterRemoved" ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "lost adapter - cleaning up sockets" );
+ bluezDetachCloseSocket( mpImpl->mpContext, &aSocketFD );
+ cleanupCommunicators();
+ }
+ else if( dbus_message_is_signal( pMsg, "org.bluez.Manager", "AdapterAdded" ) ||
+ dbus_message_is_signal( pMsg, "org.bluez.Manager", "DefaultAdapterChanged" ) )
+ {
+ SAL_WARN( "sdremote.bluetooth", "gained adapter - re-generating sockets" );
+ bluezDetachCloseSocket( mpImpl->mpContext, &aSocketFD );
+ cleanupCommunicators();
+ mpImpl->mpService = registerWithDefaultAdapter( pConnection );
+ if( mpImpl->mpService )
+ bluezCreateAttachListeningSocket( mpImpl->mpContext, &aSocketFD );
+ }
+ else
+ SAL_INFO( "sdremote.bluetooth", "unknown incoming dbus message, "
+ " type: " << dbus_message_get_type( pMsg )
+ << " path: '" << dbus_message_get_path( pMsg )
+ << "' interface: '" << dbus_message_get_interface( pMsg )
+ << "' member: '" << dbus_message_get_member( pMsg ) );
+ }
+ dbus_message_unref( pMsg );
+ }
+
+ if( aSocketFD.revents )
+ {
+ sockaddr_rc aRemoteAddr;
+ socklen_t aRemoteAddrLen = sizeof(aRemoteAddr);
+
+ SAL_INFO( "sdremote.bluetooth", "performing accept" );
+ int nClient = accept( aSocketFD.fd, reinterpret_cast<sockaddr*>(&aRemoteAddr), &aRemoteAddrLen);
+ if ( nClient < 0 && errno != EAGAIN )
+ {
+ SAL_WARN( "sdremote.bluetooth", "accept failed with errno " << errno );
+ } else {
+ SAL_INFO( "sdremote.bluetooth", "connection accepted " << nClient );
+ Communicator* pCommunicator = new Communicator( std::make_unique<BufferedStreamSocket>( nClient ) );
+ mpCommunicators->push_back( pCommunicator );
+ pCommunicator->launch();
+ }
+ }
+ if ((false)) break;
+ // silence Clang -Wunreachable-code after loop (TODO: proper fix?)
+ }
+
+ unregisterBluez5Profile( pConnection );
+ g_main_context_unref( mpImpl->mpContext );
+ mpImpl->mpConnection = nullptr;
+ mpImpl->mpContext = nullptr;
+
+#elif defined(_WIN32)
+ WORD wVersionRequested;
+ WSADATA wsaData;
+
+ wVersionRequested = MAKEWORD(2, 2);
+
+ if ( WSAStartup(wVersionRequested, &wsaData) )
+ {
+ return; // winsock dll couldn't be loaded
+ }
+
+ int aSocket = socket( AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM );
+ if ( !aSocket )
+ {
+ WSACleanup();
+ return;
+ }
+ SOCKADDR_BTH aAddr;
+ aAddr.addressFamily = AF_BTH;
+ aAddr.btAddr = 0;
+ aAddr.serviceClassId = GUID_NULL;
+ aAddr.port = BT_PORT_ANY; // Select any free socket.
+ if ( bind( aSocket, reinterpret_cast<SOCKADDR*>(&aAddr), sizeof(aAddr) ) == SOCKET_ERROR )
+ {
+ closesocket( aSocket );
+ WSACleanup();
+ return;
+ }
+
+ SOCKADDR_BTH aName;
+ int aNameSize = sizeof(aName);
+ getsockname( aSocket, reinterpret_cast<SOCKADDR*>(&aName), &aNameSize ); // Retrieve the local address and port
+
+ CSADDR_INFO aAddrInfo = {};
+ aAddrInfo.LocalAddr.lpSockaddr = reinterpret_cast<SOCKADDR*>(&aName);
+ aAddrInfo.LocalAddr.iSockaddrLength = sizeof( SOCKADDR_BTH );
+ aAddrInfo.iSocketType = SOCK_STREAM;
+ aAddrInfo.iProtocol = BTHPROTO_RFCOMM;
+
+ // To be used for setting a custom UUID once available.
+// GUID uuid;
+// uuid.Data1 = 0x00001101;
+// memset( &uuid, 0x1000 + UUID*2^96, sizeof( GUID ) );
+// uuid.Data2 = 0;
+// uuid.Data3 = 0x1000;
+// ULONGLONG aData4 = 0x800000805F9B34FB;
+// memcpy( uuid.Data4, &aData4, sizeof(uuid.Data4) );
+
+ WSAQUERYSETW aRecord = {};
+ aRecord.dwSize = sizeof(aRecord);
+ aRecord.lpszServiceInstanceName = const_cast<wchar_t *>(
+ L"LibreOffice Impress Remote Control");
+ aRecord.lpszComment = const_cast<wchar_t *>(
+ L"Remote control of presentations over bluetooth.");
+ aRecord.lpServiceClassId = const_cast<LPGUID>(&SerialPortServiceClass_UUID);
+ aRecord.dwNameSpace = NS_BTH;
+ aRecord.dwNumberOfCsAddrs = 1;
+ aRecord.lpcsaBuffer = &aAddrInfo;
+ if (WSASetServiceW( &aRecord, RNRSERVICE_REGISTER, 0 ) == SOCKET_ERROR)
+ {
+ closesocket( aSocket );
+ WSACleanup();
+ return;
+ }
+
+ if ( listen( aSocket, 1 ) == SOCKET_ERROR )
+ {
+ closesocket( aSocket );
+ WSACleanup();
+ return;
+ }
+
+ SOCKADDR_BTH aRemoteAddr;
+ int aRemoteAddrLen = sizeof(aRemoteAddr);
+ while ( true )
+ {
+ SOCKET socket;
+ if ( (socket = accept(aSocket, reinterpret_cast<sockaddr*>(&aRemoteAddr), &aRemoteAddrLen)) == INVALID_SOCKET )
+ {
+ closesocket( aSocket );
+ WSACleanup();
+ return;
+ } else {
+ Communicator* pCommunicator = new Communicator( std::make_unique<BufferedStreamSocket>( socket) );
+ mpCommunicators->push_back( pCommunicator );
+ pCommunicator->launch();
+ }
+ }
+
+#elif defined(MACOSX)
+ // Build up dictionary at run-time instead of bothering with a
+ // .plist file, using the Objective-C API
+
+ // Compare to BluetoothServiceRecord.hxx
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ NSDictionary *dict =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+
+ // Service class ID list
+ [NSArray arrayWithObject:
+ [IOBluetoothSDPUUID uuid16: kBluetoothSDPUUID16ServiceClassSerialPort]],
+ @"0001 - ServiceClassIDList",
+
+ // Protocol descriptor list
+ [NSArray arrayWithObjects:
+ [NSArray arrayWithObject: [IOBluetoothSDPUUID uuid16: kBluetoothSDPUUID16L2CAP]],
+ [NSArray arrayWithObjects:
+ [IOBluetoothSDPUUID uuid16: kBluetoothL2CAPPSMRFCOMM],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt: 1],
+ @"DataElementSize",
+ [NSNumber numberWithInt: 1],
+ @"DataElementType",
+ [NSNumber numberWithInt: 5], // RFCOMM port number, will be replaced if necessary automatically
+ @"DataElementValue",
+ nil],
+ nil],
+ nil],
+ @"0004 - Protocol descriptor list",
+
+ // Browse group list
+ [NSArray arrayWithObject:
+ [IOBluetoothSDPUUID uuid16: kBluetoothSDPUUID16ServiceClassPublicBrowseGroup]],
+ @"0005 - BrowseGroupList",
+
+ // Language base attribute ID list
+ [NSArray arrayWithObjects:
+ [NSData dataWithBytes: "en" length: 2],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt: 2],
+ @"DataElementSize",
+ [NSNumber numberWithInt: 1],
+ @"DataElementType",
+ [NSNumber numberWithInt: 0x006a], // encoding
+ @"DataElementValue",
+ nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt: 2],
+ @"DataElementSize",
+ [NSNumber numberWithInt: 1],
+ @"DataElementType",
+ [NSNumber numberWithInt: 0x0100], // offset
+ @"DataElementValue",
+ nil],
+ nil],
+ @"0006 - LanguageBaseAttributeIDList",
+
+ // Bluetooth profile descriptor list
+ [NSArray arrayWithObject:
+ [NSArray arrayWithObjects:
+ [IOBluetoothSDPUUID uuid16: kBluetoothSDPUUID16ServiceClassSerialPort],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt: 2],
+ @"DataElementSize",
+ [NSNumber numberWithInt: 1],
+ @"DataElementType",
+ [NSNumber numberWithInt: 0x0100], // version number ?
+ @"DataElementValue",
+ nil],
+ nil]],
+ @"0009 - BluetoothProfileDescriptorList",
+
+ // Attributes pointed to by the LanguageBaseAttributeIDList
+ @"LibreOffice Impress Remote Control",
+ @"0100 - ServiceName",
+ @"The Document Foundation",
+ @"0102 - ProviderName",
+ nil];
+
+ // Create service
+ IOBluetoothSDPServiceRecordRef serviceRecordRef;
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.9 IOBluetoothAddServiceDict
+ IOReturn rc = IOBluetoothAddServiceDict(reinterpret_cast<CFDictionaryRef>(dict), &serviceRecordRef);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ SAL_INFO("sdremote.bluetooth", "IOBluetoothAddServiceDict returned " << rc);
+
+ if (rc == kIOReturnSuccess)
+ {
+ IOBluetoothSDPServiceRecord *serviceRecord =
+ [IOBluetoothSDPServiceRecord withSDPServiceRecordRef: serviceRecordRef];
+
+ BluetoothRFCOMMChannelID channelID;
+ [serviceRecord getRFCOMMChannelID: &channelID];
+
+ BluetoothSDPServiceRecordHandle serviceRecordHandle;
+ [serviceRecord getServiceRecordHandle: &serviceRecordHandle];
+
+ // Register callback for incoming connections
+ IOBluetoothRegisterForFilteredRFCOMMChannelOpenNotifications(
+ incomingCallback,
+ this,
+ channelID,
+ kIOBluetoothUserNotificationChannelDirectionIncoming);
+
+ [serviceRecord release];
+ }
+
+ [pool release];
+
+ (void) mpCommunicators;
+#else
+ (void) mpCommunicators; // avoid warnings about unused member
+#endif
+}
+
+BluetoothServer *sd::BluetoothServer::spServer = nullptr;
+
+void BluetoothServer::setup( std::vector<Communicator*>* pCommunicators )
+{
+ if (spServer)
+ return;
+
+ spServer = new BluetoothServer( pCommunicators );
+ spServer->create();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/BluetoothServer.hxx b/sd/source/ui/remotecontrol/BluetoothServer.hxx
new file mode 100644
index 000000000..9e20bfa51
--- /dev/null
+++ b/sd/source/ui/remotecontrol/BluetoothServer.hxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <osl/thread.hxx>
+#include <memory>
+#include <vector>
+
+#include <config_dbus.h>
+
+#if (defined(LINUX) && !defined(__FreeBSD_kernel__)) && ENABLE_DBUS && DBUS_HAVE_GLIB
+# define LINUX_BLUETOOTH
+#endif
+
+namespace sd
+{
+ class Communicator;
+
+ class BluetoothServer:
+ public osl::Thread
+ {
+ public:
+ static void setup( std::vector<Communicator*>* pCommunicators );
+
+ /// ensure that Bluetooth discoverability is on
+ static void ensureDiscoverable();
+ /// restore the state of discoverability from before ensureDiscoverable
+ static void restoreDiscoverable();
+
+ // called by C / idle callbacks
+ static void doEnsureDiscoverable();
+ static void doRestoreDiscoverable();
+
+#if defined(MACOSX)
+ void addCommunicator( Communicator* pCommunicator );
+#endif
+ private:
+ explicit BluetoothServer( std::vector<Communicator*>* pCommunicators );
+ virtual ~BluetoothServer() override;
+
+ enum { UNKNOWN, DISCOVERABLE, NOT_DISCOVERABLE } meWasDiscoverable;
+ static BluetoothServer *spServer;
+
+#ifdef LINUX_BLUETOOTH
+ struct Impl;
+ std::unique_ptr<Impl> mpImpl;
+#endif
+ virtual void SAL_CALL run() override;
+
+ void cleanupCommunicators();
+ std::vector<Communicator*>* mpCommunicators;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/BluetoothServer.mm b/sd/source/ui/remotecontrol/BluetoothServer.mm
new file mode 100644
index 000000000..28288ff6f
--- /dev/null
+++ b/sd/source/ui/remotecontrol/BluetoothServer.mm
@@ -0,0 +1 @@
+#include "BluetoothServer.cxx" \ No newline at end of file
diff --git a/sd/source/ui/remotecontrol/BluetoothServiceRecord.hxx b/sd/source/ui/remotecontrol/BluetoothServiceRecord.hxx
new file mode 100644
index 000000000..c1a00fb3b
--- /dev/null
+++ b/sd/source/ui/remotecontrol/BluetoothServiceRecord.hxx
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+// FIXME: look into sharing definitions across OS's (i.e. UUID and port ).
+// Look into dynamically determining which ports are available.
+
+// SDP is a Service Description Protocol cf.
+// http://developer.bluetooth.org/TechnologyOverview/Pages/DI.aspx
+// This is an XML representation, an alternative would be a
+// binary SDP record.
+
+// for numbers see:
+// https://www.bluetooth.org/Technical/AssignedNumbers/service_discovery.htm
+
+const char * const bluetooth_service_record =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<record>"
+ "<attribute id=\"0x0001\">" // Service class ID list
+ "<sequence>"
+ "<uuid value=\"0x1101\"/>" // an assigned service class meaning: 'serial port'
+ // we could add our own 'LibreOffice remote' service
+ // class here too in future ...
+ "</sequence>"
+ "</attribute>"
+ "<attribute id=\"0x0004\">" // Protocol Descriptor list
+ "<sequence>"
+ "<sequence>"
+ "<uuid value=\"0x0100\"/>" // L2CAP Protocol descriptor
+ "</sequence>"
+ "<sequence>"
+ "<uuid value=\"0x0003\"/>" // enumeration value of RFCOMM protocol
+ "<uint8 value=\"0x05\"/>" // RFCOMM port number
+ "</sequence>"
+ "</sequence>"
+ "</attribute>"
+ "<attribute id=\"0x0005\">" // Browse Group List
+ "<sequence>"
+ "<uuid value=\"0x1002\"/>" // public browse class
+ "</sequence>"
+ "</attribute>"
+ "<attribute id=\"0x0006\">" // Language Base Attribute ID List
+ "<sequence>"
+ "<uint16 value=\"0x656e\"/>" // code_ISO639
+ "<uint16 value=\"0x006a\"/>" // encoding 0x6a
+ "<uint16 value=\"0x0100\"/>" // base_offset ie. points to below =>
+ "</sequence>"
+ "</attribute>"
+ "<attribute id=\"0x0009\">" // Bluetooth Profile Descriptor List
+ "<sequence>"
+ "<sequence>"
+ "<uuid value=\"0x1101\"/>" // 'serial port' UUID as above
+ "<uint16 value=\"0x0100\"/>"// version number 1.0 ?
+ "</sequence>"
+ "</sequence>"
+ "</attribute>"
+ // Attribute identifiers are pointed to by the Language Base Attribute ID List
+ // id+0 = ServiceName, id+1 = ServiceDescription, id+2=ProviderName
+ "<attribute id=\"0x0100\">"
+ "<text value=\"LibreOffice Impress Remote Control\"/>"
+ "</attribute>"
+ "<attribute id=\"0x0102\">"
+ "<text value=\"The Document Foundation\"/>"
+ "</attribute>"
+ "</record>"
+ ;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/BufferedStreamSocket.cxx b/sd/source/ui/remotecontrol/BufferedStreamSocket.cxx
new file mode 100644
index 000000000..64ad5eb8d
--- /dev/null
+++ b/sd/source/ui/remotecontrol/BufferedStreamSocket.cxx
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "BufferedStreamSocket.hxx"
+
+#include <osl/socket.hxx>
+#include <sal/log.hxx>
+#include <algorithm>
+
+#ifdef _WIN32
+ // LO vs WinAPI conflict
+ #undef WB_LEFT
+ #undef WB_RIGHT
+
+ #include <winsock2.h>
+#else
+ #include <sys/socket.h>
+ #include <unistd.h>
+#endif
+using namespace sd;
+using namespace osl;
+
+BufferedStreamSocket::BufferedStreamSocket( const osl::StreamSocket &aSocket ):
+ StreamSocket( aSocket ),
+ aRet( 0 ),
+ aRead( 0 ),
+ mSocket( 0 ),
+ usingCSocket( false )
+{
+}
+
+BufferedStreamSocket::BufferedStreamSocket( int aSocket ):
+ aRet( 0 ),
+ aRead( 0 ),
+ mSocket( aSocket ),
+ usingCSocket( true )
+{
+}
+
+BufferedStreamSocket::~BufferedStreamSocket() {
+ close();
+}
+
+void BufferedStreamSocket::getPeerAddr(osl::SocketAddr& rAddr)
+{
+ assert ( !usingCSocket );
+ StreamSocket::getPeerAddr( rAddr );
+}
+
+sal_Int32 BufferedStreamSocket::write( const void* pBuffer, sal_uInt32 n )
+{
+ if ( !usingCSocket )
+ return StreamSocket::write( pBuffer, n );
+ else
+ return ::send(
+ mSocket,
+#if defined(_WIN32)
+ static_cast<char const *>(pBuffer),
+#else
+ pBuffer,
+#endif
+ static_cast<size_t>(n), 0 );
+}
+
+void BufferedStreamSocket::close()
+{
+ if( usingCSocket && mSocket != -1 )
+ {
+#ifdef _WIN32
+ ::closesocket( mSocket );
+#else
+ ::close( mSocket );
+#endif
+ mSocket = -1;
+ }
+ else
+ ::osl::StreamSocket::close();
+}
+
+sal_Int32 BufferedStreamSocket::readLine( OString& aLine )
+{
+ while ( true )
+ {
+ // Process buffer first in case data already present.
+ std::vector<char>::iterator aIt;
+ if ( (aIt = find( aBuffer.begin(), aBuffer.end(), '\n' ))
+ != aBuffer.end() )
+ {
+ sal_uInt64 aLocation = aIt - aBuffer.begin();
+
+ aLine = OString( &(*aBuffer.begin()), aLocation );
+
+ aBuffer.erase( aBuffer.begin(), aIt + 1 ); // Also delete the empty line
+ aRead -= (aLocation + 1);
+
+ SAL_INFO( "sdremote.bluetooth", "recv line '" << aLine << "'" );
+
+ return aLine.getLength() + 1;
+ }
+
+ // Then try and receive if nothing present
+ aBuffer.resize( aRead + 100 );
+ if ( !usingCSocket)
+ aRet = StreamSocket::recv( &aBuffer[aRead], 100 );
+ else
+ aRet = ::recv( mSocket, &aBuffer[aRead], 100, 0 );
+
+ SAL_INFO( "sdremote.bluetooth", "recv " << aRet << " aBuffer len " << aBuffer.size() );
+ if ( aRet <= 0 )
+ {
+ return 0;
+ }
+ // Prevent buffer from growing massively large.
+ if ( aRead > MAX_LINE_LENGTH )
+ {
+ aBuffer.clear();
+ return 0;
+ }
+ aRead += aRet;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/BufferedStreamSocket.hxx b/sd/source/ui/remotecontrol/BufferedStreamSocket.hxx
new file mode 100644
index 000000000..6abf7ec1b
--- /dev/null
+++ b/sd/source/ui/remotecontrol/BufferedStreamSocket.hxx
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include "IBluetoothSocket.hxx"
+#include <osl/socket_decl.hxx>
+#include <vector>
+
+#define MAX_LINE_LENGTH 20000
+
+namespace sd
+{
+
+ /**
+ * [A wrapper for an osl StreamSocket to allow reading lines.]
+ *
+ * Currently wraps either an osl StreamSocket or a standard c socket,
+ * allowing reading and writing for our purposes. Should eventually be
+ * returned to being a StreamSocket wrapper if/when Bluetooth is
+ * integrated into osl Sockets.
+ */
+ class BufferedStreamSocket final :
+ public IBluetoothSocket,
+ private ::osl::StreamSocket
+ {
+ public:
+ /**
+ * Create a BufferedStreamSocket on top of an
+ * osl::StreamSocket.
+ */
+ explicit BufferedStreamSocket( const osl::StreamSocket &aSocket );
+ /**
+ * Create a BufferedStreamSocket on top of a POSIX or WinSock socket.
+ */
+ explicit BufferedStreamSocket( int aSocket );
+ BufferedStreamSocket( const BufferedStreamSocket &aSocket );
+
+ ~BufferedStreamSocket();
+
+ /**
+ * Blocks until a line is read.
+ * Returns whatever the last call of recv returned, i.e. 0 or less
+ * if there was a problem in communications.
+ */
+ virtual sal_Int32 readLine( OString& aLine ) override;
+
+ virtual sal_Int32 write( const void* pBuffer, sal_uInt32 n ) override;
+
+ virtual void close() override;
+
+ void getPeerAddr(osl::SocketAddr&);
+ private:
+ sal_Int32 aRet, aRead;
+ std::vector<char> aBuffer;
+ int mSocket;
+ bool usingCSocket;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Communicator.cxx b/sd/source/ui/remotecontrol/Communicator.cxx
new file mode 100644
index 000000000..59509ed3c
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Communicator.cxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#include <algorithm>
+#include <vector>
+
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/presentation/XPresentation2.hpp>
+#include <com/sun/star/presentation/XPresentationSupplier.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/documentinfo.hxx>
+#include <config_version.h>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+
+#include "Communicator.hxx"
+#include "IBluetoothSocket.hxx"
+#include "Listener.hxx"
+#include "Receiver.hxx"
+#include "Transmitter.hxx"
+#include <RemoteServer.hxx>
+
+using namespace sd;
+using namespace com::sun::star;
+using namespace osl;
+
+Communicator::Communicator( std::unique_ptr<IBluetoothSocket> pSocket ):
+ Thread( "CommunicatorThread" ),
+ mpSocket( std::move(pSocket) )
+{
+}
+
+Communicator::~Communicator()
+{
+}
+
+/// Close the underlying socket from another thread to force
+/// an early exit / termination
+void Communicator::forceClose()
+{
+ if( mpSocket )
+ mpSocket->close();
+}
+
+// Run as a thread
+void Communicator::execute()
+{
+ pTransmitter.reset( new Transmitter( mpSocket.get() ) );
+ pTransmitter->create();
+
+ pTransmitter->addMessage( "LO_SERVER_SERVER_PAIRED\n\n",
+ Transmitter::PRIORITY_HIGH );
+
+ pTransmitter->addMessage( "LO_SERVER_INFO\n" LIBO_VERSION_DOTTED "\n\n",
+ Transmitter::PRIORITY_HIGH );
+
+ Receiver aReceiver( pTransmitter.get() );
+ try {
+ uno::Reference< frame::XDesktop2 > xFramesSupplier = frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ uno::Reference< frame::XFrame > xFrame = xFramesSupplier->getActiveFrame();
+
+ uno::Reference<presentation::XPresentationSupplier> xPS;
+ if( xFrame.is() )
+ xPS.set( xFrame->getController()->getModel(), uno::UNO_QUERY );
+ uno::Reference<presentation::XPresentation2> xPresentation;
+ if( xPS.is() )
+ xPresentation.set( xPS->getPresentation(), uno::UNO_QUERY );
+ if ( xPresentation.is() && xPresentation->isRunning() )
+ {
+ presentationStarted( xPresentation->getController() );
+ OString aBuffer =
+ "slideshow_info\n" +
+ OUStringToOString( ::comphelper::DocumentInfo::getDocumentTitle( xFrame->getController()->getModel() ), RTL_TEXTENCODING_UTF8 ) +
+ "\n\n";
+
+ pTransmitter->addMessage( aBuffer.getStr(), Transmitter::PRIORITY_LOW );
+ }
+ else
+ {
+ pTransmitter->addMessage( "slideshow_finished\n\n",
+ Transmitter::PRIORITY_HIGH );
+ }
+ }
+ catch (uno::RuntimeException &)
+ {
+ }
+
+ sal_uInt64 aRet;
+ std::vector<OString> aCommand;
+ while ( true )
+ {
+ OString aLine;
+ aRet = mpSocket->readLine( aLine );
+ if ( aRet == 0 )
+ {
+ break; // I.e. transmission finished.
+ }
+ if ( aLine.getLength() )
+ {
+ aCommand.push_back( aLine );
+ }
+ else
+ {
+ aReceiver.pushCommand( aCommand );
+ aCommand.clear();
+ }
+ }
+
+ SAL_INFO ("sdremote", "Exiting transmission loop");
+
+ disposeListener();
+
+ pTransmitter->notifyFinished();
+ pTransmitter->join();
+ pTransmitter = nullptr;
+
+ mpSocket->close();
+ mpSocket.reset();
+
+ RemoteServer::removeCommunicator( this );
+}
+
+void Communicator::informListenerDestroyed()
+{
+ if ( pTransmitter )
+ pTransmitter->addMessage( "slideshow_finished\n\n",
+ Transmitter::PRIORITY_HIGH );
+}
+
+void Communicator::presentationStarted( const css::uno::Reference<
+ css::presentation::XSlideShowController > &rController )
+{
+ if ( pTransmitter )
+ {
+ mListener.set( new Listener( this, pTransmitter.get() ) );
+ mListener->init( rController );
+ }
+}
+
+void Communicator::disposeListener()
+{
+ if ( mListener.is() )
+ {
+ mListener->dispose();
+ mListener = nullptr;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Communicator.hxx b/sd/source/ui/remotecontrol/Communicator.hxx
new file mode 100644
index 000000000..f8f23c58c
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Communicator.hxx
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <memory>
+
+#include <rtl/ref.hxx>
+#include <salhelper/thread.hxx>
+
+namespace com::sun::star::uno { template <typename > class Reference; }
+namespace com::sun::star::presentation { class XSlideShowController; }
+namespace sd { struct IBluetoothSocket; }
+
+namespace sd
+{
+
+ class Transmitter;
+ class Listener;
+
+ /** Class used for communication with one single client, dealing with all
+ * tasks specific to this client.
+ *
+ * Needs to be created, then started using launch(), disposes itself.
+ */
+ class Communicator : public salhelper::Thread
+ {
+ public:
+ explicit Communicator( std::unique_ptr<IBluetoothSocket> pSocket );
+ virtual ~Communicator() override;
+
+ void presentationStarted( const css::uno::Reference<
+ css::presentation::XSlideShowController > &rController );
+ void informListenerDestroyed();
+ void disposeListener();
+ void forceClose();
+
+ private:
+ void execute() override;
+ std::unique_ptr<IBluetoothSocket> mpSocket;
+
+ std::unique_ptr<Transmitter> pTransmitter;
+ rtl::Reference<Listener> mListener;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/DiscoveryService.cxx b/sd/source/ui/remotecontrol/DiscoveryService.cxx
new file mode 100644
index 000000000..bdd0b51c8
--- /dev/null
+++ b/sd/source/ui/remotecontrol/DiscoveryService.cxx
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <iostream>
+
+#include <osl/socket.hxx>
+#include <config_features.h>
+#include <sal/log.hxx>
+
+#include "DiscoveryService.hxx"
+#include "ZeroconfService.hxx"
+
+#ifdef _WIN32
+ // LO vs WinAPI conflict
+ #undef WB_LEFT
+ #undef WB_RIGHT
+
+ #include <winsock2.h>
+ #include <ws2tcpip.h>
+
+ #include "WINNetworkService.hxx"
+ typedef int socklen_t;
+#else
+ #include <unistd.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+#endif
+
+#ifdef MACOSX
+ #include <osl/conditn.hxx>
+ #include <premac.h>
+ #import <CoreFoundation/CoreFoundation.h>
+ #include <postmac.h>
+ #import "OSXNetworkService.hxx"
+#endif
+
+#if HAVE_FEATURE_AVAHI
+ #include "AvahiNetworkService.hxx"
+#endif
+
+using namespace osl;
+using namespace sd;
+
+DiscoveryService::DiscoveryService()
+ : mSocket(-1)
+ , zService(nullptr)
+{
+}
+
+DiscoveryService::~DiscoveryService()
+{
+ if (mSocket != -1)
+ {
+#ifdef _WIN32
+ closesocket( mSocket );
+#else
+ close( mSocket );
+#endif
+ }
+
+ if (zService)
+ zService->clear();
+}
+
+void DiscoveryService::setupSockets()
+{
+
+#ifdef MACOSX
+ // Bonjour for OSX
+ zService = new OSXNetworkService();
+ zService->setup();
+#endif
+
+#if HAVE_FEATURE_AVAHI
+ // Avahi for Linux
+ char hostname[1024];
+ hostname[1023] = '\0';
+ gethostname(hostname, 1023);
+
+ zService = new AvahiNetworkService(hostname);
+ zService->setup();
+#endif
+
+#ifdef _WIN32
+ zService = new WINNetworkService();
+ zService->setup();
+#endif
+
+ // Old implementation for backward compatibility matter
+ mSocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
+ if (mSocket == -1)
+ {
+ SAL_WARN("sd", "DiscoveryService: socket failed: " << errno);
+ return; // would be better to throw, but unsure if caller handles that
+ }
+
+ sockaddr_in aAddr = {};
+ aAddr.sin_family = AF_INET;
+ aAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ aAddr.sin_port = htons( PORT_DISCOVERY );
+
+ int rc = bind( mSocket, reinterpret_cast<sockaddr*>(&aAddr), sizeof(sockaddr_in) );
+
+ if (rc)
+ {
+ SAL_WARN("sd", "DiscoveryService: bind failed: " << errno);
+ return; // would be better to throw, but unsure if caller handles that
+ }
+
+ struct ip_mreq multicastRequest;
+
+ multicastRequest.imr_multiaddr.s_addr = htonl((239U << 24) | 1U); // 239.0.0.1
+ multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY);
+
+ rc = setsockopt( mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ #ifdef _WIN32
+ reinterpret_cast<const char*>(&multicastRequest),
+ #else
+ &multicastRequest,
+ #endif
+ sizeof(multicastRequest));
+
+ if (rc)
+ {
+ SAL_WARN("sd", "DiscoveryService: setsockopt failed: " << errno);
+ return; // would be better to throw, but unsure if caller handles that
+ }
+}
+
+void SAL_CALL DiscoveryService::run()
+{
+ osl::Thread::setName("DiscoveryService");
+
+ setupSockets();
+
+ // Kept for backward compatibility
+ while ( true )
+ {
+ char aBuffer[BUFFER_SIZE] = {};
+ sockaddr_in aAddr;
+ socklen_t aLen = sizeof( aAddr );
+ if(recvfrom( mSocket, aBuffer, BUFFER_SIZE, 0, reinterpret_cast<sockaddr*>(&aAddr), &aLen ) > 0)
+ {
+ OString aString( aBuffer, strlen( "LOREMOTE_SEARCH" ) );
+ if ( aString == "LOREMOTE_SEARCH" )
+ {
+ OString aStringBuffer = "LOREMOTE_ADVERTISE\n" +
+ OUStringToOString(osl::SocketAddr::getLocalHostname(), RTL_TEXTENCODING_UTF8 ) +
+ "\n\n";
+ if ( sendto( mSocket, aStringBuffer.getStr(),
+ aStringBuffer.getLength(), 0, reinterpret_cast<sockaddr*>(&aAddr),
+ sizeof(aAddr) ) <= 0 )
+ {
+ // Write error or closed socket -- we are done.
+ return;
+ }
+ }
+ }
+ else
+ {
+ // Read error or closed socket -- we are done.
+ return;
+ }
+ }
+}
+
+DiscoveryService *sd::DiscoveryService::spService = nullptr;
+
+void DiscoveryService::setup()
+{
+ if (spService)
+ return;
+
+ spService = new DiscoveryService();
+ spService->create();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/DiscoveryService.hxx b/sd/source/ui/remotecontrol/DiscoveryService.hxx
new file mode 100644
index 000000000..4b235fe89
--- /dev/null
+++ b/sd/source/ui/remotecontrol/DiscoveryService.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <osl/thread.hxx>
+
+namespace sd { class ZeroconfService; }
+
+namespace sd
+{
+ class DiscoveryService : public osl::Thread
+ {
+ public:
+ static void setup();
+
+ private:
+ DiscoveryService();
+ virtual ~DiscoveryService() override;
+
+ /**
+ * Networking related setup -- must be run within our own thread
+ * to prevent the application blocking (fdo#75328).
+ */
+ void setupSockets();
+
+ static DiscoveryService *spService;
+ virtual void SAL_CALL run() override;
+ int mSocket;
+
+ ZeroconfService * zService;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/DiscoveryService.mm b/sd/source/ui/remotecontrol/DiscoveryService.mm
new file mode 100644
index 000000000..3cad7cdfb
--- /dev/null
+++ b/sd/source/ui/remotecontrol/DiscoveryService.mm
@@ -0,0 +1 @@
+#include "DiscoveryService.cxx" \ No newline at end of file
diff --git a/sd/source/ui/remotecontrol/IBluetoothSocket.hxx b/sd/source/ui/remotecontrol/IBluetoothSocket.hxx
new file mode 100644
index 000000000..4b75a1e82
--- /dev/null
+++ b/sd/source/ui/remotecontrol/IBluetoothSocket.hxx
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <rtl/string.hxx>
+
+namespace sd
+{
+ /** Interface for bluetooth data io
+ */
+ struct IBluetoothSocket
+ {
+ IBluetoothSocket() = default;
+ virtual ~IBluetoothSocket() {}
+ IBluetoothSocket(const IBluetoothSocket&) = delete;
+ IBluetoothSocket& operator=(const IBluetoothSocket&) = delete;
+
+ /** Blocks until a line is read.
+
+ @return whatever the last call of recv returned, i.e. 0 or less
+ if there was a problem in communications.
+ */
+ virtual sal_Int32 readLine(OString& aLine) = 0;
+
+ /** Write a number of bytes
+
+ @return number of bytes actually written
+ */
+ virtual sal_Int32 write( const void* pBuffer, sal_uInt32 n ) = 0;
+
+ virtual void close() {};
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/ImagePreparer.cxx b/sd/source/ui/remotecontrol/ImagePreparer.cxx
new file mode 100644
index 000000000..ba8d2c1f3
--- /dev/null
+++ b/sd/source/ui/remotecontrol/ImagePreparer.cxx
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "ImagePreparer.hxx"
+#include "Transmitter.hxx"
+
+#include <comphelper/base64.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <osl/file.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/drawing/GraphicExportFilter.hpp>
+#include <com/sun/star/lang/XServiceName.hpp>
+#include <com/sun/star/presentation/XSlideShowController.hpp>
+#include <com/sun/star/presentation/XPresentationPage.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+
+using namespace ::sd;
+using namespace ::osl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+ImagePreparer::ImagePreparer(
+ const uno::Reference<presentation::XSlideShowController>& rxController,
+ Transmitter *aTransmitter )
+ : Timer("sd ImagePreparer"),
+ xController( rxController ),
+ pTransmitter( aTransmitter )
+{
+ SAL_INFO( "sdremote", "ImagePreparer - start" );
+ SetTimeout( 50 );
+ mnSendingSlide = 0;
+ Start();
+}
+
+ImagePreparer::~ImagePreparer()
+{
+ SAL_INFO( "sdremote", "ImagePreparer - stop" );
+ Stop();
+}
+
+void ImagePreparer::Invoke()
+{
+ sal_uInt32 aSlides = xController->getSlideCount();
+ SAL_INFO( "sdremote", "ImagePreparer " << xController->isRunning() <<
+ " sending slide " << mnSendingSlide << " of " << aSlides );
+ if ( xController->isRunning() && // not stopped/disposed of.
+ mnSendingSlide < aSlides )
+ {
+ sendPreview( mnSendingSlide );
+ sendNotes( mnSendingSlide );
+ mnSendingSlide++;
+ Start();
+ }
+ else
+ Stop();
+}
+
+void ImagePreparer::sendPreview( sal_uInt32 aSlideNumber )
+{
+ sal_uInt64 aSize;
+ uno::Sequence<sal_Int8> aImageData = preparePreview( aSlideNumber, 320, 240,
+ aSize );
+ if ( !xController->isRunning() )
+ return;
+
+ OUStringBuffer aStrBuffer;
+ ::comphelper::Base64::encode( aStrBuffer, aImageData );
+
+ OString aEncodedShortString = OUStringToOString(
+ aStrBuffer.makeStringAndClear(), RTL_TEXTENCODING_UTF8 );
+
+ // Start the writing
+ OString aBuffer = "slide_preview\n" +
+ OString::number(aSlideNumber) +
+ "\n" + aEncodedShortString + "\n\n";
+ pTransmitter->addMessage( aBuffer,
+ Transmitter::PRIORITY_LOW );
+
+}
+
+uno::Sequence<sal_Int8> ImagePreparer::preparePreview(
+ sal_uInt32 aSlideNumber, sal_uInt32 aWidth, sal_uInt32 aHeight,
+ sal_uInt64 &rSize )
+{
+ OUString aFileURL;
+ FileBase::createTempFile( nullptr, nullptr, &aFileURL );
+
+ uno::Reference< drawing::XGraphicExportFilter > xFilter =
+ drawing::GraphicExportFilter::create( ::comphelper::getProcessComponentContext() );
+
+ if ( !xController->isRunning() )
+ return uno::Sequence<sal_Int8>();
+
+ uno::Reference< lang::XComponent > xSourceDoc(
+ xController->getSlideByIndex( aSlideNumber ),
+ uno::UNO_QUERY_THROW );
+
+ xFilter->setSourceDocument( xSourceDoc );
+
+ uno::Sequence< beans::PropertyValue > aFilterData{
+ comphelper::makePropertyValue("PixelWidth", aWidth),
+ comphelper::makePropertyValue("PixelHeight", aHeight),
+ comphelper::makePropertyValue("ColorMode", sal_Int32(0)) // 0: Color, 1: B&W
+ };
+
+ uno::Sequence< beans::PropertyValue > aProps{
+ comphelper::makePropertyValue("MediaType", OUString( "image/png" )),
+ comphelper::makePropertyValue("URL", aFileURL),
+ comphelper::makePropertyValue("FilterData", aFilterData)
+ };
+
+ xFilter->filter( aProps );
+
+ File aFile(aFileURL);
+ if (aFile.open(0) != osl::File::E_None)
+ return uno::Sequence<sal_Int8>();
+
+ sal_uInt64 aRead;
+ rSize = 0;
+ aFile.getSize( rSize );
+ uno::Sequence<sal_Int8> aContents( rSize );
+
+ aFile.read( aContents.getArray(), rSize, aRead );
+ if (aRead != rSize)
+ aContents.realloc(aRead);
+
+ aFile.close();
+ File::remove( aFileURL );
+ return aContents;
+
+}
+
+void ImagePreparer::sendNotes( sal_uInt32 aSlideNumber )
+{
+
+ OString aNotes = prepareNotes( aSlideNumber );
+
+ if ( aNotes.isEmpty() )
+ return;
+
+ if ( !xController->isRunning() )
+ return;
+
+ // Start the writing
+ OString aBuffer =
+ "slide_notes\n" +
+ OString::number( static_cast<sal_Int32>(aSlideNumber) ) +
+ "\n"
+ "<html><body>" +
+ aNotes +
+ "</body></html>"
+ "\n\n";
+ pTransmitter->addMessage( aBuffer,
+ Transmitter::PRIORITY_LOW );
+}
+
+// Code copied from sdremote/source/presenter/PresenterNotesView.cxx
+OString ImagePreparer::prepareNotes( sal_uInt32 aSlideNumber )
+{
+ OUStringBuffer aRet;
+
+ if ( !xController->isRunning() )
+ return "";
+
+ uno::Reference<css::drawing::XDrawPage> aNotesPage;
+ uno::Reference< drawing::XDrawPage > xSourceDoc(
+ xController->getSlideByIndex( aSlideNumber ),
+ uno::UNO_SET_THROW );
+ uno::Reference<presentation::XPresentationPage> xPresentationPage(
+ xSourceDoc, UNO_QUERY);
+ if (xPresentationPage.is())
+ aNotesPage = xPresentationPage->getNotesPage();
+ else
+ return "";
+
+ static constexpr OUStringLiteral sNotesShapeName (
+ u"com.sun.star.presentation.NotesShape" );
+ static constexpr OUStringLiteral sTextShapeName (
+ u"com.sun.star.drawing.TextShape" );
+
+ if (aNotesPage.is())
+ {
+
+ // Iterate over all shapes and find the one that holds the text.
+ sal_Int32 nCount (aNotesPage->getCount());
+ for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex)
+ {
+
+ uno::Reference<lang::XServiceName> xServiceName (
+ aNotesPage->getByIndex(nIndex), UNO_QUERY);
+ if (xServiceName.is()
+ && xServiceName->getServiceName() == sNotesShapeName)
+ {
+ uno::Reference<text::XTextRange> xText (xServiceName, UNO_QUERY);
+ if (xText.is())
+ {
+ aRet.append(xText->getString());
+ aRet.append("<br/>");
+ }
+ }
+ else
+ {
+ uno::Reference<drawing::XShapeDescriptor> xShapeDescriptor (
+ aNotesPage->getByIndex(nIndex), UNO_QUERY);
+ if (xShapeDescriptor.is())
+ {
+ OUString sType (xShapeDescriptor->getShapeType());
+ if (sType == sNotesShapeName || sType == sTextShapeName)
+ {
+ uno::Reference<text::XTextRange> xText (
+ aNotesPage->getByIndex(nIndex), UNO_QUERY);
+ if (xText.is())
+ {
+ aRet.append(xText->getString());
+ aRet.append("<br/>");
+ }
+ }
+ }
+ }
+ }
+ }
+ // Replace all newlines with <br\> tags
+ for ( sal_Int32 i = 0; i < aRet.getLength(); i++ )
+ {
+ if ( aRet[i] == '\n' )
+ {
+ aRet[i]= '<';
+ aRet.insert( i+1, "br/>" );
+ }
+ }
+ return OUStringToOString(
+ aRet.makeStringAndClear(), RTL_TEXTENCODING_UTF8 );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/ImagePreparer.hxx b/sd/source/ui/remotecontrol/ImagePreparer.hxx
new file mode 100644
index 000000000..146eba073
--- /dev/null
+++ b/sd/source/ui/remotecontrol/ImagePreparer.hxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <vcl/timer.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+
+namespace com::sun::star::presentation { class XSlideShowController; }
+namespace sd { class Transmitter; }
+
+namespace sd
+{
+
+class ImagePreparer : private Timer
+{
+ sal_uInt32 mnSendingSlide;
+public:
+ ImagePreparer( const
+ css::uno::Reference<css::presentation::XSlideShowController>&
+ rxController, sd::Transmitter *aTransmitter );
+ virtual ~ImagePreparer() override;
+
+private:
+ css::uno::Reference<css::presentation::XSlideShowController> xController;
+ Transmitter *pTransmitter;
+
+ virtual void Invoke() override;
+
+ void sendPreview( sal_uInt32 aSlideNumber );
+ css::uno::Sequence<sal_Int8> preparePreview( sal_uInt32 aSlideNumber,
+ sal_uInt32 aWidth, sal_uInt32 aHeight, sal_uInt64 &rSize );
+
+ void sendNotes( sal_uInt32 aSlideNumber );
+ OString prepareNotes( sal_uInt32 aSlideNumber );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Listener.cxx b/sd/source/ui/remotecontrol/Listener.cxx
new file mode 100644
index 000000000..3753ed9b5
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Listener.cxx
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+
+#include "Communicator.hxx"
+#include "Listener.hxx"
+#include "ImagePreparer.hxx"
+#include "Transmitter.hxx"
+
+#include <com/sun/star/presentation/XSlideShowController.hpp>
+
+using namespace sd;
+using namespace ::com::sun::star::presentation;
+
+Listener::Listener( const ::rtl::Reference<Communicator>& rCommunicator,
+ sd::Transmitter *aTransmitter ):
+ mCommunicator( rCommunicator ),
+ pTransmitter( nullptr )
+{
+ pTransmitter = aTransmitter;
+}
+
+Listener::~Listener()
+{
+}
+
+void Listener::init( const css::uno::Reference< css::presentation::XSlideShowController >& aController)
+{
+ if ( aController.is() )
+ {
+ mController.set( aController );
+ aController->addSlideShowListener( this );
+
+ sal_Int32 aSlides = aController->getSlideCount();
+ sal_Int32 aCurrentSlide = aController->getCurrentSlideIndex();
+ OString aBuffer = "slideshow_started\n" +
+ OString::number( aSlides ) + "\n" +
+ OString::number( aCurrentSlide ) + "\n\n";
+
+ pTransmitter->addMessage( aBuffer,
+ Transmitter::PRIORITY_HIGH );
+
+ {
+ SolarMutexGuard aGuard;
+ /* ImagePreparer* pPreparer = */ new ImagePreparer( aController, pTransmitter );
+ }
+ }
+ else
+ {
+ SAL_INFO( "sdremote", "Listener::init but no controller - so no preview push queued" );
+ }
+}
+
+//----- XAnimationListener ----------------------------------------------------
+
+void SAL_CALL Listener::beginEvent(const css::uno::Reference<
+ css::animations::XAnimationNode >& )
+{}
+
+void SAL_CALL Listener::endEvent( const css::uno::Reference<
+ css::animations::XAnimationNode >& )
+{}
+
+void SAL_CALL Listener::repeat( const css::uno::Reference<
+ css::animations::XAnimationNode >&, ::sal_Int32 )
+{}
+
+//----- XSlideShowListener ----------------------------------------------------
+
+void SAL_CALL Listener::paused()
+{
+}
+
+void SAL_CALL Listener::resumed()
+{
+}
+
+void SAL_CALL Listener::slideEnded (sal_Bool)
+{
+}
+
+void SAL_CALL Listener::hyperLinkClicked (const OUString &)
+{
+}
+
+void SAL_CALL Listener::slideTransitionStarted()
+{
+ sal_Int32 aSlide = mController->getCurrentSlideIndex();
+
+ OString aBuilder = "slide_updated\n" +
+ OString::number( aSlide ) +
+ "\n\n";
+
+ if ( pTransmitter )
+ {
+ pTransmitter->addMessage( aBuilder,
+ Transmitter::PRIORITY_HIGH );
+ }
+}
+
+void SAL_CALL Listener::slideTransitionEnded()
+{
+}
+
+void SAL_CALL Listener::slideAnimationsEnded()
+{
+}
+
+void Listener::disposing(std::unique_lock<std::mutex>&)
+{
+ pTransmitter = nullptr;
+ if ( mController.is() )
+ {
+ mController->removeSlideShowListener( this );
+ mController = nullptr;
+ }
+ mCommunicator->informListenerDestroyed();
+}
+
+void SAL_CALL Listener::disposing (
+ const css::lang::EventObject&)
+{
+ dispose();
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Listener.hxx b/sd/source/ui/remotecontrol/Listener.hxx
new file mode 100644
index 000000000..58d7483f6
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Listener.hxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <sal/config.h>
+#include <com/sun/star/presentation/XSlideShowListener.hpp>
+
+#include <rtl/ref.hxx>
+#include <comphelper/compbase.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+
+namespace com::sun::star::presentation { class XSlideShowController; }
+namespace sd { class Communicator; }
+namespace sd { class Transmitter; }
+
+namespace sd {
+/**
+ * Slide show listener. This class can also be used for anything else that is
+ * specific to the lifetime of one slideshow while a client is connected.
+ */
+class Listener
+ : public comphelper::WeakComponentImplHelper< css::presentation::XSlideShowListener >
+{
+public:
+ Listener( const ::rtl::Reference<Communicator>& rServer, sd::Transmitter *aTransmitter );
+ virtual ~Listener() override;
+ void init( const css::uno::Reference< css::presentation::XSlideShowController >& aController );
+
+ // XAnimationListener
+ virtual void SAL_CALL beginEvent(const css::uno::Reference<
+ css::animations::XAnimationNode >& rNode ) override;
+ virtual void SAL_CALL endEvent( const css::uno::Reference<
+ css::animations::XAnimationNode >& rNode ) override;
+ virtual void SAL_CALL repeat( const css::uno::Reference<
+ css::animations::XAnimationNode >& rNode, ::sal_Int32 Repeat ) override;
+
+ // XSlideShowListener
+ virtual void SAL_CALL paused( ) override;
+ virtual void SAL_CALL resumed( ) override;
+ virtual void SAL_CALL slideTransitionStarted( ) override;
+ virtual void SAL_CALL slideTransitionEnded( ) override;
+ virtual void SAL_CALL slideAnimationsEnded( ) override;
+ virtual void SAL_CALL slideEnded(sal_Bool bReverse) override;
+ virtual void SAL_CALL hyperLinkClicked( const OUString& hyperLink ) override;
+
+ // XEventListener
+ virtual void disposing(std::unique_lock<std::mutex>&) override;
+ virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override;
+
+private:
+ rtl::Reference<Communicator> mCommunicator;
+ sd::Transmitter *pTransmitter;
+ css::uno::Reference< css::presentation::XSlideShowController > mController;
+};
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/OSXBluetooth.h b/sd/source/ui/remotecontrol/OSXBluetooth.h
new file mode 100644
index 000000000..64f095f6c
--- /dev/null
+++ b/sd/source/ui/remotecontrol/OSXBluetooth.h
@@ -0,0 +1,30 @@
+/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
+
+#include "IBluetoothSocket.hxx"
+#include "Communicator.hxx"
+#include "OSXBluetoothWrapper.hxx"
+
+@interface ChannelDelegate : NSObject<IOBluetoothRFCOMMChannelDelegate>
+{
+ sd::Communicator* pCommunicator;
+ sd::OSXBluetoothWrapper* pSocket;
+}
+
+- (id) initWithCommunicatorAndSocket:(sd::Communicator*)communicator socket:(sd::OSXBluetoothWrapper*)socket;
+- (void) rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel data:(void *)dataPointer length:(size_t)dataLength;
+- (void) rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel;
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/OSXBluetooth.mm b/sd/source/ui/remotecontrol/OSXBluetooth.mm
new file mode 100644
index 000000000..8b705c50b
--- /dev/null
+++ b/sd/source/ui/remotecontrol/OSXBluetooth.mm
@@ -0,0 +1,53 @@
+/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#include <osl/conditn.hxx>
+#include <sal/log.hxx>
+
+#include <premac.h>
+#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h>
+#include <postmac.h>
+
+#include "OSXBluetooth.h"
+
+@implementation ChannelDelegate
+
+- (id) initWithCommunicatorAndSocket:(sd::Communicator*)communicator socket:(sd::OSXBluetoothWrapper*)socket
+{
+ pCommunicator = communicator;
+ pSocket = socket;
+ return self;
+}
+
+- (void) rfcommChannelData:(IOBluetoothRFCOMMChannel*)rfcommChannel data:(void *)dataPointer length:(size_t)dataLength
+{
+ (void) rfcommChannel;
+
+ if ( pSocket )
+ {
+ pSocket->appendData(dataPointer, dataLength);
+ }
+}
+
+- (void) rfcommChannelClosed:(IOBluetoothRFCOMMChannel*)rfcommChannel
+{
+ (void) rfcommChannel;
+
+ SAL_INFO( "sdremote.bluetooth", "ChannelDelegate::rfcommChannelClosed()");
+
+ if ( pSocket )
+ {
+ pSocket->channelClosed();
+ }
+ pCommunicator = nullptr;
+ pSocket = nullptr;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/OSXBluetoothWrapper.hxx b/sd/source/ui/remotecontrol/OSXBluetoothWrapper.hxx
new file mode 100644
index 000000000..26e1349f0
--- /dev/null
+++ b/sd/source/ui/remotecontrol/OSXBluetoothWrapper.hxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <rtl/string.hxx>
+#include <osl/conditn.hxx>
+#include <osl/mutex.hxx>
+#include <vector>
+
+#include "IBluetoothSocket.hxx"
+
+namespace sd
+{
+ class OSXBluetoothWrapper : public IBluetoothSocket
+ {
+ IOBluetoothRFCOMMChannel* mpChannel;
+ int mnMTU;
+ osl::Condition mHaveBytes;
+ osl::Mutex mMutex;
+ std::vector<char> mBuffer;
+
+ public:
+ OSXBluetoothWrapper( IOBluetoothRFCOMMChannel* channel );
+ virtual sal_Int32 readLine( OString& aLine ) override;
+ virtual sal_Int32 write( const void* pBuffer, sal_uInt32 len ) override;
+ void appendData(void* pBuffer, size_t len );
+ void channelClosed();
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/OSXNetworkService.h b/sd/source/ui/remotecontrol/OSXNetworkService.h
new file mode 100644
index 000000000..7298d901b
--- /dev/null
+++ b/sd/source/ui/remotecontrol/OSXNetworkService.h
@@ -0,0 +1,30 @@
+/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <premac.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <Foundation/NSNetServices.h>
+#import <Foundation/NSRunLoop.h>
+#include <postmac.h>
+
+@interface OSXBonjourService : NSObject <NSNetServiceDelegate>
+{
+ NSNetService* netService;
+}
+
+- (void)publishImpressRemoteServiceOnLocalNetworkWithName:(NSString*)sName;
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/OSXNetworkService.hxx b/sd/source/ui/remotecontrol/OSXNetworkService.hxx
new file mode 100644
index 000000000..78ab13eff
--- /dev/null
+++ b/sd/source/ui/remotecontrol/OSXNetworkService.hxx
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <algorithm>
+#include <vector>
+#include <iostream>
+
+#include <osl/conditn.hxx>
+#include <premac.h>
+#import <CoreFoundation/CoreFoundation.h>
+#include <postmac.h>
+#import "OSXNetworkService.h"
+
+#include "ZeroconfService.hxx"
+
+namespace sd {
+ class OSXNetworkService : public ZeroconfService
+ {
+ private:
+ OSXBonjourService *osxservice;
+ public:
+ OSXNetworkService(const std::string& aname = "", unsigned int aport = 1599)
+ : ZeroconfService(aname, aport){}
+
+ void clear() override {
+ [osxservice dealloc];
+ }
+ void setup() override {
+ osxservice = [[OSXBonjourService alloc] init];
+ [osxservice publishImpressRemoteServiceOnLocalNetworkWithName: @""];
+ };
+ };
+}
diff --git a/sd/source/ui/remotecontrol/OSXNetworkService.mm b/sd/source/ui/remotecontrol/OSXNetworkService.mm
new file mode 100644
index 000000000..51cbd8c99
--- /dev/null
+++ b/sd/source/ui/remotecontrol/OSXNetworkService.mm
@@ -0,0 +1,43 @@
+/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#include <osl/conditn.hxx>
+
+#include <premac.h>
+ #import <CoreFoundation/CoreFoundation.h>
+ #import "OSXNetworkService.h"
+#include <postmac.h>
+
+@implementation OSXBonjourService
+
+- (void) publishImpressRemoteServiceOnLocalNetworkWithName:(NSString *)sName
+{
+ netService = [[NSNetService alloc] initWithDomain:@"local" type:@"_impressremote._tcp" name:sName port:1599];
+
+ if (netService != nil)
+ {
+ [netService setDelegate:self];
+ [netService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
+ [netService publish];
+ }
+}
+
+-(void)netService:(NSNetService *)aNetService
+ didNotPublish:(NSDictionary *)dict {
+ NSLog(@"Service %p did not publish: %@", aNetService, dict);
+}
+
+- (void)dealloc {
+ [netService stop];
+ [netService release];
+ [super dealloc];
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Receiver.cxx b/sd/source/ui/remotecontrol/Receiver.cxx
new file mode 100644
index 000000000..dd92e8e99
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Receiver.cxx
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#include "Receiver.hxx"
+#include <com/sun/star/presentation/XSlideShowController.hpp>
+#include <com/sun/star/presentation/XPresentationSupplier.hpp>
+#include <com/sun/star/presentation/XPresentation2.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <sal/log.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <vcl/svapp.hxx>
+#include <tools/diagnose_ex.h>
+
+using namespace sd;
+using namespace ::osl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::presentation;
+using namespace ::com::sun::star::beans;
+
+Receiver::Receiver( Transmitter *aTransmitter ) : Timer("sd Receiver")
+{
+ pTransmitter = aTransmitter;
+ SetTimeout( 0 );
+}
+
+Receiver::~Receiver()
+{
+}
+
+// Bounce the commands to the main thread to avoid threading woes
+void Receiver::pushCommand( const std::vector<OString> &rCommand )
+{
+ SolarMutexGuard aGuard;
+ maExecQueue.push_back( rCommand );
+ Start();
+}
+
+void Receiver::Invoke()
+{
+ if( !maExecQueue.empty() )
+ {
+ std::vector< OString > aCommands( maExecQueue.front() );
+ maExecQueue.pop_front();
+ if( !aCommands.empty() )
+ executeCommand( aCommands );
+ Start();
+ }
+ else
+ Stop();
+}
+
+void Receiver::executeCommand( const std::vector<OString> &aCommand )
+{
+ uno::Reference<presentation::XSlideShowController> xSlideShowController;
+ uno::Reference<presentation::XPresentation2> xPresentation;
+ uno::Reference<presentation::XSlideShow> xSlideShow;
+ try {
+ uno::Reference< frame::XDesktop2 > xFramesSupplier = frame::Desktop::create( ::comphelper::getProcessComponentContext() );
+ uno::Reference< frame::XFrame > xFrame ( xFramesSupplier->getActiveFrame(), uno::UNO_SET_THROW );
+ uno::Reference<presentation::XPresentationSupplier> xPS ( xFrame->getController()->getModel(), uno::UNO_QUERY_THROW);
+ xPresentation.set( xPS->getPresentation(), uno::UNO_QUERY_THROW);
+ // Throws an exception if no slideshow running
+ xSlideShowController.set( xPresentation->getController(), uno::UNO_SET_THROW );
+ xSlideShow.set( xSlideShowController->getSlideShow(), uno::UNO_SET_THROW );
+ }
+ catch (uno::RuntimeException &)
+ {
+ }
+
+ if ( aCommand[0] == "transition_next" )
+ {
+ if ( xSlideShowController.is() )
+ xSlideShowController->gotoNextEffect();
+ }
+ else if ( aCommand[0] == "transition_previous" )
+ {
+ if ( xSlideShowController.is() )
+ xSlideShowController->gotoPreviousEffect();
+ }
+ else if ( aCommand[0] == "goto_slide" )
+ {
+ // FIXME: if 0 returned, then not a valid number
+ sal_Int32 aSlide = aCommand[1].toInt32();
+ if ( xSlideShowController.is() &&
+ xSlideShowController->getCurrentSlideIndex() != aSlide )
+ {
+ xSlideShowController->gotoSlideIndex( aSlide );
+ }
+ }
+ else if ( aCommand[0] == "presentation_start" )
+ {
+ if ( xPresentation.is() )
+ xPresentation->start();
+ }
+ else if ( aCommand[0] == "presentation_stop" )
+ {
+ if ( xPresentation.is() )
+ xPresentation->end();
+ }
+ else if ( aCommand[0] == "presentation_blank_screen" )
+ {
+ if ( aCommand.size() > 1 )
+ {
+// aColour = FIXME: get the colour in some format from this string
+// Determine the formatting first.
+ }
+ if ( xSlideShowController.is() )
+ {
+ xSlideShowController->blankScreen( 0 ); // Default is black
+ }
+ }
+ else if (aCommand[0] == "pointer_started" )
+ {
+ // std::cerr << "pointer_started" << std::endl;
+ float x = aCommand[1].toFloat();
+ float y = aCommand[2].toFloat();
+ SolarMutexGuard aSolarGuard;
+
+ const css::geometry::RealPoint2D pos(x,y);
+ // std::cerr << "Pointer at ("<<pos.X<<","<<pos.Y<<")" << std::endl;
+
+ if (xSlideShow.is())
+ {
+ try
+ {
+ // std::cerr << "pointer_coordination in the is" << std::endl;
+ xSlideShow->setProperty(beans::PropertyValue("PointerPosition", -1, Any(pos),
+ beans::PropertyState_DIRECT_VALUE));
+ }
+ catch (Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("sdremote", "sd::SlideShowImpl::setPointerPosition()");
+ }
+
+ try
+ {
+ xSlideShow->setProperty(beans::PropertyValue("PointerVisible", -1, Any(true),
+ beans::PropertyState_DIRECT_VALUE));
+ }
+ catch (Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("sdremote", "sd::SlideShowImpl::setPointerMode()");
+ }
+ }
+
+ SAL_INFO( "sdremote", "Pointer started, we display the pointer on screen" );
+ }
+ else if (aCommand[0] == "pointer_dismissed" )
+ {
+ SolarMutexGuard aSolarGuard;
+ if (xSlideShow.is()) try
+ {
+ xSlideShow->setProperty(
+ beans::PropertyValue( "PointerVisible" ,
+ -1,
+ Any( false ),
+ beans::PropertyState_DIRECT_VALUE ) );
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sdremote", "sd::SlideShowImpl::setPointerMode()" );
+ }
+
+ SAL_INFO( "sdremote", "Pointer dismissed, we hide the pointer on screen" );
+ }
+ else if (aCommand[0] == "pointer_coordination" )
+ {
+ float x = aCommand[1].toFloat();
+ float y = aCommand[2].toFloat();
+
+ SAL_INFO( "sdremote", "Pointer at ("<<x<<","<<y<<")" );
+ const css::geometry::RealPoint2D pos(x,y);
+
+ SolarMutexGuard aSolarGuard;
+ if (xSlideShow.is()) try
+ {
+ xSlideShow->setProperty(
+ beans::PropertyValue( "PointerPosition" ,
+ -1,
+ Any( pos ),
+ beans::PropertyState_DIRECT_VALUE ) );
+ }
+ catch ( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sdremote", "sd::SlideShowImpl::setPointerPosition()" );
+ }
+ }
+ else if ( aCommand[0] == "presentation_resume" )
+ {
+ if ( xSlideShowController.is() )
+ {
+ xSlideShowController->resume();
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Receiver.hxx b/sd/source/ui/remotecontrol/Receiver.hxx
new file mode 100644
index 000000000..d3fadf0da
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Receiver.hxx
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <rtl/string.hxx>
+#include <vcl/timer.hxx>
+
+#include <vector>
+#include <deque>
+
+namespace sd { class Transmitter; }
+
+namespace sd
+{
+
+// Timer is protected by the solar mutex => so are we.
+class Receiver : private Timer
+{
+ std::deque< std::vector< OString > > maExecQueue;
+public:
+ explicit Receiver( Transmitter *aTransmitter );
+ virtual ~Receiver() override;
+ virtual void Invoke() override;
+ void pushCommand( const std::vector<OString> &rCommand );
+ static void executeCommand( const std::vector<OString> &aCommand );
+
+private:
+ Transmitter *pTransmitter;
+};
+
+}
diff --git a/sd/source/ui/remotecontrol/Server.cxx b/sd/source/ui/remotecontrol/Server.cxx
new file mode 100644
index 000000000..53bf0352c
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Server.cxx
@@ -0,0 +1,373 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <algorithm>
+#include <vector>
+
+#include <officecfg/Office/Impress.hxx>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/configuration.hxx>
+#include <comphelper/sequence.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <osl/socket.hxx>
+
+#include <sddll.hxx>
+
+#include "DiscoveryService.hxx"
+#include "Listener.hxx"
+#include <RemoteServer.hxx>
+#include "BluetoothServer.hxx"
+#include "Communicator.hxx"
+#include "BufferedStreamSocket.hxx"
+
+using namespace sd;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::lang;
+using namespace ::osl;
+using namespace ::comphelper;
+
+namespace sd {
+ /**
+ * Used to keep track of clients that have attempted to connect, but haven't
+ * yet been approved.
+ */
+ struct ClientInfoInternal:
+ ClientInfo
+ {
+ BufferedStreamSocket *mpStreamSocket;
+ OUString mPin;
+
+ ClientInfoInternal( const OUString& rName,
+ BufferedStreamSocket *pSocket,
+ const OUString& rPin ):
+ ClientInfo( rName, false ),
+ mpStreamSocket( pSocket ),
+ mPin( rPin ) {}
+ };
+}
+
+RemoteServer::RemoteServer() :
+ Thread( "RemoteServerThread" )
+{
+ SAL_INFO( "sdremote", "Instantiated RemoteServer" );
+}
+
+RemoteServer::~RemoteServer()
+{
+}
+
+void RemoteServer::execute()
+{
+ SAL_INFO( "sdremote", "RemoteServer::execute called" );
+ osl::SocketAddr aAddr( "0.0.0.0", PORT );
+ if ( !mSocket.bind( aAddr ) )
+ {
+ SAL_WARN( "sdremote", "bind failed" << mSocket.getErrorAsString() );
+ spServer = nullptr;
+ return;
+ }
+
+ if ( !mSocket.listen(3) )
+ {
+ SAL_WARN( "sdremote", "listen failed" << mSocket.getErrorAsString() );
+ spServer = nullptr;
+ return;
+ }
+ while ( true )
+ {
+ StreamSocket aSocket;
+ SAL_INFO( "sdremote", "waiting on accept" );
+ if ( mSocket.acceptConnection( aSocket ) == osl_Socket_Error )
+ {
+ SAL_WARN( "sdremote", "accept failed" << mSocket.getErrorAsString() );
+ spServer = nullptr;
+ return; // Closed, or other issue.
+ }
+ BufferedStreamSocket *pSocket = new BufferedStreamSocket( aSocket);
+ handleAcceptedConnection( pSocket );
+ }
+ SAL_INFO( "sdremote", "shutting down RemoteServer" );
+ spServer = nullptr; // Object is destroyed when Thread::execute() ends.
+}
+
+void RemoteServer::handleAcceptedConnection( BufferedStreamSocket *pSocket )
+{
+ OString aLine;
+ if ( ! ( pSocket->readLine( aLine)
+ && aLine == "LO_SERVER_CLIENT_PAIR"
+ && pSocket->readLine( aLine ) ) )
+ {
+ SAL_INFO( "sdremote", "client failed to send LO_SERVER_CLIENT_PAIR, ignoring" );
+ delete pSocket;
+ return;
+ }
+
+ OString aName( aLine );
+
+ if ( ! pSocket->readLine( aLine ) )
+ {
+ delete pSocket;
+ return;
+ }
+ OString aPin( aLine );
+
+ SocketAddr aClientAddr;
+ pSocket->getPeerAddr( aClientAddr );
+
+ do
+ {
+ // Read off any additional non-empty lines
+ // We know that we at least have the empty termination line to read.
+ if ( ! pSocket->readLine( aLine ) ) {
+ delete pSocket;
+ return;
+ }
+ }
+ while ( aLine.getLength() > 0 );
+
+ MutexGuard aGuard( sDataMutex );
+ std::shared_ptr< ClientInfoInternal > pClient =
+ std::make_shared<ClientInfoInternal>(
+ OStringToOUString( aName, RTL_TEXTENCODING_UTF8 ),
+ pSocket, OStringToOUString( aPin, RTL_TEXTENCODING_UTF8 ) );
+ mAvailableClients.push_back( pClient );
+
+ // Check if we already have this server.
+ Reference< XNameAccess > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get();
+ const Sequence< OUString > aNames = xConfig->getElementNames();
+ for ( const auto& rName : aNames )
+ {
+ if ( rName == pClient->mName )
+ {
+ Reference<XNameAccess> xSetItem( xConfig->getByName(rName), UNO_QUERY );
+ Any axPin(xSetItem->getByName("PIN"));
+ OUString sPin;
+ axPin >>= sPin;
+
+ if ( sPin == pClient->mPin ) {
+ SAL_INFO( "sdremote", "client found on validated list -- connecting" );
+ connectClient( pClient, sPin );
+ return;
+ }
+ }
+ }
+
+ // Pin not found so inform the client.
+ SAL_INFO( "sdremote", "client not found on validated list" );
+ pSocket->write( "LO_SERVER_VALIDATING_PIN\n\n",
+ strlen( "LO_SERVER_VALIDATING_PIN\n\n" ) );
+}
+
+RemoteServer *sd::RemoteServer::spServer = nullptr;
+::osl::Mutex sd::RemoteServer::sDataMutex;
+::std::vector<Communicator*> sd::RemoteServer::sCommunicators;
+
+void RemoteServer::setup()
+{
+ if (spServer)
+ return;
+
+ spServer = new RemoteServer();
+ spServer->launch();
+
+#ifdef ENABLE_SDREMOTE_BLUETOOTH
+ sd::BluetoothServer::setup( &sCommunicators );
+#endif
+}
+
+void RemoteServer::presentationStarted( const css::uno::Reference<
+ css::presentation::XSlideShowController > &rController )
+{
+ if ( !spServer )
+ return;
+ MutexGuard aGuard( sDataMutex );
+ for ( const auto& rpCommunicator : sCommunicators )
+ {
+ rpCommunicator->presentationStarted( rController );
+ }
+}
+void RemoteServer::presentationStopped()
+{
+ if ( !spServer )
+ return;
+ MutexGuard aGuard( sDataMutex );
+ for ( const auto& rpCommunicator : sCommunicators )
+ {
+ rpCommunicator->disposeListener();
+ }
+}
+
+void RemoteServer::removeCommunicator( Communicator const * mCommunicator )
+{
+ if ( !spServer )
+ return;
+ MutexGuard aGuard( sDataMutex );
+ auto aIt = std::find(sCommunicators.begin(), sCommunicators.end(), mCommunicator);
+ if (aIt != sCommunicators.end())
+ sCommunicators.erase( aIt );
+}
+
+std::vector< std::shared_ptr< ClientInfo > > RemoteServer::getClients()
+{
+ SAL_INFO( "sdremote", "RemoteServer::getClients() called" );
+ std::vector< std::shared_ptr< ClientInfo > > aClients;
+ if ( spServer )
+ {
+ MutexGuard aGuard( sDataMutex );
+ aClients.assign( spServer->mAvailableClients.begin(),
+ spServer->mAvailableClients.end() );
+ }
+ else
+ {
+ SAL_INFO( "sdremote", "No remote server instance => no remote clients" );
+ }
+ // We also need to provide authorised clients (no matter whether or not
+ // they are actually available), so that they can be de-authorised if
+ // necessary. We specifically want these to be at the end of the list
+ // since the user is more likely to be trying to connect a new remote
+ // than removing an existing remote.
+ // We can also be sure that pre-authorised clients will not be on the
+ // available clients list, as they get automatically connected if seen.
+ // TODO: we should probably add some sort of extra labelling to mark
+ // authorised AND connected client.
+ Reference< XNameAccess > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get();
+ const Sequence< OUString > aNames = xConfig->getElementNames();
+ std::transform(aNames.begin(), aNames.end(), std::back_inserter(aClients),
+ [](const OUString& rName) -> std::shared_ptr<ClientInfo> {
+ return std::make_shared<ClientInfo>(rName, true); });
+
+ return aClients;
+}
+
+bool RemoteServer::connectClient( const std::shared_ptr< ClientInfo >& pClient, std::u16string_view aPin )
+{
+ SAL_INFO( "sdremote", "RemoteServer::connectClient called" );
+ if ( !spServer )
+ return false;
+
+ ClientInfoInternal* apClient = dynamic_cast< ClientInfoInternal* >( pClient.get() );
+ if ( !apClient )
+ // could happen if we try to "connect" an already authorised client
+ {
+ return false;
+ }
+
+ if ( apClient->mPin == aPin )
+ {
+ // Save in settings first
+ std::shared_ptr< ConfigurationChanges > aChanges = ConfigurationChanges::create();
+ Reference< XNameContainer > const xConfig = officecfg::Office::Impress::Misc::AuthorisedRemotes::get( aChanges );
+
+ Reference<XSingleServiceFactory> xChildFactory (
+ xConfig, UNO_QUERY);
+ Reference<XNameReplace> xChild( xChildFactory->createInstance(), UNO_QUERY);
+ Any aValue;
+ if (xChild.is())
+ {
+ // Check whether the client is already saved
+ Sequence< OUString > aNames = xConfig->getElementNames();
+ if (comphelper::findValue(aNames, apClient->mName) != -1)
+ xConfig->replaceByName( apClient->mName, Any( xChild ) );
+ else
+ xConfig->insertByName( apClient->mName, Any( xChild ) );
+ aValue <<= apClient->mPin;
+ xChild->replaceByName("PIN", aValue);
+ aChanges->commit();
+ }
+
+ Communicator* pCommunicator = new Communicator( std::unique_ptr<IBluetoothSocket>(apClient->mpStreamSocket) );
+ MutexGuard aGuard( sDataMutex );
+
+ sCommunicators.push_back( pCommunicator );
+
+ auto aIt = std::find(spServer->mAvailableClients.begin(), spServer->mAvailableClients.end(), pClient);
+ if (aIt != spServer->mAvailableClients.end())
+ spServer->mAvailableClients.erase( aIt );
+ pCommunicator->launch();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void RemoteServer::deauthoriseClient( const std::shared_ptr< ClientInfo >& pClient )
+{
+ // TODO: we probably want to forcefully disconnect at this point too?
+ // But possibly via a separate function to allow just disconnecting from
+ // the UI.
+
+ SAL_INFO( "sdremote", "RemoteServer::deauthoriseClient called" );
+
+ if ( !pClient->mbIsAlreadyAuthorised )
+ // We can't remove unauthorised clients from the authorised list...
+ {
+ return;
+ }
+
+ std::shared_ptr< ConfigurationChanges > aChanges = ConfigurationChanges::create();
+ Reference< XNameContainer > const xConfig =
+ officecfg::Office::Impress::Misc::AuthorisedRemotes::get( aChanges );
+
+ xConfig->removeByName( pClient->mName );
+ aChanges->commit();
+}
+
+void SdDLL::RegisterRemotes()
+{
+ SAL_INFO( "sdremote", "SdDLL::RegisterRemotes called" );
+
+ // The remote server is likely of no use in headless mode. And as only
+ // one instance of the server can actually own the appropriate ports its
+ // probably best to not even try to do so from our headless instance
+ // (i.e. as to avoid blocking expected usage).
+ // It could perhaps be argued that we would still need the remote
+ // server for tiled rendering of presentations, but even then this
+ // implementation would not be of much use, i.e. would be controlling
+ // the purely imaginary headless presentation -- instead we'd need
+ // to have some sort of mechanism of plugging in our tiled rendering
+ // client to be controlled by the remote server, or provide an
+ // alternative implementation.
+ if ( Application::IsHeadlessModeEnabled() )
+ return;
+
+ if ( !officecfg::Office::Impress::Misc::Start::EnableSdremote::get() )
+ return;
+
+ sd::RemoteServer::setup();
+ sd::DiscoveryService::setup();
+}
+
+void RemoteServer::ensureDiscoverable()
+{
+ // FIXME: we could also enable listening on our WiFi
+ // socket here to significantly reduce the attack surface.
+#ifdef ENABLE_SDREMOTE_BLUETOOTH
+ BluetoothServer::ensureDiscoverable();
+#endif
+}
+
+void RemoteServer::restoreDiscoverable()
+{
+#ifdef ENABLE_SDREMOTE_BLUETOOTH
+ BluetoothServer::restoreDiscoverable();
+#endif
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Transmitter.cxx b/sd/source/ui/remotecontrol/Transmitter.cxx
new file mode 100644
index 000000000..cca6a3bee
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Transmitter.cxx
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#include "Transmitter.hxx"
+#include "IBluetoothSocket.hxx"
+#include <sal/log.hxx>
+
+using namespace osl; // Sockets etc.
+using namespace sd;
+
+Transmitter::Transmitter( IBluetoothSocket* aSocket )
+ : pStreamSocket( aSocket ),
+ mFinishRequested( false )
+{
+}
+
+void SAL_CALL Transmitter::run()
+{
+ osl_setThreadName("bluetooth Transmitter");
+
+ while ( true )
+ {
+ mProcessingRequired.wait();
+
+ ::osl::MutexGuard aGuard( mMutex );
+
+ if ( mFinishRequested ) {
+ return;
+ }
+ if ( !mHighPriority.empty() )
+ {
+ OString aMessage( mHighPriority.front() );
+ mHighPriority.pop();
+ SAL_INFO( "sdremote.bluetooth", "write high prio line '" << aMessage << "'" );
+ pStreamSocket->write( aMessage.getStr(), aMessage.getLength() );
+ }
+ else if ( !mLowPriority.empty() )
+ {
+ OString aMessage( mLowPriority.front() );
+ mLowPriority.pop();
+ SAL_INFO( "sdremote.bluetooth", "write normal line '" << aMessage << "'" );
+ pStreamSocket->write( aMessage.getStr(), aMessage.getLength() );
+ }
+
+ if ( mLowPriority.empty() && mHighPriority.empty())
+ {
+ mProcessingRequired.reset();
+ }
+ }
+}
+
+void Transmitter::notifyFinished()
+{
+ ::osl::MutexGuard aGuard( mMutex );
+ mFinishRequested = true;
+ mProcessingRequired.set();
+}
+
+Transmitter::~Transmitter()
+{
+}
+
+void Transmitter::addMessage( const OString& aMessage, const Priority aPriority )
+{
+ ::osl::MutexGuard aGuard( mMutex );
+ switch ( aPriority )
+ {
+ case PRIORITY_LOW:
+ mLowPriority.push( aMessage );
+ break;
+ case PRIORITY_HIGH:
+ mHighPriority.push( aMessage );
+ break;
+ }
+ if ( !mProcessingRequired.check() )
+ {
+ mProcessingRequired.set();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/Transmitter.hxx b/sd/source/ui/remotecontrol/Transmitter.hxx
new file mode 100644
index 000000000..c24f5a5a4
--- /dev/null
+++ b/sd/source/ui/remotecontrol/Transmitter.hxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include <osl/conditn.hxx>
+#include <osl/mutex.hxx>
+#include <osl/thread.hxx>
+#include <rtl/string.hxx>
+
+#include <queue>
+
+namespace sd { struct IBluetoothSocket; }
+
+namespace sd
+{
+
+class Transmitter
+: public osl::Thread
+{
+public:
+ enum Priority { PRIORITY_LOW = 1, PRIORITY_HIGH };
+ explicit Transmitter( ::sd::IBluetoothSocket* aSocket );
+ virtual ~Transmitter() override;
+ void addMessage( const OString& aMessage, const Priority aPriority );
+ void notifyFinished();
+
+private:
+ virtual void SAL_CALL run() override;
+
+ ::sd::IBluetoothSocket* pStreamSocket;
+
+ ::osl::Condition mProcessingRequired;
+
+ ::osl::Mutex mMutex;
+ /**
+ * Used to indicate that we're done and the transmitter loop should exit.
+ * All access must be guarded my `mMutex`.
+ */
+ bool mFinishRequested;
+ /// Queue for low priority messages. All access must be guarded my `mMutex`.
+ std::queue<OString> mLowPriority;
+ /// Queue for high priority messages. All access must be guarded my `mMutex`.
+ std::queue<OString> mHighPriority;
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/remotecontrol/WINNetworkService.cxx b/sd/source/ui/remotecontrol/WINNetworkService.cxx
new file mode 100644
index 000000000..bd2decf62
--- /dev/null
+++ b/sd/source/ui/remotecontrol/WINNetworkService.cxx
@@ -0,0 +1,19 @@
+#include <string>
+#include <iostream>
+#include "WINNetworkService.hxx"
+#include <sal/log.hxx>
+
+void sd::WINNetworkService::setup()
+{
+ DNSServiceErrorType err = DNSServiceRegister(&client, 0, 0, nullptr, kREG_TYPE, "local", nullptr, 1599, 1, "", nullptr, this );
+
+ if (kDNSServiceErr_NoError != err)
+ SAL_WARN("sdremote.wifi", "DNSServiceRegister failed: " << err);
+
+ // Fail silently
+}
+
+void sd::WINNetworkService::clear()
+{
+ DNSServiceRefDeallocate(client);
+}
diff --git a/sd/source/ui/remotecontrol/WINNetworkService.hxx b/sd/source/ui/remotecontrol/WINNetworkService.hxx
new file mode 100644
index 000000000..3d096dc0f
--- /dev/null
+++ b/sd/source/ui/remotecontrol/WINNetworkService.hxx
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <string>
+#undef WB_LEFT
+#undef WB_RIGHT
+#include <dns_sd.h>
+#include "ZeroconfService.hxx"
+
+namespace sd{
+ class WINNetworkService : public ZeroconfService
+ {
+ private:
+ DNSServiceRef client;
+
+ public:
+ WINNetworkService(const std::string& aname = "", unsigned int aport = 1599)
+ : ZeroconfService(aname, aport), client(nullptr) {}
+
+ void clear() override;
+ void setup() override;
+
+ };
+}
diff --git a/sd/source/ui/remotecontrol/ZeroconfService.hxx b/sd/source/ui/remotecontrol/ZeroconfService.hxx
new file mode 100644
index 000000000..a595d0b58
--- /dev/null
+++ b/sd/source/ui/remotecontrol/ZeroconfService.hxx
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#ifndef ZEROCONF_SERVICE
+#define ZEROCONF_SERVICE
+
+#include <string>
+
+/**
+* The port used by LO's custom remote server discovery protocol.
+*/
+#define PORT_DISCOVERY 1598
+#define BUFFER_SIZE 200
+
+#define kREG_TYPE "_impressremote._tcp"
+
+struct sockaddr_in;
+
+typedef unsigned int uint;
+
+namespace sd{
+
+ class ZeroconfService
+ {
+ protected:
+ std::string name;
+ uint port;
+
+ public:
+ explicit ZeroconfService(const std::string& aname, uint aport)
+ :name(aname), port(aport){}
+ virtual ~ZeroconfService(){}
+
+ const std::string& getName() const {return name;}
+ void setName(const char * n) {name = n;}
+
+ // Clean up the service when closing
+ virtual void clear() = 0;
+ // Bonjour for OSX, Avahi for Linux
+ virtual void setup() = 0;
+ };
+
+}
+#endif