diff --git a/MultiSoundChanger.xcodeproj/project.pbxproj b/MultiSoundChanger.xcodeproj/project.pbxproj index 75c9711..5a61d4f 100644 --- a/MultiSoundChanger.xcodeproj/project.pbxproj +++ b/MultiSoundChanger.xcodeproj/project.pbxproj @@ -3,12 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 4743EFAB1E91493B0032F5AA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4743EFAA1E91493B0032F5AA /* AppDelegate.swift */; }; 6985C6FE251951F8003C2FDB /* OSD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6985C6FD251951F8003C2FDB /* OSD.framework */; }; + D914CAC52698095C00FB55D2 /* SimplyCoreAudio in Frameworks */ = {isa = PBXBuildFile; productRef = D914CAC42698095C00FB55D2 /* SimplyCoreAudio */; }; E4FFDC0757FD125F92CC0F62 /* Pods_MultiSoundChanger.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C34C05E9BD81D579A0C4957 /* Pods_MultiSoundChanger.framework */; }; F312C54E25B3741C00205846 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F373D8C02561D24600642274 /* Main.storyboard */; }; F312C55025B3742200205846 /* Volume.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F373D8BC2561D22000642274 /* Volume.storyboard */; }; @@ -62,6 +63,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D914CAC52698095C00FB55D2 /* SimplyCoreAudio in Frameworks */, 6985C6FE251951F8003C2FDB /* OSD.framework in Frameworks */, E4FFDC0757FD125F92CC0F62 /* Pods_MultiSoundChanger.framework in Frameworks */, ); @@ -242,6 +244,9 @@ dependencies = ( ); name = MultiSoundChanger; + packageProductDependencies = ( + D914CAC42698095C00FB55D2 /* SimplyCoreAudio */, + ); productName = DynamicsIllusion; productReference = 4743EFA71E91493B0032F5AA /* MultiSoundChanger.app */; productType = "com.apple.product-type.application"; @@ -271,6 +276,9 @@ Base, ); mainGroup = 4743EF9E1E91493B0032F5AA; + packageReferences = ( + D914CAC32698095C00FB55D2 /* XCRemoteSwiftPackageReference "SimplyCoreAudio" */, + ); productRefGroup = 4743EFA81E91493B0032F5AA /* Products */; projectDirPath = ""; projectRoot = ""; @@ -500,7 +508,8 @@ MACOSX_DEPLOYMENT_TARGET = 10.10; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = Release; @@ -520,8 +529,11 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = MultiSoundChanger/Other/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.12; MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.rlxone.multisoundchanger; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -546,8 +558,11 @@ "$(PROJECT_DIR)", ); INFOPLIST_FILE = MultiSoundChanger/Other/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.12; MARKETING_VERSION = 1.0.1; PRODUCT_BUNDLE_IDENTIFIER = com.rlxone.multisoundchanger; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -579,6 +594,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + D914CAC32698095C00FB55D2 /* XCRemoteSwiftPackageReference "SimplyCoreAudio" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/rnine/SimplyCoreAudio"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + D914CAC42698095C00FB55D2 /* SimplyCoreAudio */ = { + isa = XCSwiftPackageProductDependency; + package = D914CAC32698095C00FB55D2 /* XCRemoteSwiftPackageReference "SimplyCoreAudio" */; + productName = SimplyCoreAudio; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 4743EF9F1E91493B0032F5AA /* Project object */; } diff --git a/MultiSoundChanger/Sources/Classes/ApplicationController.swift b/MultiSoundChanger/Sources/Classes/ApplicationController.swift index 6498952..1dc3e79 100644 --- a/MultiSoundChanger/Sources/Classes/ApplicationController.swift +++ b/MultiSoundChanger/Sources/Classes/ApplicationController.swift @@ -8,6 +8,7 @@ import Foundation import MediaKeyTap +import SimplyCoreAudio // MARK: - Protocols @@ -18,13 +19,34 @@ protocol ApplicationController: class { // MARK: - Implementation final class ApplicationControllerImp: ApplicationController { + private lazy var simplyCA: SimplyCoreAudio = SimplyCoreAudio() private lazy var audioManager: AudioManager = AudioManagerImpl() private lazy var mediaManager: MediaManager = MediaManagerImpl(delegate: self) - private lazy var statusBarController: StatusBarController = StatusBarControllerImpl(audioManager: audioManager) + private lazy var statusBarController: StatusBarController = StatusBarControllerImpl(audioManager: audioManager, simplyCoreAudio: simplyCA) + var observers: [NSObjectProtocol] = [] + func start() { statusBarController.createMenu() mediaManager.listenMediaKeyTaps() + + observers.append(NotificationCenter.default.addObserver(forName: .deviceListChanged, + object: nil, + queue: .main) { [weak self] _ in + self?.statusBarController.createMenu() + }) + + observers.append(NotificationCenter.default.addObserver(forName: .defaultOutputDeviceChanged, + object: nil, + queue: .main) { [weak self] _ in + self?.statusBarController.createMenu() + }) + } + + deinit { + for observer in observers { + NotificationCenter.default.removeObserver(observer) + } } } diff --git a/MultiSoundChanger/Sources/Classes/AudioManager.swift b/MultiSoundChanger/Sources/Classes/AudioManager.swift index 9f54308..fadaf65 100644 --- a/MultiSoundChanger/Sources/Classes/AudioManager.swift +++ b/MultiSoundChanger/Sources/Classes/AudioManager.swift @@ -12,8 +12,6 @@ import Foundation // MARK: - Protocols protocol AudioManager: class { - func getDefaultOutputDevice() -> AudioDeviceID - func getOutputDevices() -> [AudioDeviceID: String]? func selectDevice(deviceID: AudioDeviceID) func getSelectedDeviceVolume() -> Float? func setSelectedDeviceVolume(masterChannelLevel: Float, leftChannelLevel: Float, rightChannelLevel: Float) @@ -27,14 +25,28 @@ protocol AudioManager: class { final class AudioManagerImpl: AudioManager { private let audio: Audio = AudioImpl() - private let devices: [AudioDeviceID: String]? + private var devices: [AudioDeviceID: String]? private var selectedDevice: AudioDeviceID? - + + private lazy var observer = NotificationCenter.default.addObserver(forName: .deviceListChanged, + object: nil, + queue: .main) { [weak self] _ in + self?.refreshDevices() + } + init() { - devices = audio.getOutputDevices() - printDevices() + refreshDevices() } + deinit { + NotificationCenter.default.removeObserver(observer) + } + + func refreshDevices() { + self.devices = audio.getOutputDevices() + self.printDevices() + } + func getDefaultOutputDevice() -> AudioDeviceID { return audio.getDefaultOutputDevice() } diff --git a/MultiSoundChanger/Sources/Classes/StatusBarController.swift b/MultiSoundChanger/Sources/Classes/StatusBarController.swift index a23eb41..35fdf48 100644 --- a/MultiSoundChanger/Sources/Classes/StatusBarController.swift +++ b/MultiSoundChanger/Sources/Classes/StatusBarController.swift @@ -6,6 +6,7 @@ // Copyright © 2020 Dmitry Medyuho. All rights reserved. // +import SimplyCoreAudio import AudioToolbox import Cocoa @@ -37,9 +38,11 @@ final class StatusBarControllerImpl: StatusBarController { private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) private let volumeController: VolumeViewController private let audioManager: AudioManager + private let simplyCA: SimplyCoreAudio - init(audioManager: AudioManager) { + init(audioManager: AudioManager, simplyCoreAudio: SimplyCoreAudio) { self.audioManager = audioManager + self.simplyCA = simplyCoreAudio self.volumeController = Stories.volume.controller(VolumeViewController.self) self.volumeController.audioManager = audioManager @@ -51,7 +54,8 @@ final class StatusBarControllerImpl: StatusBarController { button.image = Images.volumeImage1 } - let menu = NSMenu() + let menu = statusItem.menu ?? NSMenu() + menu.removeAllItems() menu.autoenablesItems = false let volumeItem = getMenuItem(by: .volume) @@ -135,24 +139,22 @@ final class StatusBarControllerImpl: StatusBarController { } private func setOutputDeviceList(for menu: NSMenu) { - guard let devices = audioManager.getOutputDevices() else { - return - } + let devices = simplyCA.allOutputDevices - let defaultDevice = audioManager.getDefaultOutputDevice() + let defaultDevice = simplyCA.defaultOutputDevice for device in devices { let item = NSMenuItem( - title: truncate(device.value, length: Constants.optionMaxLength), + title: truncate(device.name, length: Constants.optionMaxLength), action: #selector(menuItemAction), keyEquivalent: String() ) item.target = self - item.tag = Int(device.key) + item.tag = Int(device.id) - if device.key == defaultDevice { + if device.id == defaultDevice?.id { item.state = .on - selectDevice(device: defaultDevice) + selectDevice(device: device.id) } menu.addItem(item) @@ -160,7 +162,15 @@ final class StatusBarControllerImpl: StatusBarController { } private func selectDevice(device: AudioDeviceID) { - audioManager.selectDevice(deviceID: device) + guard let new_device = simplyCA.allDevices.first(where: { $0.id == device }) else { + return + } + new_device.isDefaultOutputDevice = true + new_device.isDefaultSystemOutputDevice = true + if new_device.isOutputOnlyDevice == false { + new_device.isDefaultInputDevice = true + } + guard let volume = audioManager.getSelectedDeviceVolume() else { return }