mirror of
https://github.com/MonitorControl/MonitorControl.git
synced 2026-05-16 06:05:52 -06:00
495 lines
22 KiB
Swift
495 lines
22 KiB
Swift
import Cocoa
|
|
import Foundation
|
|
import MediaKeyTap
|
|
import os.log
|
|
import Preferences
|
|
import SimplyCoreAudio
|
|
|
|
var app: AppDelegate!
|
|
let prefs = UserDefaults.standard
|
|
|
|
@NSApplicationMain
|
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
|
@IBOutlet var statusMenu: NSMenu!
|
|
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
var monitorItems: [NSMenuItem] = []
|
|
var mediaKeyTap: MediaKeyTap?
|
|
var keyRepeatTimers: [MediaKey: Timer] = [:]
|
|
let coreAudio = SimplyCoreAudio()
|
|
var accessibilityObserver: NSObjectProtocol!
|
|
var reconfigureID: Int = 0 // dispatched reconfigure command ID
|
|
var sleepID: Int = 0 // Don't reconfigure display as the system or display is sleeping or wake just recently.
|
|
lazy var preferencesWindowController: PreferencesWindowController = {
|
|
let storyboard = NSStoryboard(name: "Main", bundle: Bundle.main)
|
|
let mainPrefsVc = storyboard.instantiateController(withIdentifier: "MainPrefsVC") as? MainPrefsViewController
|
|
let displayPrefsVc = storyboard.instantiateController(withIdentifier: "DisplayPrefsVC") as? DisplayPrefsViewController
|
|
let advancedPrefsVc = storyboard.instantiateController(withIdentifier: "AdvancedPrefsVC") as? AdvancedPrefsViewController
|
|
return PreferencesWindowController(
|
|
preferencePanes: [
|
|
mainPrefsVc!,
|
|
displayPrefsVc!,
|
|
advancedPrefsVc!,
|
|
],
|
|
animated: true // causes glitchy animations
|
|
)
|
|
}()
|
|
|
|
func applicationDidFinishLaunching(_: Notification) {
|
|
app = self
|
|
self.subscribeEventListeners()
|
|
self.setDefaultPrefs()
|
|
self.updateMediaKeyTap()
|
|
if #available(macOS 11.0, *) {
|
|
self.statusItem.button?.image = NSImage(systemSymbolName: "sun.max", accessibilityDescription: "MonitorControl")
|
|
} else {
|
|
self.statusItem.button?.image = NSImage(named: "status")
|
|
}
|
|
self.statusItem.isVisible = prefs.bool(forKey: Utils.PrefKeys.hideMenuIcon.rawValue) ? false : true
|
|
self.statusItem.menu = self.statusMenu
|
|
self.checkPermissions()
|
|
CGDisplayRegisterReconfigurationCallback({ _, _, _ in app.displayReconfigured() }, nil)
|
|
self.updateDisplays(firstrun: true)
|
|
}
|
|
|
|
func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool {
|
|
self.prefsClicked(self)
|
|
return true
|
|
}
|
|
|
|
func applicationWillTerminate(_: Notification) {
|
|
os_log("Goodbye!", type: .info)
|
|
DisplayManager.shared.resetSwBrightness()
|
|
self.statusItem.isVisible = true
|
|
}
|
|
|
|
@IBAction func quitClicked(_: AnyObject) {
|
|
NSApplication.shared.terminate(self)
|
|
}
|
|
|
|
@IBAction func prefsClicked(_: AnyObject) {
|
|
self.preferencesWindowController.show()
|
|
}
|
|
|
|
func setDefaultPrefs() {
|
|
if !prefs.bool(forKey: Utils.PrefKeys.appAlreadyLaunched.rawValue) {
|
|
prefs.set(true, forKey: Utils.PrefKeys.appAlreadyLaunched.rawValue)
|
|
prefs.set(false, forKey: Utils.PrefKeys.showContrast.rawValue)
|
|
prefs.set(true, forKey: Utils.PrefKeys.showVolume.rawValue)
|
|
prefs.set(false, forKey: Utils.PrefKeys.lowerSwAfterBrightness.rawValue)
|
|
prefs.set(true, forKey: Utils.PrefKeys.fallbackSw.rawValue)
|
|
prefs.set(false, forKey: Utils.PrefKeys.hideMenuIcon.rawValue)
|
|
}
|
|
}
|
|
|
|
func clearDisplays() {
|
|
if self.statusMenu.items.count > 2 {
|
|
var items: [NSMenuItem] = []
|
|
for i in 0 ..< self.statusMenu.items.count - 2 {
|
|
items.append(self.statusMenu.items[i])
|
|
}
|
|
|
|
for item in items {
|
|
self.statusMenu.removeItem(item)
|
|
}
|
|
}
|
|
|
|
self.monitorItems = []
|
|
DisplayManager.shared.clearDisplays()
|
|
}
|
|
|
|
func getDisplayName(displayID: CGDirectDisplayID) -> String {
|
|
let defaultName: String = NSLocalizedString("Unknown", comment: "Unknown display name") // + String(CGDisplaySerialNumber(displayID))
|
|
if #available(macOS 11.0, *) {
|
|
if let dictionary = ((CoreDisplay_DisplayCreateInfoDictionary(displayID))?.takeRetainedValue() as NSDictionary?), let nameList = dictionary["DisplayProductName"] as? [String: String], var name = nameList[Locale.current.identifier] ?? nameList["en_US"] ?? nameList.first?.value {
|
|
if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
|
|
let mirroredDisplayID = CGDisplayMirrorsDisplay(displayID)
|
|
if mirroredDisplayID != 0, let dictionary = ((CoreDisplay_DisplayCreateInfoDictionary(mirroredDisplayID))?.takeRetainedValue() as NSDictionary?), let nameList = dictionary["DisplayProductName"] as? [String: String], let mirroredName = nameList[Locale.current.identifier] ?? nameList["en_US"] ?? nameList.first?.value {
|
|
name.append("~" + mirroredName)
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
}
|
|
if let screen = NSScreen.getByDisplayID(displayID: displayID) {
|
|
if #available(OSX 10.15, *) {
|
|
return screen.localizedName
|
|
} else {
|
|
return screen.displayName ?? defaultName
|
|
}
|
|
}
|
|
return defaultName
|
|
}
|
|
|
|
func updateAVServices() {
|
|
if Arm64DDCUtils.isArm64 {
|
|
os_log("arm64 AVService update requested", type: .info)
|
|
var displayIDs: [CGDirectDisplayID] = []
|
|
for externalDisplay in DisplayManager.shared.getExternalDisplays() {
|
|
displayIDs.append(externalDisplay.identifier)
|
|
}
|
|
for serviceMatch in Arm64DDCUtils.getServiceMatches(displayIDs: displayIDs) {
|
|
for externalDisplay in DisplayManager.shared.getExternalDisplays() where externalDisplay.identifier == serviceMatch.displayID && serviceMatch.service != nil {
|
|
externalDisplay.arm64avService = serviceMatch.service
|
|
os_log("Display service match successful for display %{public}@", type: .info, String(serviceMatch.displayID))
|
|
// if Arm64DDCUtils.read(service: externalDisplay.arm64avService, command: UInt8(0xF1)) != nil {
|
|
// externalDisplay.arm64ddc = true
|
|
// }
|
|
if !serviceMatch.isDiscouraged {
|
|
externalDisplay.arm64ddc = true
|
|
}
|
|
}
|
|
}
|
|
os_log("AVService update done", type: .info)
|
|
}
|
|
}
|
|
|
|
func displayReconfigured() {
|
|
if self.sleepID == 0 {
|
|
self.reconfigureID += 1
|
|
let dispatchedReconfigureID = self.reconfigureID
|
|
os_log("Display to be reconfigured with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID))
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
|
self.updateDisplays(dispatchedReconfigureID: dispatchedReconfigureID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateMenus(controllableExternalDisplays: [ExternalDisplay]) {
|
|
if controllableExternalDisplays.count == 0 {
|
|
let item = NSMenuItem()
|
|
item.title = NSLocalizedString("No supported display found", comment: "Shown in menu")
|
|
item.isEnabled = false
|
|
self.monitorItems.append(item)
|
|
self.statusMenu.insertItem(item, at: 0)
|
|
self.statusMenu.insertItem(NSMenuItem.separator(), at: 1)
|
|
} else {
|
|
for display in controllableExternalDisplays {
|
|
os_log("Supported display found: %{public}@", type: .info, "\(display.name) (Vendor: \(display.vendorNumber ?? 0), Model: \(display.modelNumber ?? 0))")
|
|
let asSubmenu: Bool = controllableExternalDisplays.count > 2 ? true : false
|
|
if asSubmenu {
|
|
self.statusMenu.insertItem(NSMenuItem.separator(), at: 0)
|
|
}
|
|
self.addDisplayToMenu(display: display, asSubMenu: asSubmenu)
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateDisplays(dispatchedReconfigureID: Int = 0, firstrun: Bool = false) {
|
|
guard self.sleepID == 0, dispatchedReconfigureID == self.reconfigureID else {
|
|
return
|
|
}
|
|
os_log("Request for updateDisplay with reconfigreID %{public}@", type: .info, String(dispatchedReconfigureID))
|
|
self.reconfigureID = 0
|
|
self.clearDisplays()
|
|
var onlineDisplayIDs = [CGDirectDisplayID](repeating: 0, count: 10)
|
|
var displayCount: UInt32 = 0
|
|
guard CGGetOnlineDisplayList(10, &onlineDisplayIDs, &displayCount) == .success else {
|
|
os_log("Unable to get display list.", type: .info)
|
|
return
|
|
}
|
|
for onlineDisplayID in onlineDisplayIDs where onlineDisplayID != 0 {
|
|
let name = getDisplayName(displayID: onlineDisplayID)
|
|
let id = onlineDisplayID
|
|
let vendorNumber = CGDisplayVendorNumber(onlineDisplayID)
|
|
let modelNumber = CGDisplayVendorNumber(onlineDisplayID)
|
|
let display: Display
|
|
var isVirtual: Bool = false
|
|
if #available(macOS 11.0, *) {
|
|
if let dictionary = ((CoreDisplay_DisplayCreateInfoDictionary(onlineDisplayID))?.takeRetainedValue() as NSDictionary?) {
|
|
let isVirtualDevice = dictionary["kCGDisplayIsVirtualDevice"] as? Bool
|
|
let displayIsAirplay = dictionary["kCGDisplayIsAirPlay"] as? Bool
|
|
if isVirtualDevice ?? displayIsAirplay ?? false {
|
|
isVirtual = true
|
|
}
|
|
}
|
|
}
|
|
if CGDisplayIsBuiltin(onlineDisplayID) != 0 {
|
|
display = InternalDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual)
|
|
} else {
|
|
display = ExternalDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual)
|
|
}
|
|
DisplayManager.shared.addDisplay(display: display)
|
|
}
|
|
if !firstrun {
|
|
DisplayManager.shared.resetSwBrightness()
|
|
} else {
|
|
DisplayManager.shared.restoreSwBrightness()
|
|
}
|
|
self.updateAVServices()
|
|
var controllableExternalDisplays: [ExternalDisplay] = []
|
|
if prefs.bool(forKey: Utils.PrefKeys.fallbackSw.rawValue) {
|
|
controllableExternalDisplays = DisplayManager.shared.getExternalDisplays()
|
|
} else {
|
|
controllableExternalDisplays = DisplayManager.shared.getDdcCapableDisplays()
|
|
}
|
|
self.updateMenus(controllableExternalDisplays: controllableExternalDisplays)
|
|
}
|
|
|
|
private func addDisplayToMenu(display: ExternalDisplay, asSubMenu: Bool) {
|
|
if !asSubMenu {
|
|
self.statusMenu.insertItem(NSMenuItem.separator(), at: 0)
|
|
}
|
|
let monitorSubMenu: NSMenu = asSubMenu ? NSMenu() : self.statusMenu
|
|
|
|
if !display.isSw() {
|
|
if prefs.bool(forKey: Utils.PrefKeys.showVolume.rawValue) {
|
|
let volumeSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, forDisplay: display, command: .audioSpeakerVolume, title: NSLocalizedString("Volume", comment: "Shown in menu"))
|
|
display.volumeSliderHandler = volumeSliderHandler
|
|
}
|
|
if prefs.bool(forKey: Utils.PrefKeys.showContrast.rawValue) {
|
|
let contrastSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, forDisplay: display, command: .contrast, title: NSLocalizedString("Contrast", comment: "Shown in menu"))
|
|
display.contrastSliderHandler = contrastSliderHandler
|
|
}
|
|
}
|
|
let brightnessSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, forDisplay: display, command: .brightness, title: NSLocalizedString("Brightness", comment: "Shown in menu"))
|
|
display.brightnessSliderHandler = brightnessSliderHandler
|
|
|
|
let monitorMenuItem = NSMenuItem()
|
|
if asSubMenu {
|
|
monitorMenuItem.title = "\(display.getFriendlyName())"
|
|
monitorMenuItem.submenu = monitorSubMenu
|
|
} else {
|
|
let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.systemGray, .font: NSFont.boldSystemFont(ofSize: 12)]
|
|
monitorMenuItem.attributedTitle = NSAttributedString(string: "\(display.getFriendlyName())", attributes: attrs)
|
|
}
|
|
|
|
self.monitorItems.append(monitorMenuItem)
|
|
self.statusMenu.insertItem(monitorMenuItem, at: 0)
|
|
}
|
|
|
|
private func checkPermissions() {
|
|
let permissionsRequired: Bool = prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue) != Utils.ListenForKeys.none.rawValue
|
|
if !Utils.readPrivileges(prompt: false) && permissionsRequired {
|
|
Utils.acquirePrivileges()
|
|
}
|
|
}
|
|
|
|
private func subscribeEventListeners() {
|
|
NotificationCenter.default.addObserver(self, selector: #selector(handleListenForChanged), name: .listenFor, object: nil) // subscribe KeyTap event listeners
|
|
NotificationCenter.default.addObserver(self, selector: #selector(handleShowContrastChanged), name: .showContrast, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(handleShowVolumeChanged), name: .showVolume, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(handleFallbackSwChanged), name: .fallbackSw, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(handleFriendlyNameChanged), name: .friendlyName, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(handlePreferenceReset), name: .preferenceReset, object: nil)
|
|
NotificationCenter.default.addObserver(self, selector: #selector(audioDeviceChanged), name: Notification.Name.defaultOutputDeviceChanged, object: nil) // subscribe Audio output detector (SimplyCoreAudio)
|
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.screensDidSleepNotification, object: nil) // sleep and wake listeners
|
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotofication), name: NSWorkspace.screensDidWakeNotification, object: nil)
|
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.willSleepNotification, object: nil)
|
|
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotofication), name: NSWorkspace.didWakeNotification, object: nil)
|
|
_ = DistributedNotificationCenter.default().addObserver(forName: .accessibilityApi, object: nil, queue: nil) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
self.updateMediaKeyTap() // listen for accessibility status changes
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func sleepNotification() {
|
|
self.sleepID += 1
|
|
os_log("Sleeping with sleep %{public}@", type: .info, String(self.sleepID))
|
|
}
|
|
|
|
@objc private func wakeNotofication() {
|
|
if self.sleepID != 0 {
|
|
os_log("Waking up from sleep %{public}@", type: .info, String(self.sleepID))
|
|
let dispatchedSleepID = self.sleepID
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 6.0) { // Some displays take time to recover...
|
|
self.soberNow(dispatchedSleepID: dispatchedSleepID)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func soberNow(dispatchedSleepID: Int) {
|
|
if self.sleepID == dispatchedSleepID {
|
|
os_log("Sober from sleep %{public}@", type: .info, String(self.sleepID))
|
|
self.sleepID = 0
|
|
self.updateDisplays()
|
|
}
|
|
}
|
|
|
|
private func oppositeMediaKey(mediaKey: MediaKey) -> MediaKey? {
|
|
if mediaKey == .brightnessUp {
|
|
return .brightnessDown
|
|
} else if mediaKey == .brightnessDown {
|
|
return .brightnessUp
|
|
} else if mediaKey == .volumeUp {
|
|
return .volumeDown
|
|
} else if mediaKey == .volumeDown {
|
|
return .volumeUp
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func handleOpenPrefPane(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) -> Bool {
|
|
guard let modifiers = modifiers else { return false }
|
|
if !(modifiers.contains(.option) && !modifiers.contains(.shift)) {
|
|
return false
|
|
}
|
|
if event?.keyRepeat == true {
|
|
return false
|
|
}
|
|
switch mediaKey {
|
|
case .brightnessUp, .brightnessDown:
|
|
NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Library/PreferencePanes/Displays.prefPane"))
|
|
case .mute, .volumeUp, .volumeDown:
|
|
NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Library/PreferencePanes/Sound.prefPane"))
|
|
default:
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
extension AppDelegate: MediaKeyTapDelegate {
|
|
func handle(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) {
|
|
guard self.sleepID == 0, self.reconfigureID == 0 else {
|
|
return
|
|
}
|
|
if self.handleOpenPrefPane(mediaKey: mediaKey, event: event, modifiers: modifiers) {
|
|
return
|
|
}
|
|
let isSmallIncrement = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.shift, .option])) ?? false
|
|
// control internal display when holding ctrl modifier
|
|
let isControlModifier = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.control])) ?? false
|
|
if isControlModifier, mediaKey == .brightnessUp || mediaKey == .brightnessDown {
|
|
if let internalDisplay = DisplayManager.shared.getBuiltInDisplay() as? InternalDisplay {
|
|
internalDisplay.stepBrightness(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
|
|
return
|
|
}
|
|
}
|
|
let oppositeKey: MediaKey? = self.oppositeMediaKey(mediaKey: mediaKey)
|
|
let isRepeat = event?.keyRepeat ?? false
|
|
// If the opposite key to the one being held has an active timer, cancel it - we'll be going in the opposite direction
|
|
if let oppositeKey = oppositeKey, let oppositeKeyTimer = self.keyRepeatTimers[oppositeKey], oppositeKeyTimer.isValid {
|
|
oppositeKeyTimer.invalidate()
|
|
} else if let mediaKeyTimer = self.keyRepeatTimers[mediaKey], mediaKeyTimer.isValid {
|
|
// If there's already an active timer for the key being held down, let it run rather than executing it again
|
|
if isRepeat {
|
|
return
|
|
}
|
|
mediaKeyTimer.invalidate()
|
|
}
|
|
self.sendDisplayCommand(mediaKey: mediaKey, isRepeat: isRepeat, isSmallIncrement: isSmallIncrement)
|
|
}
|
|
|
|
private func getAffectedDisplays() -> [Display]? {
|
|
var affectedDisplays: [Display]
|
|
let allDisplays = DisplayManager.shared.getAllDisplays()
|
|
guard let currentDisplay = DisplayManager.shared.getCurrentDisplay() else {
|
|
return nil
|
|
}
|
|
// let allDisplays = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? displays : [currentDisplay]
|
|
if prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) {
|
|
affectedDisplays = allDisplays
|
|
} else {
|
|
affectedDisplays = [currentDisplay]
|
|
if CGDisplayIsInHWMirrorSet(currentDisplay.identifier) != 0 || CGDisplayIsInMirrorSet(currentDisplay.identifier) != 0, CGDisplayMirrorsDisplay(currentDisplay.identifier) == 0 {
|
|
for display in allDisplays where CGDisplayMirrorsDisplay(display.identifier) == currentDisplay.identifier {
|
|
affectedDisplays.append(display)
|
|
}
|
|
}
|
|
}
|
|
return affectedDisplays
|
|
}
|
|
|
|
private func sendDisplayCommand(mediaKey: MediaKey, isRepeat: Bool, isSmallIncrement: Bool) {
|
|
guard self.sleepID == 0, self.reconfigureID == 0, let affectedDisplays = self.getAffectedDisplays() else {
|
|
return
|
|
}
|
|
let delay = isRepeat ? 0.05 : 0 // Introduce a small delay to handle the media key being held down
|
|
var isAnyDisplayInSwAfterBrightnessMode: Bool = false
|
|
for display in affectedDisplays where (display as? ExternalDisplay)?.isSwBrightnessNotDefault() ?? false {
|
|
isAnyDisplayInSwAfterBrightnessMode = true
|
|
}
|
|
self.keyRepeatTimers[mediaKey] = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { _ in
|
|
for display in affectedDisplays where display.isEnabled && !display.isVirtual {
|
|
switch mediaKey {
|
|
case .brightnessUp:
|
|
if !(isAnyDisplayInSwAfterBrightnessMode && !((display as? ExternalDisplay)?.isSwBrightnessNotDefault() ?? false)) {
|
|
display.stepBrightness(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
|
|
}
|
|
case .brightnessDown:
|
|
display.stepBrightness(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
|
|
case .mute:
|
|
// The mute key should not respond to press + hold
|
|
if !isRepeat {
|
|
// mute only matters for external displays
|
|
if let display = display as? ExternalDisplay {
|
|
display.toggleMute()
|
|
}
|
|
}
|
|
case .volumeUp, .volumeDown:
|
|
// volume only matters for external displays
|
|
if let display = display as? ExternalDisplay {
|
|
display.stepVolume(isUp: mediaKey == .volumeUp, isSmallIncrement: isSmallIncrement)
|
|
}
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
@objc func handleListenForChanged() {
|
|
self.checkPermissions()
|
|
self.updateMediaKeyTap()
|
|
}
|
|
|
|
@objc func handleShowContrastChanged() {
|
|
self.updateDisplays()
|
|
}
|
|
|
|
@objc func handleShowVolumeChanged() {
|
|
self.updateDisplays()
|
|
}
|
|
|
|
@objc func handleFallbackSwChanged() {
|
|
DisplayManager.shared.resetSwBrightness()
|
|
self.updateDisplays()
|
|
}
|
|
|
|
@objc func handleFriendlyNameChanged() {
|
|
self.updateDisplays()
|
|
}
|
|
|
|
@objc func handlePreferenceReset() {
|
|
self.setDefaultPrefs()
|
|
self.updateDisplays()
|
|
self.checkPermissions()
|
|
self.updateMediaKeyTap()
|
|
DisplayManager.shared.resetSwBrightness()
|
|
self.updateDisplays()
|
|
}
|
|
|
|
private func updateMediaKeyTap() {
|
|
var keys: [MediaKey]
|
|
switch prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue) {
|
|
case Utils.ListenForKeys.brightnessOnlyKeys.rawValue:
|
|
keys = [.brightnessUp, .brightnessDown]
|
|
case Utils.ListenForKeys.volumeOnlyKeys.rawValue:
|
|
keys = [.mute, .volumeUp, .volumeDown]
|
|
case Utils.ListenForKeys.none.rawValue:
|
|
keys = []
|
|
default:
|
|
keys = [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown]
|
|
}
|
|
if self.coreAudio.defaultOutputDevice?.canSetVirtualMasterVolume(scope: .output) == true { // Remove volume related keys.
|
|
let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute]
|
|
keys.removeAll { keysToDelete.contains($0) }
|
|
}
|
|
self.mediaKeyTap?.stop()
|
|
// returning an empty array listens for all mediakeys in MediaKeyTap
|
|
if keys.count > 0 {
|
|
self.mediaKeyTap = MediaKeyTap(delegate: self, for: keys, observeBuiltIn: true)
|
|
self.mediaKeyTap?.start()
|
|
}
|
|
}
|
|
|
|
@objc private func audioDeviceChanged() {
|
|
#if DEBUG
|
|
if let defaultDevice = self.coreAudio.defaultOutputDevice {
|
|
os_log("Default output device changed to “%{public}@”.", type: .info, defaultDevice.name)
|
|
os_log("Can device set its own volume? %{public}@", type: .info, defaultDevice.canSetVirtualMasterVolume(scope: .output).description)
|
|
}
|
|
#endif
|
|
self.updateMediaKeyTap()
|
|
}
|
|
}
|