mirror of
https://github.com/MonitorControl/MonitorControl.git
synced 2026-05-16 14:15:55 -06:00
Merge 3fbdc6353d into 3cfc40598a
This commit is contained in:
commit
9f2ea8a748
16 changed files with 543 additions and 13 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,4 +5,4 @@ Carthage
|
|||
.DS_Store
|
||||
|
||||
### Xcode ###
|
||||
xcuserdata/
|
||||
xcuserdata/build/
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
AACE5E2327050C63006C2A48 /* NSNotification+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AACE5E2227050C63006C2A48 /* NSNotification+Extension.swift */; };
|
||||
AAD7DD342CAFF3D90062822F /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = AAD7DD332CAFF3D90062822F /* Settings */; };
|
||||
AADB625A26BC196900DFFAA5 /* DisplayServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AADB625926BC196900DFFAA5 /* DisplayServices.framework */; };
|
||||
E1D2C3B52F10000100000001 /* EdgeScrollManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D2C3B42F10000100000001 /* EdgeScrollManager.swift */; };
|
||||
E1D2C3B72F10000100000001 /* MousePrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D2C3B62F10000100000001 /* MousePrefsViewController.swift */; };
|
||||
F01B0699228221B7008E64DB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F01B0680228221B6008E64DB /* Localizable.strings */; };
|
||||
F01B069F228221B7008E64DB /* SliderHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01B068F228221B7008E64DB /* SliderHandler.swift */; };
|
||||
F03A8DF21FFBAA6F0034DC27 /* OtherDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A8DF11FFBAA6F0034DC27 /* OtherDisplay.swift */; };
|
||||
|
|
@ -166,6 +168,8 @@
|
|||
B7FA437E2AC5857A00A94C01 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = "<group>"; };
|
||||
B7FA437F2AC5857A00A94C01 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InternetAccessPolicy.strings; sourceTree = "<group>"; };
|
||||
B7FA43802AC5857A00A94C01 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
E1D2C3B42F10000100000001 /* EdgeScrollManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeScrollManager.swift; sourceTree = "<group>"; };
|
||||
E1D2C3B62F10000100000001 /* MousePrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MousePrefsViewController.swift; sourceTree = "<group>"; };
|
||||
F01B0682228221B6008E64DB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F01B0685228221B6008E64DB /* Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
F01B0686228221B6008E64DB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
|
@ -314,6 +318,7 @@
|
|||
6C85EFD922C941B000227EA1 /* DisplayManager.swift */,
|
||||
AA25F6D626E68C160087F3A2 /* MediaKeyTapManager.swift */,
|
||||
AA44E70627038F7F00E06865 /* KeyboardShortcutsManager.swift */,
|
||||
E1D2C3B42F10000100000001 /* EdgeScrollManager.swift */,
|
||||
AA16139A26BE772E00DCF027 /* Arm64DDC.swift */,
|
||||
AA4398A826DD55DA00943F16 /* IntelDDC.swift */,
|
||||
FE4E0895249D584C003A50BB /* OSDUtils.swift */,
|
||||
|
|
@ -356,6 +361,7 @@
|
|||
F0445D3720023E710025AE82 /* MainPrefsViewController.swift */,
|
||||
AA25F6CE26E680510087F3A2 /* MenuslidersPrefsViewController.swift */,
|
||||
AA25F6D026E681D30087F3A2 /* KeyboardPrefsViewController.swift */,
|
||||
E1D2C3B62F10000100000001 /* MousePrefsViewController.swift */,
|
||||
AA062E8926C9A039007E628C /* DisplaysPrefsViewController.swift */,
|
||||
AA062E8D26CA7BE5007E628C /* DisplaysPrefsCellView.swift */,
|
||||
AA665A5C26C5892800FEF2C1 /* AboutPrefsViewController.swift */,
|
||||
|
|
@ -628,6 +634,7 @@
|
|||
6CBFE27C23DB27A200D1BC41 /* AppleDisplay.swift in Sources */,
|
||||
AA062E8E26CA7BE5007E628C /* DisplaysPrefsCellView.swift in Sources */,
|
||||
AA25F6D726E68C160087F3A2 /* MediaKeyTapManager.swift in Sources */,
|
||||
E1D2C3B52F10000100000001 /* EdgeScrollManager.swift in Sources */,
|
||||
FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */,
|
||||
6CBFE27A23DB266000D1BC41 /* Display.swift in Sources */,
|
||||
AA44E70727038F7F00E06865 /* KeyboardShortcutsManager.swift in Sources */,
|
||||
|
|
@ -640,6 +647,7 @@
|
|||
28D1DDF2227FBE71004CB494 /* NSScreen+Extension.swift in Sources */,
|
||||
AA99521726FE25AB00612E07 /* AppDelegate.swift in Sources */,
|
||||
AA25F6D126E681D30087F3A2 /* KeyboardPrefsViewController.swift in Sources */,
|
||||
E1D2C3B72F10000100000001 /* MousePrefsViewController.swift in Sources */,
|
||||
F01B069F228221B7008E64DB /* SliderHandler.swift in Sources */,
|
||||
AACE5E2327050C63006C2A48 /* NSNotification+Extension.swift in Sources */,
|
||||
AA25F6CF26E680510087F3A2 /* MenuslidersPrefsViewController.swift in Sources */,
|
||||
|
|
@ -862,6 +870,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = MonitorControl/MonitorControlDebug.entitlements;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
|
|
@ -869,7 +878,7 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 7100;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 299YSU96J7;
|
||||
DEVELOPMENT_TEAM = HNDG94D2RC;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(PROJECT_DIR)/**",
|
||||
|
|
@ -900,13 +909,14 @@
|
|||
buildSettings = {
|
||||
ARCHS = "$(ARCHS_STANDARD)";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 7100;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 299YSU96J7;
|
||||
DEVELOPMENT_TEAM = HNDG94D2RC;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(PROJECT_DIR)/**",
|
||||
|
|
@ -946,7 +956,7 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 7100;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 299YSU96J7;
|
||||
DEVELOPMENT_TEAM = HNDG94D2RC;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = MonitorControlHelper/Info.plist;
|
||||
|
|
@ -977,7 +987,7 @@
|
|||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 7100;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 299YSU96J7;
|
||||
DEVELOPMENT_TEAM = HNDG94D2RC;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = MonitorControlHelper/Info.plist;
|
||||
|
|
|
|||
|
|
@ -87,6 +87,18 @@ enum PrefKey: String {
|
|||
// Sliders for multiple displays
|
||||
case multiSliders
|
||||
|
||||
// Mouse wheel action on the left screen edge
|
||||
case edgeScrollLeftAction
|
||||
|
||||
// Mouse wheel action on the right screen edge
|
||||
case edgeScrollRightAction
|
||||
|
||||
// Mouse wheel edge control precision
|
||||
case edgeScrollPrecision
|
||||
|
||||
// Play feedback sound when controlling volume from the screen edge
|
||||
case edgeScrollVolumeSoundFeedback
|
||||
|
||||
/* -- Display specific settings */
|
||||
|
||||
// Enable mute DDC for display
|
||||
|
|
@ -213,3 +225,25 @@ enum KeyboardVolume: Int {
|
|||
case both = 2
|
||||
case disabled = 3
|
||||
}
|
||||
|
||||
enum EdgeScrollAction: Int, CaseIterable {
|
||||
case disabled = 0
|
||||
case brightness = 1
|
||||
case volume = 2
|
||||
}
|
||||
|
||||
enum EdgeScrollPrecision: Int, CaseIterable {
|
||||
case standard = 0
|
||||
case fine = 1
|
||||
case veryFine = 2
|
||||
case coarse = 3
|
||||
|
||||
var step: Float {
|
||||
switch self {
|
||||
case .standard: return 0.02
|
||||
case .fine: return 0.01
|
||||
case .veryFine: return 0.005
|
||||
case .coarse: return 0.05
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ extension Settings.PaneIdentifier {
|
|||
static let main = Self("Main")
|
||||
static let menusliders = Self("Menu & Sliders")
|
||||
static let keyboard = Self("Keyboard")
|
||||
static let mouse = Self("Mouse")
|
||||
static let displays = Self("Displays")
|
||||
static let about = Self("About")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7141</string>
|
||||
<string>7148</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,20 @@ class Display: Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
func adjustBrightness(by delta: Float) {
|
||||
guard !self.readPrefAsBool(key: .unavailableDDC, for: .brightness) else {
|
||||
return
|
||||
}
|
||||
let value = max(0, min(1, self.getBrightness() + delta))
|
||||
if self.setBrightness(value) {
|
||||
OSDUtils.showOsdProgress(displayID: self.identifier, command: .brightness, value: value)
|
||||
if let slider = self.sliderHandler[.brightness] {
|
||||
slider.setValue(value, displayID: self.identifier)
|
||||
self.brightnessSyncSourceValue = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setBrightness(_ to: Float = -1, slow: Bool = false) -> Bool {
|
||||
if !prefs.bool(forKey: PrefKey.disableSmoothBrightness.rawValue) {
|
||||
return self.setSmoothBrightness(to, slow: slow)
|
||||
|
|
|
|||
|
|
@ -165,14 +165,32 @@ class OtherDisplay: Display {
|
|||
(command == .audioSpeakerVolume && self.readPrefAsBool(key: .enableMuteUnmute) && self.readPrefAsInt(for: .audioMuteScreenBlank) == 1) ? 0 : self.readPrefAsFloat(for: command)
|
||||
}
|
||||
|
||||
func canAdjustVolume() -> Bool {
|
||||
!self.isSw() && !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume)
|
||||
}
|
||||
|
||||
func stepVolume(isUp: Bool, isSmallIncrement: Bool) {
|
||||
guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
|
||||
guard self.canAdjustVolume() else {
|
||||
OSDUtils.showOsdVolumeDisabled(displayID: self.identifier)
|
||||
return
|
||||
}
|
||||
let currentValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
|
||||
var muteValue: Int?
|
||||
let volumeOSDValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
|
||||
self.setVolume(volumeOSDValue, roundChiclet: !isSmallIncrement)
|
||||
}
|
||||
|
||||
func adjustVolume(by delta: Float, forceOsd: Bool = false) {
|
||||
guard self.canAdjustVolume() else {
|
||||
OSDUtils.showOsdVolumeDisabled(displayID: self.identifier)
|
||||
return
|
||||
}
|
||||
let currentValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
|
||||
let volumeOSDValue = max(0, min(1, currentValue + delta))
|
||||
self.setVolume(volumeOSDValue, roundChiclet: false, forceOsd: forceOsd)
|
||||
}
|
||||
|
||||
private func setVolume(_ volumeOSDValue: Float, roundChiclet: Bool, forceOsd: Bool = false) {
|
||||
var muteValue: Int?
|
||||
if self.readPrefAsInt(for: .audioMuteScreenBlank) == 1, volumeOSDValue > 0 {
|
||||
muteValue = 2
|
||||
} else if self.readPrefAsInt(for: .audioMuteScreenBlank) != 1, volumeOSDValue == 0 {
|
||||
|
|
@ -188,8 +206,12 @@ class OtherDisplay: Display {
|
|||
self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
|
||||
}
|
||||
}
|
||||
if !self.readPrefAsBool(key: .hideOsd) {
|
||||
OSDUtils.showOsd(displayID: self.identifier, command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: !isSmallIncrement)
|
||||
if forceOsd || !self.readPrefAsBool(key: .hideOsd) {
|
||||
if roundChiclet {
|
||||
OSDUtils.showOsd(displayID: self.identifier, command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: true)
|
||||
} else {
|
||||
OSDUtils.showOsdProgress(displayID: self.identifier, command: .audioSpeakerVolume, value: volumeOSDValue)
|
||||
}
|
||||
}
|
||||
if !isAlreadySet {
|
||||
self.savePref(volumeOSDValue, for: .audioSpeakerVolume)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
}()
|
||||
var mediaKeyTap = MediaKeyTapManager()
|
||||
var keyboardShortcuts = KeyboardShortcutsManager()
|
||||
var edgeScrollManager = EdgeScrollManager()
|
||||
let coreAudio = SimplyCoreAudio()
|
||||
var accessibilityObserver: NSObjectProtocol!
|
||||
var statusItemObserver: NSObjectProtocol!
|
||||
|
|
@ -43,6 +44,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
mainPrefsVc!,
|
||||
menuslidersPrefsVc!,
|
||||
keyboardPrefsVc!,
|
||||
mousePrefsVc,
|
||||
displaysPrefsVc!,
|
||||
aboutPrefsVc!,
|
||||
],
|
||||
|
|
@ -62,6 +64,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
self.setPrefsBuildNumber()
|
||||
self.setDefaultPrefs()
|
||||
self.setMenu()
|
||||
self.edgeScrollManager.update()
|
||||
CGDisplayRegisterReconfigurationCallback({ _, _, _ in app.displayReconfigured() }, nil)
|
||||
self.configure(firstrun: true)
|
||||
DisplayManager.shared.createGammaActivityEnforcer()
|
||||
|
|
@ -112,6 +115,22 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
// Only settings that are not false, 0 or "" by default are set here. Assumes pre-wiped database.
|
||||
prefs.set(true, forKey: PrefKey.appAlreadyLaunched.rawValue)
|
||||
prefs.set(true, forKey: PrefKey.SUEnableAutomaticChecks.rawValue)
|
||||
prefs.set(EdgeScrollAction.brightness.rawValue, forKey: PrefKey.edgeScrollLeftAction.rawValue)
|
||||
prefs.set(EdgeScrollAction.volume.rawValue, forKey: PrefKey.edgeScrollRightAction.rawValue)
|
||||
prefs.set(EdgeScrollPrecision.standard.rawValue, forKey: PrefKey.edgeScrollPrecision.rawValue)
|
||||
prefs.set(true, forKey: PrefKey.edgeScrollVolumeSoundFeedback.rawValue)
|
||||
}
|
||||
if prefs.object(forKey: PrefKey.edgeScrollLeftAction.rawValue) == nil {
|
||||
prefs.set(EdgeScrollAction.brightness.rawValue, forKey: PrefKey.edgeScrollLeftAction.rawValue)
|
||||
}
|
||||
if prefs.object(forKey: PrefKey.edgeScrollRightAction.rawValue) == nil {
|
||||
prefs.set(EdgeScrollAction.volume.rawValue, forKey: PrefKey.edgeScrollRightAction.rawValue)
|
||||
}
|
||||
if prefs.object(forKey: PrefKey.edgeScrollPrecision.rawValue) == nil {
|
||||
prefs.set(EdgeScrollPrecision.standard.rawValue, forKey: PrefKey.edgeScrollPrecision.rawValue)
|
||||
}
|
||||
if prefs.object(forKey: PrefKey.edgeScrollVolumeSoundFeedback.rawValue) == nil {
|
||||
prefs.set(true, forKey: PrefKey.edgeScrollVolumeSoundFeedback.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +180,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
}
|
||||
|
||||
func checkPermissions(firstAsk: Bool = false) {
|
||||
let permissionsRequired: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) || [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue))
|
||||
let edgeScrollPermissionsRequired = EdgeScrollAction(rawValue: prefs.integer(forKey: PrefKey.edgeScrollLeftAction.rawValue)) != .disabled || EdgeScrollAction(rawValue: prefs.integer(forKey: PrefKey.edgeScrollRightAction.rawValue)) != .disabled
|
||||
let permissionsRequired: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) || [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) || edgeScrollPermissionsRequired
|
||||
if !MediaKeyTapManager.readPrivileges(prompt: false), permissionsRequired {
|
||||
MediaKeyTapManager.acquirePrivileges(firstAsk: firstAsk)
|
||||
}
|
||||
|
|
@ -174,7 +194,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotification), 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.wakeNotification), name: NSWorkspace.didWakeNotification, object: nil)
|
||||
_ = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: NSNotification.Name.accessibilityApi.rawValue), object: nil, queue: nil) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.updateMediaKeyTap() } } // listen for accessibility status changes
|
||||
_ = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: NSNotification.Name.accessibilityApi.rawValue), object: nil, queue: nil) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.updateMediaKeyTap(); self.edgeScrollManager.update() } } // listen for accessibility status changes
|
||||
self.statusItemObserver = statusItem.observe(\.isVisible, options: [.old, .new]) { _, _ in self.statusItemVisibilityChanged() }
|
||||
}
|
||||
|
||||
|
|
@ -281,6 +301,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
self.setDefaultPrefs()
|
||||
self.checkPermissions()
|
||||
self.updateMediaKeyTap()
|
||||
self.edgeScrollManager.update()
|
||||
self.configure(firstrun: true)
|
||||
}
|
||||
|
||||
|
|
|
|||
202
MonitorControl/Support/EdgeScrollManager.swift
Normal file
202
MonitorControl/Support/EdgeScrollManager.swift
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
|
||||
|
||||
import Cocoa
|
||||
import CoreGraphics
|
||||
import os.log
|
||||
import SimplyCoreAudio
|
||||
|
||||
private enum EdgeScrollSide {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
private func edgeScrollEventTapCallback(proxy _: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
|
||||
guard let refcon = refcon else {
|
||||
return Unmanaged.passUnretained(event)
|
||||
}
|
||||
let manager = Unmanaged<EdgeScrollManager>.fromOpaque(refcon).takeUnretainedValue()
|
||||
return manager.handleEventTap(type: type, event: event)
|
||||
}
|
||||
|
||||
class EdgeScrollManager {
|
||||
private let edgeActivationWidth: CGFloat = 6
|
||||
private var eventTap: CFMachPort?
|
||||
private var runLoopSource: CFRunLoopSource?
|
||||
private var globalScrollMonitor: Any?
|
||||
private var localScrollMonitor: Any?
|
||||
private var preciseScrollRemainder: CGFloat = 0
|
||||
private let volumeSoundFeedbackInterval: TimeInterval = 0.12
|
||||
private var lastVolumeSoundFeedbackTime: TimeInterval = 0
|
||||
|
||||
func update() {
|
||||
self.stop()
|
||||
guard self.isEnabled else {
|
||||
return
|
||||
}
|
||||
self.startEventTap()
|
||||
if self.eventTap == nil {
|
||||
self.startEventMonitors()
|
||||
}
|
||||
}
|
||||
|
||||
private var isEnabled: Bool {
|
||||
self.action(for: .left) != .disabled || self.action(for: .right) != .disabled
|
||||
}
|
||||
|
||||
private func stop() {
|
||||
if let eventTap = eventTap {
|
||||
CGEvent.tapEnable(tap: eventTap, enable: false)
|
||||
}
|
||||
if let runLoopSource = runLoopSource {
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
|
||||
}
|
||||
if let globalScrollMonitor = globalScrollMonitor {
|
||||
NSEvent.removeMonitor(globalScrollMonitor)
|
||||
}
|
||||
if let localScrollMonitor = localScrollMonitor {
|
||||
NSEvent.removeMonitor(localScrollMonitor)
|
||||
}
|
||||
self.eventTap = nil
|
||||
self.runLoopSource = nil
|
||||
self.globalScrollMonitor = nil
|
||||
self.localScrollMonitor = nil
|
||||
self.preciseScrollRemainder = 0
|
||||
}
|
||||
|
||||
private func startEventTap() {
|
||||
let eventMask = CGEventMask(1 << CGEventType.scrollWheel.rawValue)
|
||||
guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: eventMask, callback: edgeScrollEventTapCallback, userInfo: Unmanaged.passUnretained(self).toOpaque()) else {
|
||||
os_log("Edge scroll event tap unavailable. Falling back to event monitors.", type: .info)
|
||||
return
|
||||
}
|
||||
self.eventTap = eventTap
|
||||
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
|
||||
self.runLoopSource = runLoopSource
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
|
||||
CGEvent.tapEnable(tap: eventTap, enable: true)
|
||||
}
|
||||
|
||||
private func startEventMonitors() {
|
||||
self.globalScrollMonitor = NSEvent.addGlobalMonitorForEvents(matching: .scrollWheel) { [weak self] event in
|
||||
_ = self?.handleScroll(event)
|
||||
}
|
||||
self.localScrollMonitor = NSEvent.addLocalMonitorForEvents(matching: .scrollWheel) { [weak self] event in
|
||||
if self?.handleScroll(event) == true {
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func handleEventTap(type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
|
||||
if type == .tapDisabledByTimeout || type == .tapDisabledByUserInput {
|
||||
if let eventTap = self.eventTap {
|
||||
CGEvent.tapEnable(tap: eventTap, enable: true)
|
||||
}
|
||||
return Unmanaged.passUnretained(event)
|
||||
}
|
||||
guard type == .scrollWheel, let nsEvent = NSEvent(cgEvent: event) else {
|
||||
return Unmanaged.passUnretained(event)
|
||||
}
|
||||
return self.handleScroll(nsEvent) ? nil : Unmanaged.passUnretained(event)
|
||||
}
|
||||
|
||||
private func handleScroll(_ event: NSEvent) -> Bool {
|
||||
guard app.sleepID == 0, app.reconfigureID == 0, let target = self.targetForMouseLocation(), target.display.readPrefAsBool(key: .isDisabled) == false else {
|
||||
return false
|
||||
}
|
||||
let selectedAction = self.action(for: target.side)
|
||||
guard selectedAction != .disabled else {
|
||||
return false
|
||||
}
|
||||
let stepCount = self.stepCount(from: event)
|
||||
guard stepCount != 0 else {
|
||||
return true
|
||||
}
|
||||
let delta = self.precision.step * Float(stepCount)
|
||||
switch selectedAction {
|
||||
case .brightness:
|
||||
target.display.adjustBrightness(by: delta)
|
||||
case .volume:
|
||||
if !self.adjustSystemVolume(by: delta, displayID: target.display.identifier) {
|
||||
return false
|
||||
}
|
||||
case .disabled:
|
||||
break
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func adjustSystemVolume(by delta: Float, displayID: CGDirectDisplayID) -> Bool {
|
||||
guard let defaultDevice = app.coreAudio.defaultOutputDevice,
|
||||
defaultDevice.canSetVirtualMainVolume(scope: .output),
|
||||
let currentVolume = defaultDevice.virtualMainVolume(scope: .output) else {
|
||||
OSDUtils.showOsdVolumeDisabled(displayID: displayID)
|
||||
return false
|
||||
}
|
||||
let nextVolume = max(0, min(1, currentVolume + Float32(delta)))
|
||||
guard defaultDevice.setVirtualMainVolume(nextVolume, scope: .output) else {
|
||||
OSDUtils.showOsdVolumeDisabled(displayID: displayID)
|
||||
return false
|
||||
}
|
||||
OSDUtils.showOsdProgress(displayID: displayID, command: .audioSpeakerVolume, value: Float(nextVolume))
|
||||
self.playVolumeChangedSoundIfNeeded()
|
||||
return true
|
||||
}
|
||||
|
||||
private func playVolumeChangedSoundIfNeeded() {
|
||||
guard prefs.bool(forKey: PrefKey.edgeScrollVolumeSoundFeedback.rawValue) else {
|
||||
return
|
||||
}
|
||||
let now = Date.timeIntervalSinceReferenceDate
|
||||
guard now - self.lastVolumeSoundFeedbackTime >= self.volumeSoundFeedbackInterval else {
|
||||
return
|
||||
}
|
||||
self.lastVolumeSoundFeedbackTime = now
|
||||
DispatchQueue.main.async {
|
||||
app.playVolumeChangedSound()
|
||||
}
|
||||
}
|
||||
|
||||
private func action(for side: EdgeScrollSide) -> EdgeScrollAction {
|
||||
let prefKey = side == .left ? PrefKey.edgeScrollLeftAction : PrefKey.edgeScrollRightAction
|
||||
return EdgeScrollAction(rawValue: prefs.integer(forKey: prefKey.rawValue)) ?? .disabled
|
||||
}
|
||||
|
||||
private var precision: EdgeScrollPrecision {
|
||||
EdgeScrollPrecision(rawValue: prefs.integer(forKey: PrefKey.edgeScrollPrecision.rawValue)) ?? .standard
|
||||
}
|
||||
|
||||
private func stepCount(from event: NSEvent) -> Int {
|
||||
let scrollDelta = event.scrollingDeltaY
|
||||
guard abs(scrollDelta) > 0.01 else {
|
||||
return 0
|
||||
}
|
||||
if event.hasPreciseScrollingDeltas {
|
||||
self.preciseScrollRemainder += scrollDelta / 12
|
||||
guard abs(self.preciseScrollRemainder) >= 1 else {
|
||||
return 0
|
||||
}
|
||||
let steps = max(-3, min(3, Int(self.preciseScrollRemainder.rounded(.towardZero))))
|
||||
self.preciseScrollRemainder -= CGFloat(steps)
|
||||
return steps
|
||||
}
|
||||
let steps = max(1, min(3, Int(abs(scrollDelta).rounded(.towardZero))))
|
||||
return scrollDelta > 0 ? steps : -steps
|
||||
}
|
||||
|
||||
private func targetForMouseLocation() -> (side: EdgeScrollSide, display: Display)? {
|
||||
let mouseLocation = NSEvent.mouseLocation
|
||||
guard let screen = NSScreen.screens.first(where: { NSMouseInRect(mouseLocation, $0.frame, false) }),
|
||||
let display = DisplayManager.shared.displays.first(where: { $0.identifier == screen.displayID }) else {
|
||||
return nil
|
||||
}
|
||||
if mouseLocation.x <= screen.frame.minX + self.edgeActivationWidth {
|
||||
return (.left, display)
|
||||
}
|
||||
if mouseLocation.x >= screen.frame.maxX - self.edgeActivationWidth {
|
||||
return (.right, display)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,10 @@ class OSDUtils: NSObject {
|
|||
manager.showImage(osdImage.rawValue, onDisplayID: displayID, priority: 0x1F4, msecUntilFade: 1000, filledChiclets: UInt32(filledChiclets), totalChiclets: UInt32(totalChiclets), locked: lock)
|
||||
}
|
||||
|
||||
static func showOsdProgress(displayID: CGDirectDisplayID, command: Command, value: Float, maxValue: Float = 1, lock: Bool = false) {
|
||||
self.showOsd(displayID: displayID, command: command, value: value * 64, maxValue: maxValue * 64, roundChiclet: false, lock: lock)
|
||||
}
|
||||
|
||||
static func showOsdVolumeDisabled(displayID: CGDirectDisplayID) {
|
||||
guard let manager = OSDManager.sharedManager() as? OSDManager else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -145,8 +145,41 @@
|
|||
/* Shown in menu */
|
||||
"Volume" = "Volume";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Volume feedback sound" = "Volume feedback sound";
|
||||
|
||||
/* Shown in the alert dialog */
|
||||
"Yes" = "Yes";
|
||||
|
||||
/* Shown in the alert dialog */
|
||||
"You need to enable MonitorControl in System Settings > Security and Privacy > Accessibility for the keyboard shortcuts to work" = "You need to enable MonitorControl in System Settings > Security and Privacy > Accessibility for the keyboard shortcuts to work";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Coarse (5%)" = "Coarse (5%)";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Disabled" = "Disabled";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Fine (1%)" = "Fine (1%)";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Left screen edge" = "Left screen edge";
|
||||
|
||||
/* Shown in the main prefs window */
|
||||
"Mouse" = "Mouse";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Move the pointer to a screen edge and use the scroll wheel to control the selected value on that screen." = "Move the pointer to a screen edge and use the scroll wheel to control the selected value on that screen.";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Right screen edge" = "Right screen edge";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Scroll wheel precision" = "Scroll wheel precision";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Standard (2%)" = "Standard (2%)";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Very fine (0.5%)" = "Very fine (0.5%)";
|
||||
|
|
|
|||
|
|
@ -147,8 +147,41 @@
|
|||
/* Shown in menu */
|
||||
"Volume" = "音量";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Volume feedback sound" = "音量反馈声音";
|
||||
|
||||
/* Shown in the alert dialog */
|
||||
"Yes" = "是";
|
||||
|
||||
/* Shown in the alert dialog */
|
||||
"You need to enable MonitorControl in System Settings > Security and Privacy > Accessibility for the keyboard shortcuts to work" = "您需要在「系统偏好设置」>「安全性与隐私」>「辅助功能」中启用MonitorControl让键盘快捷键生效";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Coarse (5%)" = "粗略 (5%)";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Disabled" = "关闭";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Fine (1%)" = "精细 (1%)";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Left screen edge" = "屏幕左边缘";
|
||||
|
||||
/* Shown in the main prefs window */
|
||||
"Mouse" = "鼠标";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Move the pointer to a screen edge and use the scroll wheel to control the selected value on that screen." = "将指针移到屏幕边缘,然后使用滚轮控制该屏幕上的选定项目。";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Right screen edge" = "屏幕右边缘";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Scroll wheel precision" = "滚轮调节精度";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Standard (2%)" = "标准 (2%)";
|
||||
|
||||
/* Shown in Mouse Settings */
|
||||
"Very fine (0.5%)" = "非常精细 (0.5%)";
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class MainPrefsViewController: NSViewController, SettingsPane {
|
|||
// Preload Display settings to some extent to properly set up size in orther that animation won't fail
|
||||
menuslidersPrefsVc?.view.layoutSubtreeIfNeeded()
|
||||
keyboardPrefsVc?.view.layoutSubtreeIfNeeded()
|
||||
mousePrefsVc.view.layoutSubtreeIfNeeded()
|
||||
displaysPrefsVc?.view.layoutSubtreeIfNeeded()
|
||||
aboutPrefsVc?.view.layoutSubtreeIfNeeded()
|
||||
self.updateGridLayout()
|
||||
|
|
@ -152,6 +153,7 @@ class MainPrefsViewController: NSViewController, SettingsPane {
|
|||
self.populateSettings()
|
||||
menuslidersPrefsVc?.populateSettings()
|
||||
keyboardPrefsVc?.populateSettings()
|
||||
mousePrefsVc.populateSettings()
|
||||
displaysPrefsVc?.populateSettings()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
|
||||
|
||||
import Cocoa
|
||||
import Settings
|
||||
|
||||
private final class MousePrefsRootView: NSView {
|
||||
override var intrinsicContentSize: NSSize {
|
||||
NSSize(width: 480, height: 220)
|
||||
}
|
||||
}
|
||||
|
||||
class MousePrefsViewController: NSViewController, SettingsPane {
|
||||
let paneIdentifier = Settings.PaneIdentifier.mouse
|
||||
let paneTitle: String = NSLocalizedString("Mouse", comment: "Shown in the main prefs window")
|
||||
|
||||
var toolbarItemIcon: NSImage {
|
||||
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
|
||||
return NSImage(systemSymbolName: "computermouse", accessibilityDescription: "Mouse") ?? NSImage(named: NSImage.infoName)!
|
||||
} else {
|
||||
return NSImage(named: NSImage.infoName)!
|
||||
}
|
||||
}
|
||||
|
||||
private let leftEdgeAction = NSPopUpButton()
|
||||
private let rightEdgeAction = NSPopUpButton()
|
||||
private let scrollPrecision = NSPopUpButton()
|
||||
private let volumeSoundFeedback = NSButton(checkboxWithTitle: "", target: nil, action: nil)
|
||||
|
||||
override func loadView() {
|
||||
self.view = MousePrefsRootView(frame: NSRect(x: 0, y: 0, width: 480, height: 220))
|
||||
self.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
self.view.widthAnchor.constraint(equalToConstant: 480),
|
||||
self.view.heightAnchor.constraint(equalToConstant: 220),
|
||||
])
|
||||
self.buildView()
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.populateSettings()
|
||||
}
|
||||
|
||||
func populateSettings() {
|
||||
self.leftEdgeAction.selectItem(withTag: prefs.integer(forKey: PrefKey.edgeScrollLeftAction.rawValue))
|
||||
self.rightEdgeAction.selectItem(withTag: prefs.integer(forKey: PrefKey.edgeScrollRightAction.rawValue))
|
||||
self.scrollPrecision.selectItem(withTag: prefs.integer(forKey: PrefKey.edgeScrollPrecision.rawValue))
|
||||
self.volumeSoundFeedback.state = prefs.bool(forKey: PrefKey.edgeScrollVolumeSoundFeedback.rawValue) ? .on : .off
|
||||
}
|
||||
|
||||
private func buildView() {
|
||||
self.configureActionPopUp(self.leftEdgeAction)
|
||||
self.configureActionPopUp(self.rightEdgeAction)
|
||||
self.configurePrecisionPopUp(self.scrollPrecision)
|
||||
|
||||
self.leftEdgeAction.target = self
|
||||
self.leftEdgeAction.action = #selector(self.leftEdgeActionChanged(_:))
|
||||
self.rightEdgeAction.target = self
|
||||
self.rightEdgeAction.action = #selector(self.rightEdgeActionChanged(_:))
|
||||
self.scrollPrecision.target = self
|
||||
self.scrollPrecision.action = #selector(self.scrollPrecisionChanged(_:))
|
||||
self.volumeSoundFeedback.target = self
|
||||
self.volumeSoundFeedback.action = #selector(self.volumeSoundFeedbackChanged(_:))
|
||||
self.volumeSoundFeedback.setAccessibilityLabel(NSLocalizedString("Volume feedback sound", comment: "Shown in Mouse Settings"))
|
||||
|
||||
let stack = NSStackView()
|
||||
stack.orientation = .vertical
|
||||
stack.alignment = .leading
|
||||
stack.spacing = 14
|
||||
stack.edgeInsets = NSEdgeInsets(top: 24, left: 28, bottom: 24, right: 28)
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.view.addSubview(stack)
|
||||
|
||||
stack.addArrangedSubview(self.makeRow(title: NSLocalizedString("Left screen edge", comment: "Shown in Mouse Settings"), control: self.leftEdgeAction))
|
||||
stack.addArrangedSubview(self.makeRow(title: NSLocalizedString("Right screen edge", comment: "Shown in Mouse Settings"), control: self.rightEdgeAction))
|
||||
stack.addArrangedSubview(self.makeRow(title: NSLocalizedString("Scroll wheel precision", comment: "Shown in Mouse Settings"), control: self.scrollPrecision))
|
||||
stack.addArrangedSubview(self.makeRow(title: NSLocalizedString("Volume feedback sound", comment: "Shown in Mouse Settings"), control: self.volumeSoundFeedback))
|
||||
|
||||
let infoLabel = NSTextField(labelWithString: NSLocalizedString("Move the pointer to a screen edge and use the scroll wheel to control the selected value on that screen.", comment: "Shown in Mouse Settings"))
|
||||
infoLabel.textColor = .secondaryLabelColor
|
||||
infoLabel.maximumNumberOfLines = 2
|
||||
infoLabel.lineBreakMode = .byWordWrapping
|
||||
infoLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
stack.addArrangedSubview(infoLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
stack.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
|
||||
stack.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
|
||||
stack.topAnchor.constraint(equalTo: self.view.topAnchor),
|
||||
stack.bottomAnchor.constraint(lessThanOrEqualTo: self.view.bottomAnchor),
|
||||
infoLabel.widthAnchor.constraint(equalToConstant: 420),
|
||||
])
|
||||
}
|
||||
|
||||
private func makeRow(title: String, control: NSView) -> NSStackView {
|
||||
let label = NSTextField(labelWithString: title)
|
||||
label.alignment = .right
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
control.translatesAutoresizingMaskIntoConstraints = false
|
||||
let row = NSStackView(views: [label, control])
|
||||
row.orientation = .horizontal
|
||||
row.alignment = .centerY
|
||||
row.spacing = 12
|
||||
NSLayoutConstraint.activate([
|
||||
label.widthAnchor.constraint(equalToConstant: 160),
|
||||
control.widthAnchor.constraint(equalToConstant: 190),
|
||||
])
|
||||
return row
|
||||
}
|
||||
|
||||
private func configureActionPopUp(_ popUpButton: NSPopUpButton) {
|
||||
popUpButton.removeAllItems()
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Disabled", comment: "Shown in Mouse Settings"), tag: EdgeScrollAction.disabled.rawValue)
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Brightness", comment: "Shown in Mouse Settings"), tag: EdgeScrollAction.brightness.rawValue)
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Volume", comment: "Shown in Mouse Settings"), tag: EdgeScrollAction.volume.rawValue)
|
||||
}
|
||||
|
||||
private func configurePrecisionPopUp(_ popUpButton: NSPopUpButton) {
|
||||
popUpButton.removeAllItems()
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Standard (2%)", comment: "Shown in Mouse Settings"), tag: EdgeScrollPrecision.standard.rawValue)
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Fine (1%)", comment: "Shown in Mouse Settings"), tag: EdgeScrollPrecision.fine.rawValue)
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Very fine (0.5%)", comment: "Shown in Mouse Settings"), tag: EdgeScrollPrecision.veryFine.rawValue)
|
||||
self.addItem(to: popUpButton, title: NSLocalizedString("Coarse (5%)", comment: "Shown in Mouse Settings"), tag: EdgeScrollPrecision.coarse.rawValue)
|
||||
}
|
||||
|
||||
private func addItem(to popUpButton: NSPopUpButton, title: String, tag: Int) {
|
||||
popUpButton.addItem(withTitle: title)
|
||||
popUpButton.lastItem?.tag = tag
|
||||
}
|
||||
|
||||
@objc private func leftEdgeActionChanged(_ sender: NSPopUpButton) {
|
||||
prefs.set(sender.selectedTag(), forKey: PrefKey.edgeScrollLeftAction.rawValue)
|
||||
self.edgeScrollSettingsChanged()
|
||||
}
|
||||
|
||||
@objc private func rightEdgeActionChanged(_ sender: NSPopUpButton) {
|
||||
prefs.set(sender.selectedTag(), forKey: PrefKey.edgeScrollRightAction.rawValue)
|
||||
self.edgeScrollSettingsChanged()
|
||||
}
|
||||
|
||||
@objc private func scrollPrecisionChanged(_ sender: NSPopUpButton) {
|
||||
prefs.set(sender.selectedTag(), forKey: PrefKey.edgeScrollPrecision.rawValue)
|
||||
self.edgeScrollSettingsChanged()
|
||||
}
|
||||
|
||||
@objc private func volumeSoundFeedbackChanged(_ sender: NSButton) {
|
||||
prefs.set(sender.state == .on, forKey: PrefKey.edgeScrollVolumeSoundFeedback.rawValue)
|
||||
}
|
||||
|
||||
private func edgeScrollSettingsChanged() {
|
||||
app.checkPermissions()
|
||||
app.edgeScrollManager.update()
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ let mainPrefsVc = storyboard.instantiateController(withIdentifier: "MainPrefsVC"
|
|||
let displaysPrefsVc = storyboard.instantiateController(withIdentifier: "DisplaysPrefsVC") as? DisplaysPrefsViewController
|
||||
let menuslidersPrefsVc = storyboard.instantiateController(withIdentifier: "MenuslidersPrefsVC") as? MenuslidersPrefsViewController
|
||||
let keyboardPrefsVc = storyboard.instantiateController(withIdentifier: "KeyboardPrefsVC") as? KeyboardPrefsViewController
|
||||
let mousePrefsVc = MousePrefsViewController()
|
||||
let aboutPrefsVc = storyboard.instantiateController(withIdentifier: "AboutPrefsVC") as? AboutPrefsViewController
|
||||
let onboardingVc = storyboard.instantiateController(withIdentifier: "onboardingViewController") as? NSWindowController
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>7141</string>
|
||||
<string>7148</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSBackgroundOnly</key>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue