From 6707ba03e9829e331508500b431a3eeb5e16d652 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 9 Jul 2021 21:11:48 -0400 Subject: [PATCH 1/5] refresh devices on changes --- MultiSoundChanger.xcodeproj/project.pbxproj | 46 ++++++++++++++++--- .../Classes/ApplicationController.swift | 20 ++++++++ .../Sources/Classes/AudioManager.swift | 2 +- .../Sources/Classes/StatusBarController.swift | 3 +- 4 files changed, 63 insertions(+), 8 deletions(-) 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..121f89d 100644 --- a/MultiSoundChanger/Sources/Classes/ApplicationController.swift +++ b/MultiSoundChanger/Sources/Classes/ApplicationController.swift @@ -22,9 +22,29 @@ final class ApplicationControllerImp: ApplicationController { private lazy var mediaManager: MediaManager = MediaManagerImpl(delegate: self) private lazy var statusBarController: StatusBarController = StatusBarControllerImpl(audioManager: audioManager) + 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..c379a29 100644 --- a/MultiSoundChanger/Sources/Classes/AudioManager.swift +++ b/MultiSoundChanger/Sources/Classes/AudioManager.swift @@ -29,7 +29,7 @@ final class AudioManagerImpl: AudioManager { private let audio: Audio = AudioImpl() private let devices: [AudioDeviceID: String]? private var selectedDevice: AudioDeviceID? - + init() { devices = audio.getOutputDevices() printDevices() diff --git a/MultiSoundChanger/Sources/Classes/StatusBarController.swift b/MultiSoundChanger/Sources/Classes/StatusBarController.swift index a23eb41..2f17a06 100644 --- a/MultiSoundChanger/Sources/Classes/StatusBarController.swift +++ b/MultiSoundChanger/Sources/Classes/StatusBarController.swift @@ -51,7 +51,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) From 61bf12e95e014ae1680a897ef10d746a43e0b2e0 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 9 Jul 2021 21:31:04 -0400 Subject: [PATCH 2/5] use simply CA for iterating devices This gives us the same order every time --- .../Classes/ApplicationController.swift | 4 +++- .../Sources/Classes/AudioManager.swift | 2 -- .../Sources/Classes/StatusBarController.swift | 19 ++++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/MultiSoundChanger/Sources/Classes/ApplicationController.swift b/MultiSoundChanger/Sources/Classes/ApplicationController.swift index 121f89d..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,9 +19,10 @@ 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] = [] diff --git a/MultiSoundChanger/Sources/Classes/AudioManager.swift b/MultiSoundChanger/Sources/Classes/AudioManager.swift index c379a29..3a2e892 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) diff --git a/MultiSoundChanger/Sources/Classes/StatusBarController.swift b/MultiSoundChanger/Sources/Classes/StatusBarController.swift index 2f17a06..4fbf773 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 @@ -136,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) From c2e89567dffef0eb31c9991115f980893eee43d2 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 9 Jul 2021 21:31:31 -0400 Subject: [PATCH 3/5] refresh devices in audio manager --- .../Sources/Classes/AudioManager.swift | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/MultiSoundChanger/Sources/Classes/AudioManager.swift b/MultiSoundChanger/Sources/Classes/AudioManager.swift index 3a2e892..8a9b0dc 100644 --- a/MultiSoundChanger/Sources/Classes/AudioManager.swift +++ b/MultiSoundChanger/Sources/Classes/AudioManager.swift @@ -25,14 +25,24 @@ 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() } - + + func refreshDevices() { + self.devices = audio.getOutputDevices() + self.printDevices() + } + func getDefaultOutputDevice() -> AudioDeviceID { return audio.getDefaultOutputDevice() } From b7dee9600214469b8d5edf99beb7b5ff8e558ead Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 9 Jul 2021 21:37:05 -0400 Subject: [PATCH 4/5] remove observer on deinit --- MultiSoundChanger/Sources/Classes/AudioManager.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MultiSoundChanger/Sources/Classes/AudioManager.swift b/MultiSoundChanger/Sources/Classes/AudioManager.swift index 8a9b0dc..fadaf65 100644 --- a/MultiSoundChanger/Sources/Classes/AudioManager.swift +++ b/MultiSoundChanger/Sources/Classes/AudioManager.swift @@ -37,6 +37,10 @@ final class AudioManagerImpl: AudioManager { init() { refreshDevices() } + + deinit { + NotificationCenter.default.removeObserver(observer) + } func refreshDevices() { self.devices = audio.getOutputDevices() From 12f81af808f58fd71afbb4d254bac8443d236020 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Wed, 21 Jul 2021 20:28:39 -0400 Subject: [PATCH 5/5] change default system output + input if needed --- .../Sources/Classes/StatusBarController.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/MultiSoundChanger/Sources/Classes/StatusBarController.swift b/MultiSoundChanger/Sources/Classes/StatusBarController.swift index 4fbf773..35fdf48 100644 --- a/MultiSoundChanger/Sources/Classes/StatusBarController.swift +++ b/MultiSoundChanger/Sources/Classes/StatusBarController.swift @@ -162,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 }