diff --git a/Client/Constants.h b/Client/Constants.h index fafd6eb..7c18370 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -9,6 +9,10 @@ inline constexpr char END_MARKER{ 60 }; inline constexpr auto MAC_ADDR_STR_SIZE = 17; inline constexpr auto SERVICE_UUID = "96CC203E-5068-46ad-B32D-E316F5E069BA"; +inline unsigned char SERVICE_UUID_IN_BYTES[] = { // this is the SERVICE_UUID but in bytes + 0x96, 0xcc, 0x20, 0x3e, 0x50, 0x68, 0x46, 0xad, + 0xb3, 0x2d, 0xe3, 0x16, 0xf5, 0xe0, 0x69, 0xba +}; inline constexpr auto APP_NAME_W = L"Sony Headphones App"; diff --git a/Client/macos/AppDelegate.h b/Client/macos/AppDelegate.h new file mode 100644 index 0000000..b932706 --- /dev/null +++ b/Client/macos/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// SonyHeadphonesClient +// +// Created by Sem Visscher on 01/12/2020. +// + +#import +#import +#import +#import "BluetoothWrapper.h" +#import "MacOSBluetoothConnector.h" + +@interface AppDelegate : NSObject + @property (weak) NSWindow* window; + +@end diff --git a/Client/macos/AppDelegate.mm b/Client/macos/AppDelegate.mm new file mode 100644 index 0000000..5d09dc3 --- /dev/null +++ b/Client/macos/AppDelegate.mm @@ -0,0 +1,35 @@ +// +// AppDelegate.m +// SonyHeadphonesClient +// +// Created by Sem Visscher on 01/12/2020. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + + +@end + +@implementation AppDelegate +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + _window = [[[NSApplication sharedApplication] windows] firstObject]; +} + +- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag +{ + if (flag) { + return NO; + } + else { + [_window makeKeyAndOrderFront:self]; + return YES; + } +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + +@end diff --git a/Client/macos/MacOSBluetoothConnector.h b/Client/macos/MacOSBluetoothConnector.h new file mode 100644 index 0000000..14bd1f7 --- /dev/null +++ b/Client/macos/MacOSBluetoothConnector.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include "../IBluetoothConnector.h" +#include "IOBluetooth/IOBluetooth.h" +#include "Constants.h" +#include +#include +#include +#include +#include + +class MacOSBluetoothConnector final : public IBluetoothConnector +{ +public: + MacOSBluetoothConnector(); + ~MacOSBluetoothConnector(); + static void connectToMac(MacOSBluetoothConnector* MacOSBluetoothConnector) noexcept(false); + virtual void connect(const std::string& addrStr) noexcept(false); + virtual int send(char* buf, size_t length) noexcept(false); + virtual int recv(char* buf, size_t length) noexcept(false); + virtual void disconnect() noexcept; + virtual bool isConnected() noexcept; + virtual void closeConnection(); + + virtual std::vector getConnectedDevices() noexcept(false); + std::deque> receivedBytes; + std::mutex receiveDataMutex; + std::condition_variable receiveDataConditionVariable; + std::atomic running = false; + +private: + void *rfcommDevice; + void *rfcommchannel; + std::thread* uthread = NULL; +}; diff --git a/Client/macos/MacOSBluetoothConnector.mm b/Client/macos/MacOSBluetoothConnector.mm new file mode 100644 index 0000000..333f8b8 --- /dev/null +++ b/Client/macos/MacOSBluetoothConnector.mm @@ -0,0 +1,158 @@ +#include "MacOSBluetoothConnector.h" + +MacOSBluetoothConnector::MacOSBluetoothConnector() +{ + +} +MacOSBluetoothConnector::~MacOSBluetoothConnector() +{ + // onclose event + if (isConnected()){ + disconnect(); + } +} + +@interface AsyncCommDelegate : NSObject { +@public + MacOSBluetoothConnector* delegateCPP; +} +@end + +@implementation AsyncCommDelegate { +} +- (void)rfcommChannelClosed:(IOBluetoothRFCOMMChannel *)rfcommChannel{ + delegateCPP->disconnect(); +} + +-(void)rfcommChannelData:(IOBluetoothRFCOMMChannel *)rfcommChannel data:(void *)dataPointer length:(size_t)dataLength +{ + std::lock_guard g(delegateCPP->receiveDataMutex); + + unsigned char* buffer = (unsigned char*)dataPointer; + std::vector vectorBuffer(buffer, buffer+dataLength); + + delegateCPP->receivedBytes.push_back(vectorBuffer); + delegateCPP->receiveDataConditionVariable.notify_one(); +} + + +@end + +int MacOSBluetoothConnector::send(char* buf, size_t length) +{ + [(__bridge IOBluetoothRFCOMMChannel*)rfcommchannel writeSync:(void*)buf length:length]; + return (int)length; +} + + +void MacOSBluetoothConnector::connectToMac(MacOSBluetoothConnector* macOSBluetoothConnector) +{ + // get device + IOBluetoothDevice *device = (__bridge IOBluetoothDevice *)macOSBluetoothConnector->rfcommDevice; + // create new channel + IOBluetoothRFCOMMChannel *channel = [[IOBluetoothRFCOMMChannel alloc] init]; + // create sppServiceid + IOBluetoothSDPUUID *sppServiceUUID = [IOBluetoothSDPUUID uuidWithBytes:(void*)SERVICE_UUID_IN_BYTES length: 16]; + // get sppServiceRecord + IOBluetoothSDPServiceRecord *sppServiceRecord = [device getServiceRecordForUUID:sppServiceUUID]; + // get rfcommChannelID from sppServiceRecord + UInt8 rfcommChannelID; + [sppServiceRecord getRFCOMMChannelID:&rfcommChannelID]; + // setup delegate + AsyncCommDelegate* asyncCommDelegate = [[AsyncCommDelegate alloc] init]; + asyncCommDelegate->delegateCPP = macOSBluetoothConnector; + // try to open channel + if ( [device openRFCOMMChannelAsync:&channel withChannelID:rfcommChannelID delegate:asyncCommDelegate] != kIOReturnSuccess ) { + throw RecoverableException("Error - could not open the rfcomm.\n", true); + } + // store the channel + macOSBluetoothConnector->rfcommchannel = (__bridge void*) channel; + + macOSBluetoothConnector->running = true; + // keep thread running + while (macOSBluetoothConnector->running) { + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]]; + } +} +void MacOSBluetoothConnector::connect(const std::string& addrStr){ + // convert mac adress to nsstring + NSString *addressNSString = [NSString stringWithCString:addrStr.c_str() encoding:[NSString defaultCStringEncoding]]; + // get device based on mac adress + IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:addressNSString]; + // if device is not connected + if (![device isConnected]) { + [device openConnection]; + } + // store the device in an variable + rfcommDevice = (__bridge void*) device; + uthread = new std::thread(MacOSBluetoothConnector::connectToMac, this); +} + +int MacOSBluetoothConnector::recv(char* buf, size_t length) +{ + // wait for newly received data + std::unique_lock g(receiveDataMutex); + receiveDataConditionVariable.wait(g, [this]{ return !receivedBytes.empty(); }); + + // fill the buf with the new data + std::vector receivedVector = receivedBytes.front(); + receivedBytes.pop_front(); + + size_t lengthCopied = std::min(length, receivedVector.size()); + + // copy the first amount of bytes + std::memcpy(buf, receivedVector.data(), lengthCopied); + + // too much data, save it for next time + if (receivedVector.size() > length){ + receivedVector.erase(receivedVector.begin(), receivedVector.begin() + lengthCopied); + receivedBytes.push_front(receivedVector); + } + + return (int)lengthCopied; +} + +std::vector MacOSBluetoothConnector::getConnectedDevices() +{ + // create the output vector + std::vector res; + // loop through the paired devices (also includes non paired devices for some reason) + for (IOBluetoothDevice* device in [IOBluetoothDevice pairedDevices]) { + // check if device is connected + if ([device isConnected]) { + BluetoothDevice dev; + // save the mac address and name + dev.mac = [[device addressString]UTF8String]; + dev.name = [[device name] UTF8String]; + // add device to the connected devices vector + res.push_back(dev); + } + } + + return res; +} + +void MacOSBluetoothConnector::disconnect() noexcept +{ + running = false; + // wait for the thread to finish + uthread->join(); + // close connection + closeConnection(); +} +void MacOSBluetoothConnector::closeConnection() { + // get the channel + IOBluetoothRFCOMMChannel *chan = (__bridge IOBluetoothRFCOMMChannel*) rfcommchannel; + [chan setDelegate: nil]; + // close the channel + [chan closeChannel]; +} + + +bool MacOSBluetoothConnector::isConnected() noexcept +{ + if (!running) + return false; + IOBluetoothRFCOMMChannel *chan = (__bridge IOBluetoothRFCOMMChannel*) rfcommchannel; + return chan.isOpen; +} diff --git a/Client/macos/Main.storyboard b/Client/macos/Main.storyboard new file mode 100644 index 0000000..6c54aa6 --- /dev/null +++ b/Client/macos/Main.storyboard @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! This product is not affiliated with Sony. Use at your own risk. ! +Source: https://github.com/Plutoberth/SonyHeadphonesClient + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Client/macos/SonyHeadphonesClient.entitlements b/Client/macos/SonyHeadphonesClient.entitlements new file mode 100644 index 0000000..e9da8e1 --- /dev/null +++ b/Client/macos/SonyHeadphonesClient.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.bluetooth + + com.apple.security.files.user-selected.read-only + + + diff --git a/Client/macos/SonyHeadphonesClient.xcodeproj/project.pbxproj b/Client/macos/SonyHeadphonesClient.xcodeproj/project.pbxproj new file mode 100644 index 0000000..07168c1 --- /dev/null +++ b/Client/macos/SonyHeadphonesClient.xcodeproj/project.pbxproj @@ -0,0 +1,373 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + A87B328B259FAA8B00EA6551 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A87B328A259FAA8B00EA6551 /* Cocoa.framework */; }; + A87B328E259FAB0E00EA6551 /* IOBluetoothUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A87B328D259FAB0E00EA6551 /* IOBluetoothUI.framework */; }; + A8DC78EB2576923D00447738 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8DC78EA2576923D00447738 /* main.mm */; }; + A8DC78EC2576923D00447738 /* MacOSBluetoothConnector.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8DC78E82576923D00447738 /* MacOSBluetoothConnector.mm */; }; + A8DC78F12576952E00447738 /* BluetoothWrapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A8DC78EF2576952E00447738 /* BluetoothWrapper.cpp */; }; + A8DC78F52576953600447738 /* CommandSerializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A8DC78F32576953600447738 /* CommandSerializer.cpp */; }; + A8DC78FA2576954100447738 /* TimedMessageQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A8DC78F92576954100447738 /* TimedMessageQueue.cpp */; }; + A8DC78FF2576967D00447738 /* ByteMagic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A8DC78FD2576967D00447738 /* ByteMagic.cpp */; }; + A8DC790525769A6600447738 /* IOBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8DC790425769A6600447738 /* IOBluetooth.framework */; }; + A8DC792C25769C9900447738 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8DC792825769C9900447738 /* AppDelegate.mm */; }; + A8DC792D25769C9900447738 /* ViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A8DC792A25769C9900447738 /* ViewController.mm */; }; + A8DC793225769EA500447738 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A8DC78E92576923D00447738 /* Main.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + A863B14925A11DF500101F3B /* SonyHeadphonesClient.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SonyHeadphonesClient.entitlements; sourceTree = ""; }; + A87B327A259F69BA00EA6551 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Constants.h; path = ../Constants.h; sourceTree = ""; }; + A87B328A259FAA8B00EA6551 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + A87B328D259FAB0E00EA6551 /* IOBluetoothUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOBluetoothUI.framework; path = System/Library/Frameworks/IOBluetoothUI.framework; sourceTree = SDKROOT; }; + A8DC78E32576922700447738 /* info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = info.plist; sourceTree = ""; }; + A8DC78E62576922D00447738 /* SonyHeadphonesClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SonyHeadphonesClient.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A8DC78E72576923D00447738 /* MacOSBluetoothConnector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MacOSBluetoothConnector.h; sourceTree = ""; }; + A8DC78E82576923D00447738 /* MacOSBluetoothConnector.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacOSBluetoothConnector.mm; sourceTree = ""; }; + A8DC78E92576923D00447738 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + A8DC78EA2576923D00447738 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + A8DC78EE2576950500447738 /* IBluetoothConnector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = IBluetoothConnector.h; path = ../IBluetoothConnector.h; sourceTree = ""; }; + A8DC78EF2576952E00447738 /* BluetoothWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = BluetoothWrapper.cpp; path = ../BluetoothWrapper.cpp; sourceTree = ""; }; + A8DC78F02576952E00447738 /* BluetoothWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BluetoothWrapper.h; path = ../BluetoothWrapper.h; sourceTree = ""; }; + A8DC78F32576953600447738 /* CommandSerializer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommandSerializer.cpp; path = ../CommandSerializer.cpp; sourceTree = ""; }; + A8DC78F42576953600447738 /* CommandSerializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CommandSerializer.h; path = ../CommandSerializer.h; sourceTree = ""; }; + A8DC78F72576954100447738 /* TimedMessageQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TimedMessageQueue.h; path = ../TimedMessageQueue.h; sourceTree = ""; }; + A8DC78F82576954100447738 /* SingleInstanceFuture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SingleInstanceFuture.h; path = ../SingleInstanceFuture.h; sourceTree = ""; }; + A8DC78F92576954100447738 /* TimedMessageQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = TimedMessageQueue.cpp; path = ../TimedMessageQueue.cpp; sourceTree = ""; }; + A8DC78FD2576967D00447738 /* ByteMagic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ByteMagic.cpp; path = ../ByteMagic.cpp; sourceTree = ""; }; + A8DC78FE2576967D00447738 /* ByteMagic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ByteMagic.h; path = ../ByteMagic.h; sourceTree = ""; }; + A8DC790425769A6600447738 /* IOBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOBluetooth.framework; path = System/Library/Frameworks/IOBluetooth.framework; sourceTree = SDKROOT; }; + A8DC792825769C9900447738 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = ""; }; + A8DC792925769C9900447738 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + A8DC792A25769C9900447738 /* ViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewController.mm; sourceTree = ""; }; + A8DC792B25769C9900447738 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A8DC78C125757BF300447738 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A87B328E259FAB0E00EA6551 /* IOBluetoothUI.framework in Frameworks */, + A87B328B259FAA8B00EA6551 /* Cocoa.framework in Frameworks */, + A8DC790525769A6600447738 /* IOBluetooth.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A8DC78BB25757BF300447738 = { + isa = PBXGroup; + children = ( + A863B14925A11DF500101F3B /* SonyHeadphonesClient.entitlements */, + A87B327A259F69BA00EA6551 /* Constants.h */, + A8DC78E72576923D00447738 /* MacOSBluetoothConnector.h */, + A8DC78E82576923D00447738 /* MacOSBluetoothConnector.mm */, + A8DC78EA2576923D00447738 /* main.mm */, + A8DC78E92576923D00447738 /* Main.storyboard */, + A8DC78E32576922700447738 /* info.plist */, + A8DC78E62576922D00447738 /* SonyHeadphonesClient.app */, + A8DC78EE2576950500447738 /* IBluetoothConnector.h */, + A8DC78EF2576952E00447738 /* BluetoothWrapper.cpp */, + A8DC78F02576952E00447738 /* BluetoothWrapper.h */, + A8DC78F32576953600447738 /* CommandSerializer.cpp */, + A8DC78F42576953600447738 /* CommandSerializer.h */, + A8DC78F82576954100447738 /* SingleInstanceFuture.h */, + A8DC78F92576954100447738 /* TimedMessageQueue.cpp */, + A8DC78F72576954100447738 /* TimedMessageQueue.h */, + A8DC78FD2576967D00447738 /* ByteMagic.cpp */, + A8DC78FE2576967D00447738 /* ByteMagic.h */, + A8DC792B25769C9900447738 /* AppDelegate.h */, + A8DC792825769C9900447738 /* AppDelegate.mm */, + A8DC792925769C9900447738 /* ViewController.h */, + A8DC792A25769C9900447738 /* ViewController.mm */, + A8DC790325769A6600447738 /* Frameworks */, + ); + sourceTree = ""; + }; + A8DC790325769A6600447738 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A87B328D259FAB0E00EA6551 /* IOBluetoothUI.framework */, + A87B328A259FAA8B00EA6551 /* Cocoa.framework */, + A8DC790425769A6600447738 /* IOBluetooth.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A8DC78C325757BF300447738 /* SonyHeadphonesClient */ = { + isa = PBXNativeTarget; + buildConfigurationList = A8DC78D825757BF500447738 /* Build configuration list for PBXNativeTarget "SonyHeadphonesClient" */; + buildPhases = ( + A8DC78C025757BF300447738 /* Sources */, + A8DC78C125757BF300447738 /* Frameworks */, + A8DC78C225757BF300447738 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SonyHeadphonesClient; + productName = SonyHeadphonesClient; + productReference = A8DC78E62576922D00447738 /* SonyHeadphonesClient.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A8DC78BC25757BF300447738 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1230; + TargetAttributes = { + A8DC78C325757BF300447738 = { + CreatedOnToolsVersion = 12.2; + }; + }; + }; + buildConfigurationList = A8DC78BF25757BF300447738 /* Build configuration list for PBXProject "SonyHeadphonesClient" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = A8DC78BB25757BF300447738; + productRefGroup = A8DC78BB25757BF300447738; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A8DC78C325757BF300447738 /* SonyHeadphonesClient */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A8DC78C225757BF300447738 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A8DC793225769EA500447738 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A8DC78C025757BF300447738 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A8DC78EB2576923D00447738 /* main.mm in Sources */, + A8DC78EC2576923D00447738 /* MacOSBluetoothConnector.mm in Sources */, + A8DC792D25769C9900447738 /* ViewController.mm in Sources */, + A8DC78F12576952E00447738 /* BluetoothWrapper.cpp in Sources */, + A8DC78FA2576954100447738 /* TimedMessageQueue.cpp in Sources */, + A8DC78F52576953600447738 /* CommandSerializer.cpp in Sources */, + A8DC78FF2576967D00447738 /* ByteMagic.cpp in Sources */, + A8DC792C25769C9900447738 /* AppDelegate.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A8DC78D625757BF500447738 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + A8DC78D725757BF500447738 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + A8DC78D925757BF500447738 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CODE_SIGN_ENTITLEMENTS = SonyHeadphonesClient.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_CPLUSPLUSFLAGS = ( + "-std=c++17", + "-stdlib=libc++", + "$(OTHER_CFLAGS)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.semvis123.SonyHeadphonesClient; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + A8DC78DA25757BF500447738 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CODE_SIGN_ENTITLEMENTS = SonyHeadphonesClient.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = "$(SRCROOT)/info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_CPLUSPLUSFLAGS = ( + "-std=c++17", + "-stdlib=libc++", + "$(OTHER_CFLAGS)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.semvis123.SonyHeadphonesClient; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A8DC78BF25757BF300447738 /* Build configuration list for PBXProject "SonyHeadphonesClient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A8DC78D625757BF500447738 /* Debug */, + A8DC78D725757BF500447738 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A8DC78D825757BF500447738 /* Build configuration list for PBXNativeTarget "SonyHeadphonesClient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A8DC78D925757BF500447738 /* Debug */, + A8DC78DA25757BF500447738 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A8DC78BC25757BF300447738 /* Project object */; +} diff --git a/Client/macos/SonyHeadphonesClient.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Client/macos/SonyHeadphonesClient.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Client/macos/SonyHeadphonesClient.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Client/macos/ViewController.h b/Client/macos/ViewController.h new file mode 100644 index 0000000..528f4a4 --- /dev/null +++ b/Client/macos/ViewController.h @@ -0,0 +1,22 @@ +// +// ViewController.h +// SonyHeadphonesClient +// +// Created by Sem Visscher on 01/12/2020. +// + +#import +#import "AppDelegate.h" + +BluetoothWrapper bt = (BluetoothWrapper)nil; +NSStatusItem* statusItem; + +@interface ViewController : NSViewController +@property (weak, nonatomic) IBOutlet NSTextField *connectedLabel; +@property (weak, nonatomic) IBOutlet NSTextField *ANCValueLabel; +@property (weak, nonatomic) IBOutlet NSButton *focusOnVoice; +@property (weak, nonatomic) IBOutlet NSButton *connectButton; +@property (weak, nonatomic) IBOutlet NSSlider *ANCSlider; + +@end + diff --git a/Client/macos/ViewController.mm b/Client/macos/ViewController.mm new file mode 100644 index 0000000..9711ab5 --- /dev/null +++ b/Client/macos/ViewController.mm @@ -0,0 +1,144 @@ +// +// ViewController.m +// SonyHeadphonesClient +// +// Created by Sem Visscher on 01/12/2020. +// + +#import "ViewController.h" + +@implementation ViewController +@synthesize connectedLabel, connectButton, ANCSlider, ANCValueLabel, focusOnVoice; + +- (void)viewDidLoad { + [super viewDidLoad]; + std::unique_ptr connector = std::make_unique(); + // Wrap the connector using the bluetoothwrapper + bt = BluetoothWrapper(std::move(connector)); + statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1]; + statusItem.button.image = [NSImage imageNamed:@"NSRefreshTemplate"]; + [statusItem setTarget:self]; + [statusItem setAction:@selector(statusItemClick:)]; +} + +- (void)displayError:(RecoverableException)exc { + if (exc.shouldDisconnect){ + bt.disconnect(); + [ANCSlider setEnabled:FALSE]; + [focusOnVoice setEnabled:FALSE]; + [connectButton setTitle:@"Connect to Bluetooth device"]; + statusItem.button.image = [NSImage imageNamed:@"NSRefreshTemplate"]; + [connectedLabel setStringValue:[@"Unexpected error occurred and disconnected: \n" stringByAppendingString:@(exc.what())]]; + } else { + [connectedLabel setStringValue:[@"Unexpected error occurred: \n" stringByAppendingString:@(exc.what())]]; + } +} + +- (void)statusItemClick:(id)sender { + if (!bt.isConnected()) + return [self connectToDevice:self]; + + if ([focusOnVoice isEnabled]) { + [ANCSlider setIntValue:0]; + [focusOnVoice setEnabled:FALSE]; + } else { + [ANCSlider setIntValue:19]; + [focusOnVoice setEnabled:TRUE]; + } + [self sendData:self]; +} + +- (void)setRepresentedObject:(id)representedObject { + [super setRepresentedObject:representedObject]; +} + +- (IBAction)connectToDevice:(id)sender { + statusItem.button.image = [NSImage imageNamed:@"NSRefreshTemplate"]; + + // check if it should disconnect + if (bt.isConnected()) { + bt.disconnect(); + [ANCSlider setEnabled:FALSE]; + [focusOnVoice setEnabled:FALSE]; + [connectButton setTitle:@"Connect to Bluetooth device"]; + [connectedLabel setStringValue:@"Not connected"]; + return; + } + + // launch device selector modal + IOBluetoothDeviceSelectorController *dSelector = [IOBluetoothDeviceSelectorController deviceSelector]; + int result = [dSelector runModal]; + + + if (result == kIOBluetoothUISuccess) { + // get device + IOBluetoothDevice *device = [[dSelector getResults] lastObject]; + try { + // connect to device + bt.connect([[device addressString] UTF8String]); + } catch (RecoverableException& exc) { + [self displayError:exc]; + } + // give it some time to connect + int timeout = 5; + while(!bt.isConnected() and timeout >= 0) { + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + timeout--; + } + if (bt.isConnected()) { + IOBluetoothDevice *device = [[dSelector getResults] lastObject]; + [connectedLabel setStringValue:[@"Connected: " stringByAppendingString:[device nameOrAddress]]]; + [connectButton setTitle:@"Disconnect"]; + [ANCSlider setEnabled:TRUE]; + [focusOnVoice setEnabled:FALSE]; + statusItem.button.image = [NSImage imageNamed:@"NSHomeTemplate"]; + } else { + [connectedLabel setStringValue:@"Not connected, connection timed out."]; + bt.disconnect(); + } + } else { + [connectedLabel setStringValue:@"Not connected, device selector canceled."]; + } + +} + +- (IBAction)sendData:(id)sender { + // check if the device is still connected + if (!bt.isConnected()) { + [ANCSlider setEnabled:FALSE]; + [ANCSlider setIntValue:0]; + [focusOnVoice setEnabled:FALSE]; + [connectButton setTitle:@"Connect to Bluetooth device"]; + [connectedLabel setStringValue:@"Not connected, please reconnect."]; + statusItem.button.image = [NSImage imageNamed:@"NSRefreshTemplate"]; + return; + } + + [ANCValueLabel setStringValue:ANCSlider.stringValue]; + if (ANCSlider.intValue >= MINIMUM_VOICE_FOCUS_STEP) { + [focusOnVoice setTitle:@"Focus on Voice"]; + [focusOnVoice setEnabled:TRUE]; + statusItem.button.image = [NSImage imageNamed:@"NSFlowViewTemplate"]; + } + else { + [focusOnVoice setTitle:@"Focus on Voice isn't enabled on this level."]; + statusItem.button.image = [NSImage imageNamed:@"NSHomeTemplate"]; + [focusOnVoice setEnabled:FALSE]; + } + + // send current settings + auto ncAsmEffect = NC_ASM_EFFECT::ADJUSTMENT_COMPLETION; + auto asmId = focusOnVoice.state == NSControlStateValueOn ? ASM_ID::VOICE : ASM_ID::NORMAL; + try { + bt.sendCommand(CommandSerializer::serializeNcAndAsmSetting( + ncAsmEffect, + NC_ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, + ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, + asmId, + ANCSlider.intValue + )); + } catch (RecoverableException& exc) { + [self displayError:exc]; + } +} +@end diff --git a/Client/macos/info.plist b/Client/macos/info.plist new file mode 100644 index 0000000..f2a1628 --- /dev/null +++ b/Client/macos/info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleExecutable + SonyHeadphonesClient + CFBundleIdentifier + com.semvis123.SonyHeadphonesClient + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SonyHeadphonesClient + CFBundlePackageType + APPL + LSApplicationCategoryType + public.app-category.utilities + NSHighResolutionCapable + True + NSBluetoothAlwaysUsageDescription + This app uses Bluetooth in order to connect to your headphones. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/Client/macos/main.mm b/Client/macos/main.mm new file mode 100644 index 0000000..0eb4794 --- /dev/null +++ b/Client/macos/main.mm @@ -0,0 +1,9 @@ +#include +#import +#include "MacOSBluetoothConnector.h" +#include "BluetoothWrapper.h" + +int main(int argc, const char * argv[]) +{ + return NSApplicationMain(argc, argv); +} diff --git a/LICENSE b/LICENSE index a7e1895..d22d30e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Nir Harel, Mor Gal, and other contributors +Copyright (c) 2020 Nir Harel, Mor Gal, Sem Visscher, and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 12adfa6..23f0cfa 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ It shouldn't be too much work to add support for a new platform. Open a PR if yo - [x] Windows - [ ] [Linux](https://github.com/Plutoberth/SonyHeadphonesClient/issues/7) - In Progress by jimzrt -- [ ] [macOS](https://github.com/Plutoberth/SonyHeadphonesClient/issues/6) - In Progress by semvis123 +- [x] macOS ## For Developers @@ -71,6 +71,10 @@ It shouldn't be too much work to add support for a new platform. Open a PR if yo Use the provided solution file. +#### macOS + +Use the provided xcodeproj file. + ### Adding a new platform There are two platform dependent parts in the code - the GUI and Bluetooth communication.