diff --git a/native/app/Embedded/eqMac.driver/Contents/MacOS/eqMac b/native/app/Embedded/eqMac.driver/Contents/MacOS/eqMac index bd553694..90f99ed4 100755 Binary files a/native/app/Embedded/eqMac.driver/Contents/MacOS/eqMac and b/native/app/Embedded/eqMac.driver/Contents/MacOS/eqMac differ diff --git a/native/app/Embedded/ui.zip b/native/app/Embedded/ui.zip index 11034a48..84248fd0 100644 Binary files a/native/app/Embedded/ui.zip and b/native/app/Embedded/ui.zip differ diff --git a/native/app/Source/Application.swift b/native/app/Source/Application.swift index fc689add..65bc4e7e 100644 --- a/native/app/Source/Application.swift +++ b/native/app/Source/Application.swift @@ -18,6 +18,7 @@ import SwiftyJSON import ServiceManagement import ReSwift import Sparkle +import AudioKit enum VolumeChangeDirection: String { case UP = "UP" @@ -71,6 +72,14 @@ class Application { static public func start () { + // AudioKit Engine fucks with Driver installation, eqMac doesn't use it's engine anyway + try? AudioKit.stop() + try? AudioKit.shutdown() + AKSettings.audioInputEnabled = false + AKSettings.enableRouteChangeHandling = false + AKSettings.notificationsEnabled = false + AudioKit.engine.stop() + setupSettings() if (!Constants.DEBUG) { @@ -117,7 +126,7 @@ class Application { ) { install in if install { Driver.install(started: { - UI.showLoadingWindow("Installing eqMac audio driver") + UI.showLoadingWindow("Installing eqMac audio driver\nIf this process takes too long, please restart your Mac") }) { success in if (success) { UI.hideLoadingWindow() @@ -131,16 +140,16 @@ class Application { quit() } } - } else if (Driver.isOutdated) { + } else if (Driver.isOutdated && Driver.skipCurrentVersion) { Alert.confirm( title: "Audio Driver Update", message: "There is an optional Audio Driver update that should improve user experience. \nIn order to update eqMac will ask for your System Password. \nPlease close any apps playing audio (Spotify, YouTube etc.) otherwise installation might fail.", okText: "Update Driver", - cancelText: "Skip update" + cancelText: "Skip Driver update" ) { update in if update { Driver.install(started: { - UI.showLoadingWindow("Updating eqMac audio driver") + UI.showLoadingWindow("Updating eqMac audio driver\nIf this process takes too long, please restart your Mac") }) { success in if (success) { UI.hideLoadingWindow() @@ -150,6 +159,7 @@ class Application { } } } else { + Driver.skipCurrentVersion = true completion() } } @@ -161,7 +171,7 @@ class Application { private static func driverFailedToInstallPrompt () { UI.hideLoadingWindow() Alert.confirm( - title: "Driver failed to install", message: "Unfortunately the audio driver has failed to install. You can restart eqMac and try again or quit.", okText: "Try again", cancelText: "Quit") { restart in + title: "Driver failed to install", message: "Unfortunately the audio driver has failed to install. You can restart eqMac and try again or quit. Alternatively, please try to restart your Mac and running eqMac again.", okText: "Try again", cancelText: "Quit") { restart in if restart { return self.restart() } else { @@ -258,7 +268,7 @@ class Application { try! AudioDeviceEvents.recreateEventEmitters([.isAliveChanged, .volumeChanged, .nominalSampleRateChanged]) self.setupDriverDeviceEvents() Utilities.delay(500) { - createAudioPipeline() + createAudioPipeline() } } } @@ -288,12 +298,12 @@ class Application { return } let gain = Double(Driver.device!.virtualMasterVolume(direction: .playback)!) - Console.log(gain) if (gain <= 1 && gain != Application.store.state.effects.volume.gain) { Application.dispatchAction(VolumeAction.setGain(gain, false)) } + } - + AudioDeviceEvents.on(.muteChanged, onDevice: Driver.device!) { if (ignoreNextDriverMuteEvent) { ignoreNextDriverMuteEvent = false @@ -513,29 +523,30 @@ class Application { updater.checkForUpdates(nil) } - static func reinstallDriver (_ completion: @escaping () -> Void) { + static func reinstallDriver (_ completion: @escaping (Bool) -> Void) { Alert.confirm( title: "Audio Driver Reinstall", - message: "\nIn order to reinstall the driver eqMac we will ask for your System Password. \nPlease close any apps playing audio (Spotify, YouTube etc.) otherwise installation might fail.", + message: "\nIn order to reinstall the driver eqMac we will ask for your System Password. \nPlease close any apps playing audio (Spotify, YouTube etc.) otherwise installation might fail. eqMac will restart after this.", cancelText: "Cancel" ) { reinstall in if reinstall { Driver.install(started: { - UI.showLoadingWindow("Installing eqMac audio driver") self.stopListeners() self.stopEngines() self.switchBackToLastKnownDevice() + UI.close() + Utilities.delay(100) { UI.showLoadingWindow("Reinstalling eqMac driver\nIf this process takes too long, please restart your Mac") } }) { success in if (success) { UI.hideLoadingWindow() - setupAudio() - completion() + completion(true) + Application.restart() } else { driverFailedToInstallPrompt() } } } else { - completion() + completion(false) } } @@ -546,8 +557,8 @@ class Application { self.stopListeners() self.stopEngines() self.switchBackToLastKnownDevice() - UI.hide() - Utilities.delay(100) { UI.showLoadingWindow("Uninstalling eqMac") } + UI.close() + Utilities.delay(100) { UI.showLoadingWindow("Uninstalling eqMac\nIf this process takes too long, please restart your Mac") } }) { success in completion(success) if (success) { diff --git a/native/app/Source/ApplicationDataBus.swift b/native/app/Source/ApplicationDataBus.swift index 33c9ca9a..2375d3d6 100644 --- a/native/app/Source/ApplicationDataBus.swift +++ b/native/app/Source/ApplicationDataBus.swift @@ -23,7 +23,8 @@ class ApplicationDataBus: DataBus { return [ "name": host.localizedName as AnyObject, "model": Sysctl.model as String, - "version": Bundle.main.infoDictionary?["CFBundleVersion"] as Any + "version": Bundle.main.infoDictionary?["CFBundleVersion"] as Any, + "driverVersion": Driver.lastInstalledVersion ] } @@ -59,6 +60,17 @@ class ApplicationDataBus: DataBus { return nil } + self.on(.GET, "/driver/reinstall/available") { _, res in + return "Yes" + } + + self.on(.GET, "/driver/reinstall") { _, res in + Application.reinstallDriver { success in + res.send([ "reinstalled": success ]) + } + return nil + } + self.on(.GET, "/update") { _, _ in Application.checkForUpdates() return "Checking for updates." diff --git a/native/app/Source/Audio/AudioDeviceEvents.swift b/native/app/Source/Audio/AudioDeviceEvents.swift index 3445c2bd..1e0fbb03 100644 --- a/native/app/Source/Audio/AudioDeviceEvents.swift +++ b/native/app/Source/Audio/AudioDeviceEvents.swift @@ -36,10 +36,10 @@ enum AudioDeviceEventType { class AudioDeviceEvents: EventSubscriber { static var events = AudioDeviceEvents() + static var listeners: [EmitterKit.EventListener] = [] as! [EmitterKit.EventListener] var hashValue: Int = 1 - static var listeners: [EmitterKit.EventListener] = [] as! [EmitterKit.EventListener] - + var subscribed = false // Per Device let isJackConnectedChangedEvent = EmitterKit.Event() let isRunningSomewhereChangedEvent = EmitterKit.Event() @@ -63,17 +63,34 @@ class AudioDeviceEvents: EventSubscriber { let inputChangedEvent = EmitterKit.Event() let systemDeviceChangedEvent = EmitterKit.Event() - init () { - NotificationCenter.defaultCenter.subscribe( - self, - eventType: AudioHardwareEvent.self, - dispatchQueue: DispatchQueue.main - ) - NotificationCenter.defaultCenter.subscribe( - self, - eventType: AudioDeviceEvent.self, - dispatchQueue: DispatchQueue.main - ) + func subscribe () { + if !subscribed { + NotificationCenter.defaultCenter.subscribe( + self, + eventType: AudioHardwareEvent.self, + dispatchQueue: DispatchQueue.main + ) + NotificationCenter.defaultCenter.subscribe( + self, + eventType: AudioDeviceEvent.self, + dispatchQueue: DispatchQueue.main + ) + subscribed = true + } + } + + func unsubscribe () { + NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioHardwareEvent.self) + NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioDeviceEvent.self) + subscribed = false + } + + static func subscribe () { + events.subscribe() + } + + static func unsubscribe () { + events.unsubscribe() } internal func eventReceiver(_ event: AMCoreAudio.Event) { @@ -102,6 +119,7 @@ class AudioDeviceEvents: EventSubscriber { } static func recreateEventEmitters(_ eventsToRecreate: [AudioDeviceEventType]) throws { + subscribe() for event in eventsToRecreate { switch event { case .isAliveChanged: @@ -184,6 +202,7 @@ class AudioDeviceEvents: EventSubscriber { @discardableResult static func on (_ event: AudioDeviceEventType, retain: Bool = true, _ handler: @escaping (AudioDevice) -> Void) -> EmitterKit.EventListener { + events.subscribe() let emitter = getEventEmitterFromEventType(event) let listener: EmitterKit.EventListener = emitter.on(handler) if (retain) { @@ -199,6 +218,7 @@ class AudioDeviceEvents: EventSubscriber { @discardableResult static func once (_ event: AudioDeviceEventType, _ handler: @escaping (AudioDevice) -> Void) -> EmitterKit.EventListener { + events.subscribe() let emitter = getEventEmitterFromEventType(event) let listener: EmitterKit.EventListener = emitter.once(handler: handler) return listener @@ -223,12 +243,14 @@ class AudioDeviceEvents: EventSubscriber { } static func start () { + subscribe() for listener in listeners { listener.isListening = true } } static func stop () { + unsubscribe() for listener in listeners { listener.isListening = false } diff --git a/native/app/Source/Audio/Sources/System/Driver.swift b/native/app/Source/Audio/Sources/System/Driver.swift index 0df18185..b968d16f 100644 --- a/native/app/Source/Audio/Sources/System/Driver.swift +++ b/native/app/Source/Audio/Sources/System/Driver.swift @@ -64,6 +64,24 @@ class Driver { return info["CFBundleVersion"] as! String } + static var lastSkippedDriverVersion: String? { + get { + return Storage[.lastSkippedDriverVersion] + } + set { + Storage[.lastSkippedDriverVersion] = newValue + } + } + + static var skipCurrentVersion: Bool { + get { + return lastSkippedDriverVersion == bundledVersion + } + set { + lastSkippedDriverVersion = newValue ? bundledVersion : nil + } + } + static var isOutdated: Bool { get { return bundledVersion != lastInstalledVersion diff --git a/native/app/Source/Helpers/Storage.swift b/native/app/Source/Helpers/Storage.swift index 143774cc..61b866e6 100644 --- a/native/app/Source/Helpers/Storage.swift +++ b/native/app/Source/Helpers/Storage.swift @@ -27,6 +27,7 @@ extension DefaultsKeys { // Effects - Equalizer - Advanced static let advancedEqualizerPresets = DefaultsKey<[AdvancedEqualizerPreset]?>("advancedEqualizerPresets") static let lastInstalledDriverVersion = DefaultsKey("lastInstalledDriverVersion") + static let lastSkippedDriverVersion = DefaultsKey("lastSkippedDriverVersion") } let Storage = Defaults diff --git a/native/app/Source/UI/UI.swift b/native/app/Source/UI/UI.swift index 00dda10f..80fd4ed6 100644 --- a/native/app/Source/UI/UI.swift +++ b/native/app/Source/UI/UI.swift @@ -219,6 +219,10 @@ class UI: StoreSubscriber { load() } + static func reload () { + viewController.webView.reload() + } + func setupBridge () { bridge = Bridge(webView: UI.viewController.webView) } @@ -251,7 +255,7 @@ class UI: StoreSubscriber { } private func load () { - Networking.isReachable(UI.domain) { reachable in + remoteIsReachable() { reachable in if reachable { Console.log("Loading Remote UI") UI.viewController.load(Constants.UI_ENDPOINT_URL) @@ -263,7 +267,33 @@ class UI: StoreSubscriber { UI.viewController.load(url) } } + } + + private func remoteIsReachable (_ completion: @escaping (Bool) -> Void) { + var returned = false + AF.request(Constants.UI_ENDPOINT_URL) + .responseData { response in + switch response.result { + case .success: + if !returned { + returned = true + completion(true) + } + case .failure: + Console.log("Failed to load \(Constants.UI_ENDPOINT_URL)", response) + if !returned { + returned = true + completion(false) + } + } + } + Utilities.delay(1000) { + if (!returned) { + returned = true + completion(false) + } + } } private func cacheRemote () { diff --git a/native/app/Source/UI/Window.swift b/native/app/Source/UI/Window.swift index 692bd886..a341cd75 100644 --- a/native/app/Source/UI/Window.swift +++ b/native/app/Source/UI/Window.swift @@ -12,8 +12,8 @@ import Cocoa class Window: NSWindow, NSWindowDelegate { override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) + self.isOneShot = false - self.titleVisibility = .hidden self.titlebarAppearsTransparent = true self.isMovableByWindowBackground = true diff --git a/native/app/Supporting Files/Info.plist b/native/app/Supporting Files/Info.plist index 06e8fc48..73ea352e 100644 --- a/native/app/Supporting Files/Info.plist +++ b/native/app/Supporting Files/Info.plist @@ -32,6 +32,8 @@ NSAllowsArbitraryLoadsInWebContent + NSAllowsLocalNetworking + NSExceptionDomains diff --git a/native/app/eqMac.xcodeproj/project.pbxproj b/native/app/eqMac.xcodeproj/project.pbxproj index 24156ea1..b18196e6 100644 --- a/native/app/eqMac.xcodeproj/project.pbxproj +++ b/native/app/eqMac.xcodeproj/project.pbxproj @@ -1038,7 +1038,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 0.2.3; + CURRENT_PROJECT_VERSION = 0.3; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = JZA6C97KJA; ENABLE_HARDENED_RUNTIME = YES; @@ -1074,7 +1074,7 @@ "$(PROJECT_DIR)/Source/Vendor", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = v0.2.3; + MARKETING_VERSION = v0.3; PRODUCT_BUNDLE_IDENTIFIER = com.bitgapp.eqmac; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1098,7 +1098,7 @@ CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 0.2.3; + CURRENT_PROJECT_VERSION = 0.3; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = JZA6C97KJA; ENABLE_HARDENED_RUNTIME = YES; @@ -1134,7 +1134,7 @@ "$(PROJECT_DIR)/Source/Vendor", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = v0.2.3; + MARKETING_VERSION = v0.3; PRODUCT_BUNDLE_IDENTIFIER = com.bitgapp.eqmac; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/native/driver/Source/DeviceClients/EQM_Clients.cpp b/native/driver/Source/DeviceClients/EQM_Clients.cpp index 91cfa3d8..d512e502 100644 --- a/native/driver/Source/DeviceClients/EQM_Clients.cpp +++ b/native/driver/Source/DeviceClients/EQM_Clients.cpp @@ -72,6 +72,7 @@ void EQM_Clients::RemoveClient(const UInt32 inClientID) { mEQMAppClientID = -1; } + } #pragma mark IO Status diff --git a/ui/package.json b/ui/package.json index 44c8fe78..faf1a4a1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "eqmac", - "version": "1.1.0", + "version": "1.2.0", "scripts": { "start": "ng serve", "build": "rm -rf dist/ && ng build --prod && cd dist/ && zip -r -D ui.zip * -x '*.DS_Store' && cp ui.zip ../../native/app/Embedded", diff --git a/ui/src/app/sections/help/help.component.html b/ui/src/app/sections/help/help.component.html index 22055f08..1caa7970 100644 --- a/ui/src/app/sections/help/help.component.html +++ b/ui/src/app/sections/help/help.component.html @@ -2,4 +2,5 @@
UI Version: {{uiVersion}} App Version: {{info.version}} + Driver Version: {{info.driverVersion || '1.0.0'}}
diff --git a/ui/src/app/sections/settings/settings.component.ts b/ui/src/app/sections/settings/settings.component.ts index 3f1d03c8..2f569916 100644 --- a/ui/src/app/sections/settings/settings.component.ts +++ b/ui/src/app/sections/settings/settings.component.ts @@ -65,6 +65,14 @@ export class SettingsComponent implements OnInit { label: 'Check for Updates', action: this.update.bind(this) } + + reinstallDriverOption: ButtonOption = { + key: 'reinstall-driver', + type: 'button', + label: 'Reinstall Driver', + action: this.reinstallDriver.bind(this) + } + settings: Options = [ [ this.updateOption @@ -86,12 +94,24 @@ export class SettingsComponent implements OnInit { public app: ApplicationService, public dialog: MatDialog, private ui: UIService - ) {} + ) { + this.getDriverReinstallAvailable() + } ngOnInit () { this.sync() } + async getDriverReinstallAvailable () { + if (await this.app.getDriverReinstallAvailable()) { + this.settings[3].unshift(this.reinstallDriverOption) + } + } + + async reinstallDriver () { + this.app.reinstallDriver() + } + async sync () { await Promise.all([ this.syncSettings() diff --git a/ui/src/app/services/app.service.ts b/ui/src/app/services/app.service.ts index 39a0c75a..39dfca68 100644 --- a/ui/src/app/services/app.service.ts +++ b/ui/src/app/services/app.service.ts @@ -5,6 +5,7 @@ export interface MacInfo { name: string model: string version: string + driverVersion?: string } @Injectable({ providedIn: 'root' @@ -33,4 +34,20 @@ export class ApplicationService extends DataService { update () { return this.request({ method: 'GET', endpoint: '/update' }) } + + async getDriverReinstallAvailable () { + return new Promise(async (resolve) => { + setTimeout(() => resolve(false), 1000) + try { + await this.request({ method: 'GET', endpoint: '/driver/reinstall/available' }) + resolve(true) + } catch (err) { + resolve(false) + } + }) + } + + reinstallDriver () { + return this.request({ method: 'GET', endpoint: '/driver/reinstall' }) + } }