feat: add preference to start at login (closes #159)
This commit is contained in:
1 parent
bddb6fa5bd
commit
982fe6cf3a
8 files changed
+115
-53
No files matched your search
@@ -13,7 +13,7 @@
|
||||
D04BA084CD1236EC78D90A01 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BACCBE5F97BE9B6CA645B /* Localizable.strings */; };
|
||||
D04BA100BD0F47828EB649FF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAAEC2847830A3991F8D1 /* InfoPlist.strings */; };
|
||||
D04BA14D93726795A6937832 /* LabelAndControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA2526DC6726E0F7ACF7C /* LabelAndControl.swift */; };
|
||||
D04BA15A1B0C4871EA7CB899 /* ShortcutsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACE22DC907F03D193075 /* ShortcutsTab.swift */; };
|
||||
D04BA15A1B0C4871EA7CB899 /* GeneralTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACE22DC907F03D193075 /* GeneralTab.swift */; };
|
||||
D04BA1B133D53572D7B312C2 /* CollectionViewItemFontIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA1DF8CAB2FAB7FE9244B /* CollectionViewItemFontIcon.swift */; };
|
||||
D04BA1CEC6B9C8945FEC8740 /* CollectionViewItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA258B56193958D60978A /* CollectionViewItemView.swift */; };
|
||||
D04BA26A691D56031FCCF00C /* Sysctl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8DB8AA7E5570DAC568A /* Sysctl.swift */; };
|
||||
@@ -148,7 +148,7 @@
|
||||
D04BACABD048E62EBE4576CC /* DebugProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugProfile.swift; sourceTree = "<group>"; };
|
||||
D04BACB97A5895839BCB14BD /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D04BACD976030676FD0761D5 /* Windows.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Windows.swift; sourceTree = "<group>"; };
|
||||
D04BACE22DC907F03D193075 /* ShortcutsTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShortcutsTab.swift; sourceTree = "<group>"; };
|
||||
D04BACE22DC907F03D193075 /* GeneralTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralTab.swift; sourceTree = "<group>"; };
|
||||
D04BACEE8D430B8CAAD8C4CD /* BoldLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoldLabel.swift; sourceTree = "<group>"; };
|
||||
D04BAD1297730B191E96E7FE /* CollectionViewItemTitle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewItemTitle.swift; sourceTree = "<group>"; };
|
||||
D04BAD241A6928F45355B315 /* es */ = {isa = PBXFileReference; fileEncoding = 2483028224; lastKnownFileType = text.plist.strings; name = es; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
@@ -310,7 +310,7 @@
|
||||
children = (
|
||||
D04BA64F1F344007EA13BA05 /* AppearanceTab.swift */,
|
||||
D04BA4A26987F67DD94C827F /* AboutTab.swift */,
|
||||
D04BACE22DC907F03D193075 /* ShortcutsTab.swift */,
|
||||
D04BACE22DC907F03D193075 /* GeneralTab.swift */,
|
||||
D04BAD60C97E609A759E721E /* UpdatesTab.swift */,
|
||||
);
|
||||
path = tabs;
|
||||
@@ -685,7 +685,7 @@
|
||||
D04BADBCF20CD72057E7CF09 /* TabViewItem.swift in Sources */,
|
||||
D04BA7BE7F3DD24D58ACE942 /* AppearanceTab.swift in Sources */,
|
||||
D04BAD1BE9DC22C48C53D195 /* AboutTab.swift in Sources */,
|
||||
D04BA15A1B0C4871EA7CB899 /* ShortcutsTab.swift in Sources */,
|
||||
D04BA15A1B0C4871EA7CB899 /* GeneralTab.swift in Sources */,
|
||||
D04BA8092885B40CE3527370 /* UpdatesTab.swift in Sources */,
|
||||
D04BAD451966B43720120D2E /* Menubar.swift in Sources */,
|
||||
D04BA2E64C59D96F6EB27D9D /* FeedbackWindow.swift in Sources */,
|
||||
|
||||
@@ -122,3 +122,42 @@ extension Array {
|
||||
group.wait()
|
||||
}
|
||||
}
|
||||
|
||||
// allow using a closure for NSControl action, instead of selector
|
||||
class SelectorWrapper<T> {
|
||||
let selector: Selector
|
||||
let closure: (T) -> Void
|
||||
|
||||
init(withClosure closure: @escaping (T) -> Void) {
|
||||
self.selector = #selector(callClosure)
|
||||
self.closure = closure
|
||||
}
|
||||
|
||||
@objc
|
||||
private func callClosure(sender: AnyObject) {
|
||||
closure(sender as! T)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var handle: Int = 0
|
||||
|
||||
typealias ActionClosure = (NSControl) -> Void
|
||||
|
||||
extension NSControl {
|
||||
var onAction: ActionClosure? {
|
||||
get {
|
||||
return nil
|
||||
}
|
||||
set {
|
||||
if let newValue = newValue {
|
||||
let selectorWrapper = SelectorWrapper<NSControl>(withClosure: newValue)
|
||||
objc_setAssociatedObject(self, &handle, selectorWrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
action = selectorWrapper.selector
|
||||
target = selectorWrapper
|
||||
} else {
|
||||
action = nil
|
||||
target = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class Preferences {
|
||||
"theme": MacroPreferences.themeList.keys.first!,
|
||||
"showOnScreen": MacroPreferences.showOnScreenList.keys.first!,
|
||||
"hideSpaceNumberLabels": false,
|
||||
"startAtLogin": true,
|
||||
]
|
||||
|
||||
// constant values
|
||||
@@ -39,6 +40,7 @@ class Preferences {
|
||||
static var tabKeyCode: UInt16 { UInt16(defaults.integer(forKey: "tabKeyCode")) }
|
||||
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(defaults.integer(forKey: "windowDisplayDelay")) }
|
||||
static var hideSpaceNumberLabels: Bool { defaults.bool(forKey: "hideSpaceNumberLabels") }
|
||||
static var startAtLogin: Bool { defaults.bool(forKey: "startAtLogin") }
|
||||
|
||||
// macro values
|
||||
static var theme: Theme { MacroPreferences.themeList[defaults.string(forKey: "theme")!]! }
|
||||
|
||||
@@ -9,6 +9,7 @@ class App: NSApplication, NSApplicationDelegate {
|
||||
static let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String
|
||||
static let licence = Bundle.main.object(forInfoDictionaryKey: "NSHumanReadableCopyright") as! String
|
||||
static let repository = "https://github.com/lwouis/alt-tab-macos"
|
||||
static let url = URL(fileURLWithPath: Bundle.main.bundlePath) as CFURL
|
||||
var statusItem: NSStatusItem?
|
||||
var thumbnailsPanel: ThumbnailsPanel?
|
||||
var preferencesWindow: PreferencesWindow?
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Cocoa
|
||||
|
||||
class LabelAndControl: NSObject {
|
||||
static var callbackTarget: PreferencesWindow!
|
||||
|
||||
static func makeLabelWithInput(_ labelText: String, _ rawName: String, _ width: CGFloat, _ suffixText: String? = nil, _ suffixUrl: String? = nil, _ validator: ((String) -> Bool)? = nil) -> [NSView] {
|
||||
let input = TextField(Preferences.getAsString(rawName)!)
|
||||
input.validationHandler = validator
|
||||
@@ -13,10 +11,10 @@ class LabelAndControl: NSObject {
|
||||
return [views[0], NSStackView(views: [views[1], makeSuffix(rawName, suffixText!, suffixUrl)])]
|
||||
}
|
||||
|
||||
static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String) -> [NSView] {
|
||||
static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String, extraAction: ActionClosure? = nil) -> [NSView] {
|
||||
let checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil)
|
||||
setControlValue(checkbox, Preferences.getAsString(rawName)!)
|
||||
return makeLabelWithProvidedControl(labelText, rawName, checkbox)
|
||||
return makeLabelWithProvidedControl(labelText, rawName, checkbox, extraAction: extraAction)
|
||||
}
|
||||
|
||||
static func makeLabelWithDropdown(_ labelText: String, _ rawName: String, _ values: [String], _ suffixText: String? = nil) -> [NSView] {
|
||||
@@ -40,19 +38,16 @@ class LabelAndControl: NSObject {
|
||||
return makeLabelWithProvidedControl(labelText, rawName, slider, suffixText)
|
||||
}
|
||||
|
||||
static func makeLabelWithProvidedControl(_ labelText: String?, _ rawName: String, _ control: NSControl, _ suffixText: String? = nil, _ suffixUrl: String? = nil) -> [NSView] {
|
||||
static func makeLabelWithProvidedControl(_ labelText: String?, _ rawName: String, _ control: NSControl, _ suffixText: String? = nil, _ suffixUrl: String? = nil, extraAction: ActionClosure? = nil) -> [NSView] {
|
||||
let label = makeLabel(labelText, rawName)
|
||||
control.identifier = NSUserInterfaceItemIdentifier(rawName)
|
||||
control.target = self
|
||||
control.action = #selector(controlWasChanged)
|
||||
control.onAction = {
|
||||
PreferencesWindow.controlWasChanged($0)
|
||||
extraAction?($0)
|
||||
}
|
||||
return [label, control, suffixText != nil ? makeSuffix(rawName, suffixText!, suffixUrl) : NSView()]
|
||||
}
|
||||
|
||||
@objc
|
||||
static func controlWasChanged(senderControl: NSControl) {
|
||||
callbackTarget.controlWasChanged(senderControl)
|
||||
}
|
||||
|
||||
private static func makeLabel(_ labelText: String?, _ rawName: String) -> NSTextField {
|
||||
let label = NSTextField(wrappingLabelWithString: labelText != nil ? labelText! + ": " : "")
|
||||
label.fit()
|
||||
|
||||
@@ -5,7 +5,6 @@ class PreferencesWindow: NSWindow {
|
||||
|
||||
override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) {
|
||||
super.init(contentRect: .zero, styleMask: style, backing: backingStoreType, defer: flag)
|
||||
LabelAndControl.callbackTarget = self
|
||||
setupWindow()
|
||||
setupTabViews()
|
||||
}
|
||||
@@ -15,7 +14,7 @@ class PreferencesWindow: NSWindow {
|
||||
makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
func controlWasChanged(_ senderControl: NSControl) {
|
||||
@objc static func controlWasChanged(_ senderControl: NSControl) {
|
||||
let newValue = LabelAndControl.getControlValue(senderControl)
|
||||
LabelAndControl.updateControlExtras(senderControl, newValue)
|
||||
Preferences.set(senderControl.identifier!.rawValue, newValue)
|
||||
@@ -31,7 +30,7 @@ class PreferencesWindow: NSWindow {
|
||||
private func setupTabViews() {
|
||||
contentViewController = tabViewController
|
||||
tabViewController.tabStyle = .toolbar
|
||||
tabViewController.addTabViewItem(ShortcutsTab.make())
|
||||
tabViewController.addTabViewItem(GeneralTab.make())
|
||||
tabViewController.addTabViewItem(AppearanceTab.make())
|
||||
tabViewController.addTabViewItem(UpdatesTab.make())
|
||||
tabViewController.addTabViewItem(AboutTab.make())
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import Cocoa
|
||||
|
||||
class GeneralTab {
|
||||
private static let rowHeight = CGFloat(22) // height of the "Tab key" input
|
||||
|
||||
static func make() -> NSTabViewItem {
|
||||
return TabViewItem.make(NSLocalizedString("General", comment: ""), NSImage.preferencesGeneralName, makeView())
|
||||
}
|
||||
|
||||
private static func makeView() -> NSGridView {
|
||||
// TODO: make the validators be a part of each Preference
|
||||
let tabKeyCodeValidator: ((String) -> Bool) = {
|
||||
guard let int = Int($0) else {
|
||||
return false
|
||||
}
|
||||
// non-special keys (mac & pc keyboards): https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||
var whitelistedKeycodes: [Int] = Array(0...53)
|
||||
whitelistedKeycodes.append(contentsOf: [65, 67, 69, 75, 76, 78, ])
|
||||
whitelistedKeycodes.append(contentsOf: Array(81...89))
|
||||
whitelistedKeycodes.append(contentsOf: [91, 92, 115, 116, 117, 119, 121])
|
||||
whitelistedKeycodes.append(contentsOf: Array(123...126))
|
||||
return whitelistedKeycodes.contains(int)
|
||||
}
|
||||
|
||||
let startAtLogin = LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Start at login", comment: ""), "startAtLogin", extraAction: startAtLoginCallback)
|
||||
let view = GridView.make([
|
||||
startAtLogin,
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Alt key", comment: ""), "metaKey", MacroPreferences.metaKeyList.values.map { $0.label }),
|
||||
LabelAndControl.makeLabelWithInput(NSLocalizedString("Tab key", comment: ""), "tabKeyCode", 33, NSLocalizedString("KeyCodes Reference", comment: ""), "https://eastmanreference.com/complete-list-of-applescript-key-codes", tabKeyCodeValidator),
|
||||
])
|
||||
view.column(at: 0).xPlacement = .trailing
|
||||
view.rowAlignment = .lastBaseline
|
||||
view.setRowsHeight(rowHeight)
|
||||
view.fit()
|
||||
setLoginItemIfCheckboxIsOn(startAtLogin[1] as! NSButton)
|
||||
return view
|
||||
}
|
||||
|
||||
private static func setLoginItemIfCheckboxIsOn(_ startAtLoginCheckbox: NSButton) {
|
||||
if startAtLoginCheckbox.state == .on {
|
||||
startAtLoginCallback(startAtLoginCheckbox)
|
||||
}
|
||||
}
|
||||
|
||||
// adding/removing login item depending on the checkbox state
|
||||
@available(OSX, deprecated: 10.11)
|
||||
@objc static func startAtLoginCallback(_ sender: NSControl) {
|
||||
let loginItems = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue()
|
||||
let loginItemsSnapshot = LSSharedFileListCopySnapshot(loginItems, nil).takeRetainedValue() as! [LSSharedFileListItem]
|
||||
if (sender as! NSButton).state == .on {
|
||||
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst.takeRetainedValue(), nil, nil, App.url, nil, nil)
|
||||
} else {
|
||||
loginItemsSnapshot.forEach {
|
||||
if CFEqual(LSSharedFileListItemCopyResolvedURL($0, 0, nil).takeRetainedValue(), App.url) {
|
||||
LSSharedFileListItemRemove(loginItems, $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import Cocoa
|
||||
|
||||
class ShortcutsTab {
|
||||
private static let rowHeight = CGFloat(22) // height of the "Tab key" input
|
||||
|
||||
static func make() -> NSTabViewItem {
|
||||
return TabViewItem.make(NSLocalizedString("Shortcuts", comment: ""), NSImage.preferencesGeneralName, makeView())
|
||||
}
|
||||
|
||||
private static func makeView() -> NSGridView { // TODO: make the validators be a part of each Preference
|
||||
let tabKeyCodeValidator: ((String) -> Bool) = {
|
||||
guard let int = Int($0) else {
|
||||
return false
|
||||
}
|
||||
// non-special keys (mac & pc keyboards): https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||
var whitelistedKeycodes: [Int] = Array(0...53)
|
||||
whitelistedKeycodes.append(contentsOf: [65, 67, 69, 75, 76, 78, ])
|
||||
whitelistedKeycodes.append(contentsOf: Array(81...89))
|
||||
whitelistedKeycodes.append(contentsOf: [91, 92, 115, 116, 117, 119, 121])
|
||||
whitelistedKeycodes.append(contentsOf: Array(123...126))
|
||||
return whitelistedKeycodes.contains(int)
|
||||
}
|
||||
|
||||
let view = GridView.make([
|
||||
LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Alt key", comment: ""), "metaKey", MacroPreferences.metaKeyList.values.map { $0.label }),
|
||||
LabelAndControl.makeLabelWithInput(NSLocalizedString("Tab key", comment: ""), "tabKeyCode", 33, NSLocalizedString("KeyCodes Reference", comment: ""), "https://eastmanreference.com/complete-list-of-applescript-key-codes", tabKeyCodeValidator),
|
||||
])
|
||||
view.column(at: 0).xPlacement = .trailing
|
||||
view.rowAlignment = .lastBaseline
|
||||
view.setRowsHeight(rowHeight)
|
||||
view.fit()
|
||||
return view
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user