diff --git a/src/AboutViewController.swift b/src/AboutViewController.swift index 9320fe9..15629ef 100644 --- a/src/AboutViewController.swift +++ b/src/AboutViewController.swift @@ -9,21 +9,22 @@ fileprivate enum AboutLinks { } enum Strings { - static let copyright = "Copyright © 2024\nGarikMI. All rights reserved." - static let evaluationTitle = "License - Evaluation" + static let copyright = "Copyright © 2024\nGarikMI. All rights reserved." + static let evaluationTitle = "License - Evaluation" static let evaluationMessage = "You are currently using evaluation license. CmdBar will quit after 20 minutes. If you already own a license, enter it below or purchase a license." - static let activate = "Activate" - static let proTitle = "License - Activated" - static let proMessage = "Thank you for purchasing CmdBar! Enjoy!" - static let deactivate = "Deactivate" - static let activating = "Activating..." + static let activate = "Activate" + static let proTitle = "License - Activated" + static let proMessage = "Thank you for purchasing CmdBar! Enjoy!" + static let deactivate = "Deactivate" + static let activating = "Activating..." } class AboutViewController: NSViewController, NSTextFieldDelegate { private var appIconImage: NSImageView = { //let image = NSImageView(image: NSApp.applicationIconImage) let image = NSImageView() - image.image = NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) + image.image = + NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) image.imageScaling = .scaleAxesIndependently image.translatesAutoresizingMaskIntoConstraints = false return image @@ -31,25 +32,35 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { private var appNameLabel: NSTextField = { let textField = NSTextField() - textField.stringValue = (Bundle.main.infoDictionary?["CFBundleName"] as? String) ?? "NOT FOUND" + textField.stringValue = + (Bundle.main.infoDictionary?["CFBundleName"] as? String) + ?? + "NOT FOUND" textField.isEditable = false textField.isBezeled = false textField.drawsBackground = false textField.alignment = .center - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor .preferredFontDescriptor(forTextStyle: .title1).pointSize, weight: .bold) + textField.font = + NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .title1).pointSize, + weight: .bold) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() private var versionLabel: NSTextField = { let textField = NSTextField() - textField.stringValue = "Version \((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "-.--")" + textField.stringValue = + "Version \((Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "-.--")" textField.isEditable = false textField.isBezeled = false textField.drawsBackground = false textField.alignment = .center textField.textColor = NSColor.systemGray - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular) + textField.font = + NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .subheadline).pointSize, + weight: .regular) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -64,7 +75,10 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { textField.drawsBackground = false textField.alignment = .center textField.textColor = NSColor.systemGray - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .subheadline).pointSize, weight: .regular) + textField.font = + NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .subheadline).pointSize, + weight: .regular) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -152,44 +166,77 @@ class AboutViewController: NSViewController, NSTextFieldDelegate { // App image. NSLayoutConstraint.activate([ appIconImage.widthAnchor.constraint(equalToConstant: 100), - appIconImage.heightAnchor.constraint(equalTo: appIconImage.widthAnchor, multiplier: 1), - appIconImage.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing20), - appIconImage.centerXAnchor.constraint(equalTo: view.centerXAnchor), + appIconImage.heightAnchor + .constraint(equalTo: appIconImage.widthAnchor, + multiplier: 1), + appIconImage.topAnchor + .constraint(equalTo: view.topAnchor, + constant: ViewConstants.spacing20), + appIconImage.centerXAnchor + .constraint(equalTo: view.centerXAnchor), ]) // Title NSLayoutConstraint.activate([ - appNameLabel.topAnchor.constraint(equalTo: appIconImage.bottomAnchor, constant: ViewConstants.spacing20), - appNameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + appNameLabel.topAnchor + .constraint(equalTo: appIconImage.bottomAnchor, + constant: ViewConstants.spacing20), + appNameLabel.centerXAnchor + .constraint(equalTo: view.centerXAnchor), - versionLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: ViewConstants.spacing2), - versionLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + versionLabel.topAnchor + .constraint(equalTo: appNameLabel.bottomAnchor, + constant: ViewConstants.spacing2), + versionLabel.centerXAnchor + .constraint(equalTo: view.centerXAnchor), - copyrightLabel.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: ViewConstants.spacing10), - copyrightLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + copyrightLabel.topAnchor + .constraint(equalTo: versionLabel.bottomAnchor, + constant: ViewConstants.spacing10), + copyrightLabel.centerXAnchor + .constraint(equalTo: view.centerXAnchor), ]) // Buttons NSLayoutConstraint.activate([ - buttonsContainer.topAnchor .constraint(equalTo: copyrightLabel.bottomAnchor, constant: ViewConstants.spacing20), - buttonsContainer.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -ViewConstants.spacing20), - buttonsContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor), + buttonsContainer.topAnchor + .constraint(equalTo: copyrightLabel.bottomAnchor, + constant: ViewConstants.spacing20), + buttonsContainer.bottomAnchor + .constraint(equalTo: view.bottomAnchor, + constant: -ViewConstants.spacing20), + buttonsContainer.centerXAnchor + .constraint(equalTo: view.centerXAnchor), - authorButton.topAnchor.constraint(equalTo: buttonsContainer.topAnchor), - authorButton.bottomAnchor.constraint(equalTo: buttonsContainer.bottomAnchor), - authorButton.leadingAnchor.constraint(equalTo: buttonsContainer.leadingAnchor), - authorButton.trailingAnchor.constraint(equalTo: buttonsContainer.trailingAnchor), + authorButton.topAnchor + .constraint(equalTo: buttonsContainer.topAnchor), + authorButton.bottomAnchor + .constraint(equalTo: buttonsContainer.bottomAnchor), + authorButton.leadingAnchor + .constraint(equalTo: buttonsContainer.leadingAnchor), + authorButton.trailingAnchor + .constraint(equalTo: buttonsContainer.trailingAnchor), - // privacyButton.topAnchor.constraint(equalTo: buttonsContainer.topAnchor), - // privacyButton.bottomAnchor.constraint(equalTo: buttonsContainer.bottomAnchor), - // privacyButton.leadingAnchor.constraint(equalTo: buttonsContainer.leadingAnchor), + // privacyButton.topAnchor + // .constraint(equalTo: buttonsContainer.topAnchor), + // privacyButton.bottomAnchor + // .constraint(equalTo: buttonsContainer.bottomAnchor), + // privacyButton.leadingAnchor + // .constraint(equalTo: buttonsContainer.leadingAnchor), // - // documentationButton.firstBaselineAnchor.constraint(equalTo: privacyButton.firstBaselineAnchor), - // documentationButton.leadingAnchor.constraint(equalTo: privacyButton.trailingAnchor,constant: ViewConstants.spacing10), + // documentationButton.firstBaselineAnchor + // .constraint(equalTo: privacyButton.firstBaselineAnchor), + // documentationButton.leadingAnchor + // .constraint(equalTo: privacyButton.trailingAnchor, + // constant: ViewConstants.spacing10), // - // websiteButton.firstBaselineAnchor.constraint(equalTo: privacyButton.firstBaselineAnchor), - // websiteButton.leadingAnchor.constraint(equalTo: documentationButton.trailingAnchor,constant: ViewConstants.spacing10), - // websiteButton.trailingAnchor.constraint(equalTo: buttonsContainer.trailingAnchor), + // websiteButton.firstBaselineAnchor + // .constraint(equalTo: privacyButton.firstBaselineAnchor), + // websiteButton.leadingAnchor + // .constraint(equalTo: documentationButton.trailingAnchor, + // constant: ViewConstants.spacing10), + // websiteButton.trailingAnchor + // .constraint(equalTo: buttonsContainer.trailingAnchor), ]) } diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift index 7229a1b..0fe73e2 100644 --- a/src/AppDelegate.swift +++ b/src/AppDelegate.swift @@ -15,21 +15,28 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { window.delegate = self - // NOTE: Here we check wether the program was launched by the system. If it was not, then display the window. + // NOTE: Here we check wether the program was launched by the + // system. If it was not, then display the window. if let event = NSAppleEventManager.shared().currentAppleEvent, - !(event.eventID == kAEOpenApplication && event.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem) + !(event.eventID == kAEOpenApplication && + event.paramDescriptor(forKeyword: keyAEPropData)? + .enumCodeValue == keyAELaunchedAsLogInItem) { window.makeKeyAndOrderFront(nil) } - HotKeyManager.shared.handler = { (inHandlerCallRef, inEvent, inUserData) -> OSStatus in - if let delegate = NSApplication.shared.delegate as? AppDelegate { + HotKeyManager.shared.handler = + { (inHandlerCallRef, inEvent, inUserData) -> OSStatus in + if let delegate = NSApplication.shared.delegate as? AppDelegate + { let window = delegate.window if window.isKeyWindow { window.resignKey() } else { window.makeKeyAndOrderFront(nil) - if let controller = window.contentViewController as? SearchViewController { + if let controller = + window.contentViewController as? SearchViewController + { controller.centerWindow() } } @@ -38,13 +45,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { } HotKeyManager.shared.enable() - if let code = UserDefaults.standard.object(forKey: "keyCode") as? Int, - let mods = UserDefaults.standard.object(forKey: "keyModifiers") as? Int + if let code = + UserDefaults.standard.object(forKey: "keyCode") as? Int, + let mods = + UserDefaults.standard.object(forKey: "keyModifiers") as? Int { HotKeyManager.shared.registerHotKey(key: code, modifiers: mods) } else { - // NOTE: This is the default shortcut. If you want to change it, do not forget to change it in other files (SettingsViewController). - HotKeyManager.shared.registerHotKey(key: kVK_Space, modifiers: optionKey) + // NOTE: This is the default shortcut. If you want to change + // it, do not forget to change it in other files + // (SettingsViewController). + HotKeyManager.shared.registerHotKey(key: kVK_Space, + modifiers: optionKey) } } @@ -54,7 +66,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { } } - func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool { + func applicationShouldHandleReopen(_ sender: NSApplication, + hasVisibleWindows: Bool) -> Bool { if !window.isKeyWindow { window.makeKeyAndOrderFront(nil) } @@ -85,7 +98,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { aboutWindow.makeKeyAndOrderFront(nil) } - // NOTE: This function act like a callback is triggered by DirMonitor when file system events occur. + // NOTE: This function act like a callback is triggered by DirMonitor + // when file system events occur. public func fsEventTriggered(_ path: String, _ flags: Int) { if containsFlags(key: kFSEventStreamEventFlagItemCreated, in: flags) || containsFlags(key: kFSEventStreamEventFlagItemRemoved, in: flags) || diff --git a/src/DirMonitor.swift b/src/DirMonitor.swift index bb48038..959bc81 100644 --- a/src/DirMonitor.swift +++ b/src/DirMonitor.swift @@ -25,22 +25,34 @@ class DirMonitor { var context = FSEventStreamContext() context.info = Unmanaged.passUnretained(self).toOpaque() - guard let stream = FSEventStreamCreate(nil, { (stream, info, numEvents, eventPaths, eventFlags, eventIds) in - let pathsBase = eventPaths .assumingMemoryBound(to: UnsafePointer.self) - let pathsBuffer = UnsafeBufferPointer(start: pathsBase, count: numEvents) - let flagsBuffer = UnsafeBufferPointer(start: eventFlags, count: numEvents) - // let eventIDsBuffer = UnsafeBufferPointer(start: eventIds, count: numEvents) + guard let stream = FSEventStreamCreate(nil, + { (stream, info, numEvents, eventPaths, eventFlags, eventIds) in + let pathsBase = eventPaths + .assumingMemoryBound(to: UnsafePointer.self) + let pathsBuffer = + UnsafeBufferPointer(start: pathsBase, count: numEvents) + let flagsBuffer = + UnsafeBufferPointer(start: eventFlags, count: numEvents) + // let eventIDsBuffer = + // UnsafeBufferPointer(start: eventIds, count: numEvents) for i in 0.. Bool { } func isNumericalCode(_ key: UInt16) -> Bool { - return (key == kVK_ANSI_1 || key == kVK_ANSI_2 || key == kVK_ANSI_3 || key == kVK_ANSI_4 || key == kVK_ANSI_5 || key == kVK_ANSI_6 || key == kVK_ANSI_7 || key == kVK_ANSI_8 || key == kVK_ANSI_9) + return (key == kVK_ANSI_1 || key == kVK_ANSI_2 || key == kVK_ANSI_3 || + key == kVK_ANSI_4 || key == kVK_ANSI_5 || key == kVK_ANSI_6 || + key == kVK_ANSI_7 || key == kVK_ANSI_8 || key == kVK_ANSI_9) } func modsContainsNone(in modifiers: UInt) -> Bool { @@ -46,8 +48,10 @@ func keyName(virtualKeyCode: UInt16) -> String? { //let source = // TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue() - let source = TISCopyInputSourceForLanguage("en-US" as CFString).takeRetainedValue(); - guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) + let source = TISCopyInputSourceForLanguage("en-US" as CFString) + .takeRetainedValue(); + guard let ptr = + TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else { print("Could not get keyboard layout data") return nil @@ -55,9 +59,11 @@ func keyName(virtualKeyCode: UInt16) -> String? { let layoutData = Unmanaged.fromOpaque(ptr) .takeUnretainedValue() as Data let osStatus = layoutData.withUnsafeBytes { - UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, virtualKeyCode, - UInt16(kUCKeyActionDown), modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask), - &deadKeys, maxNameLength, &nameLength, &nameBuffer) + UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, + virtualKeyCode, UInt16(kUCKeyActionDown), + modifierKeys, keyboardType, + UInt32(kUCKeyTranslateNoDeadKeysMask), &deadKeys, + maxNameLength, &nameLength, &nameBuffer) } guard osStatus == noErr else { print("Code: \(virtualKeyCode) Status: \(osStatus)") @@ -78,16 +84,23 @@ func keyName(virtualKeyCode: UInt16) -> String? { func isDirectory(_ path: String) -> Bool { var isDirectory: ObjCBool = false - if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory), isDirectory.boolValue { + if FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory), + isDirectory.boolValue + { return true } else { return false } } -func systemImage(_ name: String, _ size: NSFont.TextStyle, _ scale: NSImage.SymbolScale, _ configuration: NSImage.SymbolConfiguration) -> NSImage? { +func systemImage(_ name: String, _ size: NSFont.TextStyle, + _ scale: NSImage.SymbolScale, + _ configuration: NSImage.SymbolConfiguration) -> NSImage? +{ return NSImage(systemSymbolName: name, accessibilityDescription: nil)? - .withSymbolConfiguration(NSImage.SymbolConfiguration(textStyle: size, scale: scale).applying(configuration)) + .withSymbolConfiguration(NSImage + .SymbolConfiguration(textStyle: size, scale: scale) + .applying(configuration)) } extension String { diff --git a/src/HotKeyManager.swift b/src/HotKeyManager.swift index 56854d6..0b9a268 100644 --- a/src/HotKeyManager.swift +++ b/src/HotKeyManager.swift @@ -4,12 +4,15 @@ import OSLog final class HotKeyManager { static let shared = HotKeyManager() - private var eventType = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)) + private var eventType = + EventTypeSpec(eventClass: OSType(kEventClassKeyboard), + eventKind: UInt32(kEventHotKeyPressed)) private var eventHandlerRef: EventHandlerRef? public var handler: EventHandlerUPP? private var hotKeyRef: EventHotKeyRef? - private let hotKeyID: EventHotKeyID = EventHotKeyID(signature: OSType("grap".fourCharCodeValue), id: 1) + private let hotKeyID: EventHotKeyID = + EventHotKeyID(signature: OSType("grap".fourCharCodeValue), id: 1) private init() {} deinit {} @@ -20,7 +23,8 @@ final class HotKeyManager { disable() } - let err = InstallEventHandler(GetApplicationEventTarget(), handler, 1, &eventType, nil, &eventHandlerRef) + let err = InstallEventHandler(GetApplicationEventTarget(), handler, + 1, &eventType, nil, &eventHandlerRef) if err == noErr { print("Installed event handler.") } else { @@ -32,7 +36,8 @@ final class HotKeyManager { guard eventHandlerRef != nil else { return } let err = RemoveEventHandler(eventHandlerRef) if err == noErr { - eventHandlerRef = nil // WARNING: Does it remove no matter what on error? + // WARNING: Does it remove no matter what on error? + eventHandlerRef = nil print("Removed event handler.") } else { print("Failed to remove event handler.") @@ -46,7 +51,10 @@ final class HotKeyManager { unregisterHotKey() } - let err = RegisterEventHotKey(UInt32(key), UInt32(modifiers), hotKeyID, GetApplicationEventTarget(), UInt32(kEventHotKeyNoOptions), &hotKeyRef) + let err = RegisterEventHotKey(UInt32(key), UInt32(modifiers), + hotKeyID, GetApplicationEventTarget(), + UInt32(kEventHotKeyNoOptions), + &hotKeyRef) if err == noErr { print("Registered hot key.") } else { @@ -59,7 +67,8 @@ final class HotKeyManager { guard hotKeyRef != nil else { return } let err = UnregisterEventHotKey(hotKeyRef) if err == noErr { - hotKeyRef = nil // WARNING: Does it unregister no matter what on error? + // WARNING: Does it unregister no matter what on error? + hotKeyRef = nil print("Successfully unregistered hot key.") } else { print("Failed to unregistered hot key.") diff --git a/src/KeyDetectorButton.swift b/src/KeyDetectorButton.swift index a7ee9af..d2cb8b2 100644 --- a/src/KeyDetectorButton.swift +++ b/src/KeyDetectorButton.swift @@ -13,7 +13,8 @@ final class KeyDetectorButton: NSButton { override var acceptsFirstResponder: Bool { true } - // This removes default bahavior from NSButton, thus allowing mouse up events. + // This removes default bahavior from NSButton, thus allowing mouse up + // events. override func mouseDown(with event: NSEvent) {} override func mouseUp(with event: NSEvent) { @@ -24,7 +25,9 @@ final class KeyDetectorButton: NSButton { if event.keyCode == kVK_Escape || event.keyCode == kVK_Return { // Ignore escape and return keys. } else if event.keyCode == kVK_Delete { - if let key = defaultKey, let character = keyName(virtualKeyCode: UInt16(key)) { + if let key = defaultKey, + let character = keyName(virtualKeyCode: UInt16(key)) + { title = character } } else { diff --git a/src/Makefile b/src/Makefile index 2b89265..18c5a86 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,10 +7,13 @@ XCODE_PATH = $(shell xcode-select --print-path) EXEC = Grapp -SRCMODULES = Helpers.swift EditableNSTextField.swift EventMonitor.swift PopoverPanel.swift SearchViewController.swift \ - SettingsViewController.swift HotKeyManager.swift KeyDetectorButton.swift PathManager.swift PathsTableCellView.swift \ - ProgramsTable.swift ShadowView.swift DirMonitor.swift MenulessWindow.swift AboutViewController.swift AppDelegate.swift \ - main.swift +SRCMODULES = Helpers.swift EditableNSTextField.swift EventMonitor.swift \ + PopoverPanel.swift SearchViewController.swift \ + SettingsViewController.swift HotKeyManager.swift \ + KeyDetectorButton.swift PathManager.swift \ + PathsTableCellView.swift ProgramsTable.swift ShadowView.swift \ + DirMonitor.swift MenulessWindow.swift AboutViewController.swift \ + AppDelegate.swift main.swift ARMOBJMODULES = $(addprefix ./arm64/,$(SRCMODULES:.swift=.o)) X86OBJMODULES = $(addprefix ./x86_64/,$(SRCMODULES:.swift=.o)) @@ -18,35 +21,52 @@ LIBS = FRAMEWORKS = -framework AppKit -framework ServiceManagement -# HACK: Target is getting touched because timestamps of the generated object file don't change unless there's an actual change in the -# outputted object code. This results in this target running every single time. I'm not sure whether that's the exact reason, but -# I can't imagine why timestamps wouldn't change. When clang generates same exact executable, timestamps do change. +# HACK: Target is getting touched because timestamps of the generated +# object file don't change unless there's an actual change in the +# outputted object code. This results in this target running every +# single time. I'm not sure whether that's the exact reason, but +# I can't imagine why timestamps wouldn't change. When clang +# generates same exact executable, timestamps do change. ./arm64/%.o: %.swift - swift -frontend -c -target arm64-apple-macos$(MACOS_VERSION) $(FLAGS) -primary-file $< $(filter-out $<, $(SRCMODULES)) $(LIBS) \ - $(FRAMEWORKS) -sdk $(SDK) -module-name $(EXEC) -o $@ -emit-module && \ + swift -frontend -c \ + -target arm64-apple-macos$(MACOS_VERSION) $(FLAGS) \ + -primary-file $< $(filter-out $<, $(SRCMODULES)) $(LIBS) \ + $(FRAMEWORKS) -sdk $(SDK) -module-name $(EXEC) -o $@ \ + -emit-module && \ touch $@ ifdef UNIVERSAL ./x86_64/%.o: %.swift - @swift -frontend -c -target x86_64-apple-macos$(MACOS_VERSION) $(FLAGS) -primary-file $< $(filter-out $<, $(SRCMODULES)) \ - $(LIBS) $(FRAMEWORKS) -sdk $(SDK) -module-name $(EXEC) -o $@ -emit-module && \ + @swift -frontend -c \ + -target x86_64-apple-macos$(MACOS_VERSION) $(FLAGS) \ + -primary-file $< $(filter-out $<, $(SRCMODULES)) \ + $(LIBS) $(FRAMEWORKS) -sdk $(SDK) -module-name $(EXEC) -o $@ \ + -emit-module && \ touch $@ endif ./arm64/$(EXEC): $(ARMOBJMODULES) - @ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch arm64 -macos_version_min $(MACOS_VERSION).0 \ + @ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch arm64 \ + -macos_version_min $(MACOS_VERSION).0 \ /Library/Developer/CommandLineTools/usr/lib/swift/macosx/libswiftCompatibilityPacks.a \ - -sectcreate __TEXT __info_plist Info.plist -L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \ - /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift -no_objc_category_merging -L $(XCODE_PATH) -rpath \ - Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx ./arm64/main.o $(filter-out ./arm64/main.o, $(ARMOBJMODULES)) -o $@ + -sectcreate __TEXT __info_plist Info.plist \ + -L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \ + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift \ + -no_objc_category_merging -L $(XCODE_PATH) -rpath \ + Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \ + ./arm64/main.o $(filter-out ./arm64/main.o, $(ARMOBJMODULES)) -o $@ ifdef UNIVERSAL ./x86_64/$(EXEC): $(X86OBJMODULES) - @ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch x86_64 -macos_version_min $(MACOS_VERSION).0 \ + @ld -syslibroot $(SDK) -lSystem $(FRAMEWORKS) -arch x86_64 \ + -macos_version_min $(MACOS_VERSION).0 \ /Library/Developer/CommandLineTools/usr/lib/swift/macosx/libswiftCompatibilityPacks.a \ - -sectcreate __TEXT __info_plist Info.plist -L /Library/Developer/CommandLineTools/usr/lib/swift/macosx -L \ - /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift -no_objc_category_merging -L $(XCODE_PATH) -rpath \ - Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx ./x86_64/main.o $(filter-out ./x86_64/main.o, $(X86OBJMODULES)) -o $@ + -sectcreate __TEXT __info_plist Info.plist \ + -L /Library/Developer/CommandLineTools/usr/lib/swift/macosx \ + -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift \ + -no_objc_category_merging -L $(XCODE_PATH) -rpath \ + Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \ + ./x86_64/main.o $(filter-out ./x86_64/main.o, $(X86OBJMODULES)) -o $@ endif ifdef UNIVERSAL @@ -64,20 +84,22 @@ $(EXEC).app: $(EXEC) cp Info.plist $@/Contents/ && \ cp resources/AppIcon.icns $@/Contents/Resources/ && \ cp $(EXEC) $@/Contents/MacOS/ && \ - $(if $(DEBUG), codesign --entitlements Grapp.entitlements -s ${APPLE_DEVELOPMENT} -f --timestamp -o runtime $(EXEC).app, \ - codesign -s ${APPLE_DEVELOPER_ID_APPLICATION} -f --timestamp -o runtime $(EXEC).app) + $(if $(DEBUG), codesign --entitlements Grapp.entitlements \ + -s ${APPLE_DEVELOPMENT} -f --timestamp -o runtime $(EXEC).app, \ + codesign -s ${APPLE_DEVELOPER_ID_APPLICATION} -f --timestamp \ + -o runtime $(EXEC).app) all: $(EXEC).app kill: -pkill $(EXEC) -open: kill all - open $(EXEC).app - run: kill all ./$(EXEC) +open: kill all + open $(EXEC).app + clean: rm -rf $(EXEC) $(EXEC).app arm64 x86_64 mkdir arm64 x86_64 diff --git a/src/MenulessWindow.swift b/src/MenulessWindow.swift index f45b604..fa50571 100644 --- a/src/MenulessWindow.swift +++ b/src/MenulessWindow.swift @@ -23,7 +23,9 @@ class MenulessWindow: NSWindow { let key = event.keyCode if event.type == NSEvent.EventType.keyDown { - if modsContains(keys: OSCmd, in: modifiers) && key == kVK_ANSI_W { + if modsContains(keys: OSCmd, in: modifiers) && + key == kVK_ANSI_W + { performClose(nil) return true } diff --git a/src/PathManager.swift b/src/PathManager.swift index c36ff2b..d62dc09 100644 --- a/src/PathManager.swift +++ b/src/PathManager.swift @@ -29,7 +29,10 @@ final class PathManager { private init() { // UserDefaults.standard.removeObject(forKey: "programPaths") - if let dirs = UserDefaults.standard.stringArray(forKey: "programPaths"), !dirs.isEmpty { + if let dirs = + UserDefaults.standard.stringArray(forKey: "programPaths"), + !dirs.isEmpty + { for dir in dirs { addPath(dir) } @@ -79,7 +82,9 @@ final class PathManager { buf.append(path.key) } - dirMonitor = DirMonitor(paths: buf, queue: DispatchQueue.global(qos: .userInitiated)) + dirMonitor = + DirMonitor(paths: buf, + queue: DispatchQueue.global(qos: .userInitiated)) // _ = dirMonitor!.start() if dirMonitor!.start() { print("Started monitoring directories.") @@ -115,10 +120,12 @@ final class PathManager { let name = String(item.dropLast(4)) if item.hasSuffix(".app"), !contains(name) { - array.append(Program(path: path, name: name, ext: ".app", img: nil)) + array.append(Program(path: path, name: name, + ext: ".app", img: nil)) } if deepness > 0 { - array += indexDirs(at: path + "/" + item, deepness: deepness-1) + array += indexDirs(at: path + "/" + item, + deepness: deepness-1) } } } catch { print("Error: \(error.localizedDescription)") } diff --git a/src/PathsTableCellView.swift b/src/PathsTableCellView.swift index 335ec49..816875f 100644 --- a/src/PathsTableCellView.swift +++ b/src/PathsTableCellView.swift @@ -28,7 +28,7 @@ class PathsTableCellView: NSTableCellView, NSTextFieldDelegate, var selectionButton: NSButton = { let button = NSButton() button.image = systemImage("hand.point.up.fill", .headline, .large, - .init(paletteColors: [.labelColor, .systemRed])) + .init(paletteColors: [.labelColor, .systemRed])) button.isBordered = false button.sizeToFit() button.toolTip = "Select Path" @@ -57,8 +57,8 @@ class PathsTableCellView: NSTableCellView, NSTextFieldDelegate, //titleField.bottomAnchor.constraint(equalTo: bottomAnchor), titleField.centerYAnchor.constraint(equalTo: centerYAnchor), titleField.leadingAnchor.constraint(equalTo: leadingAnchor), - titleField.trailingAnchor.constraint( - equalTo: selectionButton.leadingAnchor), + titleField.trailingAnchor + .constraint(equalTo: selectionButton.leadingAnchor), selectionButton.centerYAnchor.constraint( equalTo: centerYAnchor), diff --git a/src/PopoverPanel.swift b/src/PopoverPanel.swift index 2c5954c..0e33d7a 100644 --- a/src/PopoverPanel.swift +++ b/src/PopoverPanel.swift @@ -7,7 +7,8 @@ class PopoverPanel: NSPanel { init(viewController: NSViewController) { super.init( contentRect: CGRect(x: 0, y: 0, width: 100, height: 100), - styleMask: [.borderless, .nonactivatingPanel, .utilityWindow, .fullSizeContentView], + styleMask: [.borderless, .nonactivatingPanel, .utilityWindow, + .fullSizeContentView], backing: .buffered, defer: false ) @@ -25,7 +26,8 @@ class PopoverPanel: NSPanel { titlebarAppearsTransparent = true animationBehavior = .none - collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary, .transient] + collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary, + .transient] isReleasedWhenClosed = false hidesOnDeactivate = false } @@ -35,13 +37,19 @@ class PopoverPanel: NSPanel { let key = event.keyCode if event.type == NSEvent.EventType.keyDown { - if modsContains(keys: OSCmd, in: modifiers) && key == kVK_ANSI_Q { + if modsContains(keys: OSCmd, in: modifiers) && + key == kVK_ANSI_Q + { NSApplication.shared.terminate(self) return true - } else if modsContains(keys: OSCmd, in: modifiers) && key == kVK_ANSI_W { + } else if modsContains(keys: OSCmd, in: modifiers) && + key == kVK_ANSI_W + { resignKey() return true - } else if modsContains(keys: OSCmd | OSShift, in: modifiers) && key == kVK_ANSI_R { + } else if modsContains(keys: OSCmd | OSShift, in: modifiers) && + key == kVK_ANSI_R + { PathManager.shared.updateIndex() return true } else if key == kVK_Escape { diff --git a/src/ProgramsTable.swift b/src/ProgramsTable.swift index 87ee60d..33e8037 100644 --- a/src/ProgramsTable.swift +++ b/src/ProgramsTable.swift @@ -7,7 +7,8 @@ final class ProgramsTableView: NSTableView { class ProgramsTableRowView: NSTableRowView { override func drawSelection(in dirtyRect: NSRect) { if self.selectionHighlightStyle != .none { - let selectionColor = NSColor.controlAccentColor.withAlphaComponent(0.8) + let selectionColor = + NSColor.controlAccentColor.withAlphaComponent(0.8) selectionColor.setFill() self.bounds.fill() } @@ -28,7 +29,10 @@ class ProgramsTableViewCell: NSTableCellView { field.textColor = NSColor.secondaryLabelColor field.cell?.lineBreakMode = .byTruncatingTail - field.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .caption1).pointSize, weight: .bold) + field.font = NSFont + .systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .caption1).pointSize, + weight: .bold) field.translatesAutoresizingMaskIntoConstraints = false return field }() @@ -36,7 +40,8 @@ class ProgramsTableViewCell: NSTableCellView { public var appIconImage: NSImageView = { let image = NSImageView() - image.image = NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) + image.image = + NSWorkspace.shared.icon(forFile: Bundle.main.bundlePath) image.imageScaling = .scaleAxesIndependently image.translatesAutoresizingMaskIntoConstraints = false return image @@ -57,7 +62,10 @@ class ProgramsTableViewCell: NSTableCellView { field.isBordered = false field.drawsBackground = false field.lineBreakMode = .byTruncatingTail - field.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .caption1).pointSize, weight: .medium) + field.font = NSFont + .systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .caption1).pointSize, + weight: .medium) field.translatesAutoresizingMaskIntoConstraints = false return field }() @@ -66,7 +74,8 @@ class ProgramsTableViewCell: NSTableCellView { super.init(frame: frameRect) // wantsLayer = true - // layer?.backgroundColor = NSColor.yellow.withAlphaComponent(0.2).cgColor + // layer?.backgroundColor = + // NSColor.yellow.withAlphaComponent(0.2).cgColor addSubview(indexLabel) addSubview(appIconImage) @@ -77,21 +86,33 @@ class ProgramsTableViewCell: NSTableCellView { NSLayoutConstraint.activate([ indexLabel.widthAnchor.constraint(equalToConstant: 25), indexLabel.centerYAnchor.constraint(equalTo: centerYAnchor), - indexLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: ViewConstants.spacing5), + indexLabel.leadingAnchor + .constraint(equalTo: leadingAnchor, + constant: ViewConstants.spacing5), appIconImage.widthAnchor.constraint(equalToConstant: 40), appIconImage.heightAnchor.constraint(equalToConstant: 40), appIconImage.topAnchor.constraint(equalTo: topAnchor), appIconImage.bottomAnchor.constraint(equalTo: bottomAnchor), - appIconImage.leadingAnchor.constraint(equalTo: indexLabel.trailingAnchor), + appIconImage.leadingAnchor + .constraint(equalTo: indexLabel.trailingAnchor), - titleField.topAnchor.constraint(equalTo: appIconImage.topAnchor, constant: ViewConstants.spacing2), - titleField.leadingAnchor.constraint(equalTo: appIconImage.trailingAnchor, constant: ViewConstants.spacing5), - titleField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -ViewConstants.spacing5), + titleField.topAnchor + .constraint(equalTo: appIconImage.topAnchor, + constant: ViewConstants.spacing2), + titleField.leadingAnchor + .constraint(equalTo: appIconImage.trailingAnchor, + constant: ViewConstants.spacing5), + titleField.trailingAnchor + .constraint(equalTo: trailingAnchor, + constant: -ViewConstants.spacing5), - progPathLabel.topAnchor.constraint(equalTo: titleField.bottomAnchor), - progPathLabel.leadingAnchor.constraint(equalTo: titleField.leadingAnchor), - progPathLabel.trailingAnchor.constraint(equalTo: titleField.trailingAnchor), + progPathLabel.topAnchor + .constraint(equalTo: titleField.bottomAnchor), + progPathLabel.leadingAnchor + .constraint(equalTo: titleField.leadingAnchor), + progPathLabel.trailingAnchor + .constraint(equalTo: titleField.trailingAnchor), ]) } diff --git a/src/SearchViewController.swift b/src/SearchViewController.swift index 2ef9b98..5fe94fc 100644 --- a/src/SearchViewController.swift +++ b/src/SearchViewController.swift @@ -6,7 +6,10 @@ fileprivate let windowCornerRadius = 15.0 fileprivate let maxItems = 20 -class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDelegate, NSTableViewDataSource, NSTableViewDelegate { +class SearchViewController: NSViewController, NSTextFieldDelegate, + NSPopoverDelegate, NSTableViewDataSource, + NSTableViewDelegate +{ private var keyboardEvents: EventMonitor? private var listIndex = 0 @@ -46,7 +49,8 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele private var contentView: NSView = { let view = NSView() - // Clip all content to window's rounded frame emulated by backgroundView. + // Clip all content to window's rounded frame emulated by + // backgroundView. view.wantsLayer = true view.layer?.masksToBounds = true view.layer?.cornerRadius = windowCornerRadius @@ -64,14 +68,18 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele textField.focusRingType = .none textField.placeholderString = "Program Search" textField.bezelStyle = .roundedBezel - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .largeTitle).pointSize, weight: .medium) + textField.font = NSFont + .systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .largeTitle).pointSize, + weight: .medium) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() private var settingsButton: NSButton = { let button = NSButton() - button.image = systemImage("gear.circle.fill", .title1, .large, .init(paletteColors: [.white, .systemGray])) + button.image = systemImage("gear.circle.fill", .title1, .large, + .init(paletteColors: [.white, .systemGray])) button.isBordered = false button.action = #selector(openSettings) button.sizeToFit() @@ -83,7 +91,9 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele private var tableScrollView: NSScrollView = { let scroll = NSScrollView() scroll.automaticallyAdjustsContentInsets = false - scroll.contentInsets = NSEdgeInsets(top: 0, left: 0, bottom: ViewConstants.spacing10, right: 0) + scroll.contentInsets = NSEdgeInsets(top: 0, left: 0, + bottom: ViewConstants.spacing10, + right: 0) scroll.drawsBackground = false scroll.translatesAutoresizingMaskIntoConstraints = false return scroll @@ -102,7 +112,11 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele table.allowsColumnReordering = false table.allowsColumnResizing = false table.allowsColumnSelection = false - table.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Program"))) + table.addTableColumn( + NSTableColumn( + identifier: NSUserInterfaceItemIdentifier("Program") + ) + ) table.doubleAction = #selector(tableDoubleClick) @@ -123,47 +137,83 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele var tableViewHeightAnchor: NSLayoutConstraint? private func setConstraints() { - tableViewHeightAnchor = tableScrollView.heightAnchor.constraint(equalToConstant: 0) + tableViewHeightAnchor = tableScrollView.heightAnchor + .constraint(equalToConstant: 0) tableViewHeightAnchor?.isActive = true NSLayoutConstraint.activate([ - view.topAnchor.constraint(equalTo: contentView.topAnchor, constant: -100), - view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 100), - view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: -100), - view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 100), + view.topAnchor + .constraint(equalTo: contentView.topAnchor, constant: -100), + view.bottomAnchor + .constraint(equalTo: contentView.bottomAnchor, + constant: 100), + view.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor, + constant: -100), + view.trailingAnchor + .constraint(equalTo: contentView.trailingAnchor, + constant: 100), - shadowView.topAnchor.constraint(equalTo: backgroundView.topAnchor), - shadowView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor), - shadowView.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor), - shadowView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor), + shadowView.topAnchor + .constraint(equalTo: backgroundView.topAnchor), + shadowView.bottomAnchor + .constraint(equalTo: backgroundView.bottomAnchor), + shadowView.leadingAnchor + .constraint(equalTo: backgroundView.leadingAnchor), + shadowView.trailingAnchor + .constraint(equalTo: backgroundView.trailingAnchor), - backgroundView.topAnchor.constraint(equalTo: contentView.topAnchor), - backgroundView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - backgroundView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - backgroundView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + backgroundView.topAnchor + .constraint(equalTo: contentView.topAnchor), + backgroundView.bottomAnchor + .constraint(equalTo: contentView.bottomAnchor), + backgroundView.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor), + backgroundView.trailingAnchor + .constraint(equalTo: contentView.trailingAnchor), - searchInput.widthAnchor.constraint(equalToConstant: 400), - searchInput.topAnchor.constraint(equalTo: contentView.topAnchor, constant: ViewConstants.spacing10), - searchInput.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: ViewConstants.spacing15), + searchInput.widthAnchor + .constraint(equalToConstant: 400), + searchInput.topAnchor + .constraint(equalTo: contentView.topAnchor, + constant: ViewConstants.spacing10), + searchInput.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor, + constant: ViewConstants.spacing15), - settingsButton.centerYAnchor.constraint(equalTo: searchInput.centerYAnchor), - settingsButton.leadingAnchor.constraint(equalTo: searchInput.trailingAnchor, constant: ViewConstants.spacing5), - settingsButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -ViewConstants.spacing10), + settingsButton.centerYAnchor + .constraint(equalTo: searchInput.centerYAnchor), + settingsButton.leadingAnchor + .constraint(equalTo: searchInput.trailingAnchor, + constant: ViewConstants.spacing5), + settingsButton.trailingAnchor + .constraint(equalTo: contentView.trailingAnchor, + constant: -ViewConstants.spacing10), - tableScrollView.topAnchor.constraint(equalTo: searchInput.bottomAnchor, constant: ViewConstants.spacing10), - tableScrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), - tableScrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - tableScrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) + tableScrollView.topAnchor + .constraint(equalTo: searchInput.bottomAnchor, + constant: ViewConstants.spacing10), + tableScrollView.bottomAnchor + .constraint(equalTo: contentView.bottomAnchor), + tableScrollView.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor), + tableScrollView.trailingAnchor + .constraint(equalTo: contentView.trailingAnchor) ]) } override func viewDidLoad() { super.viewDidLoad() - // NOTE: This needs removeObserver on deinit? Well, technically we don't care because this view controller will exist throughout - // the whole life of the program. When the program gets killed, the OS will clear this. - DistributedNotificationCenter.default.addObserver(self, selector: #selector(osThemeChanged(sender:)), - name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), object: nil) + // NOTE: This needs removeObserver on deinit? Well, technically we + // don't care because this view controller will exist + // throughout the whole life of the program. When the + // program gets killed, the OS will clear this. + DistributedNotificationCenter.default + .addObserver(self, selector: #selector(osThemeChanged(sender:)), + name: NSNotification + .Name(rawValue: "AppleInterfaceThemeChangedNotification"), + object: nil) // Initialize an array of programs and reusable cells. for i in 0.. controller.listIndex-1 { - controller.programsTableViewSelection = controller.listIndex-1 + if controller.programsTableViewSelection > + controller.listIndex-1 + { + controller.programsTableViewSelection = + controller.listIndex-1 } else if controller.programsTableViewSelection < 0 { controller.programsTableViewSelection = 0 } let select = controller.programsTableViewSelection - self?.programsTableView.selectRowIndexes(IndexSet(integer: select), byExtendingSelection: false) + self?.programsTableView + .selectRowIndexes(IndexSet(integer: select), + byExtendingSelection: false) self?.programsTableView.scrollRowToVisible(select) } @@ -246,7 +319,8 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele view.window?.makeFirstResponder(searchInput) // searchInput should select all text whenever window appears. - NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) + NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, + from: self) } override func viewWillAppear() { @@ -267,8 +341,12 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele func centerWindow() { if let win = view.window, let scrn = NSScreen.main { - let x = (scrn.visibleFrame.origin.x + scrn.visibleFrame.size.width / 2) - (win.frame.size.width / 2) - let y = (scrn.visibleFrame.origin.y + scrn.visibleFrame.size.height * 0.9) - win.frame.size.height + let x = (scrn.visibleFrame.origin.x + + scrn.visibleFrame.size.width / 2) - + (win.frame.size.width / 2) + let y = (scrn.visibleFrame.origin.y + + scrn.visibleFrame.size.height * 0.9) - + win.frame.size.height view.window?.setFrameOrigin(NSPoint(x: x, y: y)) } } @@ -284,7 +362,8 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele @objc func openSettings() { - settingsPopover.show(relativeTo: settingsButton.bounds, of: settingsButton, preferredEdge: .maxY) + settingsPopover.show(relativeTo: settingsButton.bounds, + of: settingsButton, preferredEdge: .maxY) } @objc @@ -294,7 +373,8 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele } private func openProgram(_ program: Program) { - let url = URL(fileURLWithPath: program.path).appendingPathComponent(program.name+program.ext) + let url = URL(fileURLWithPath: program.path) + .appendingPathComponent(program.name+program.ext) let config = NSWorkspace.OpenConfiguration() NSWorkspace.shared.openApplication(at: url, configuration: config) @@ -306,7 +386,8 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele } func controlTextDidChange(_ obj: Notification) { - guard let searchInput = obj.object as? EditableNSTextField else { return } + guard let searchInput = + obj.object as? EditableNSTextField else { return } listIndex = 0 if !searchInput.stringValue.isEmpty { @@ -315,11 +396,20 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele if listIndex >= maxItems { break outerloop } let prog = path.value[i] - if prog.name.lowercased().contains(searchInput.stringValue.lowercased()) { + if prog.name.lowercased() + .contains(searchInput.stringValue.lowercased()) + { programsList[listIndex].path = prog.path programsList[listIndex].name = prog.name - programsList[listIndex].ext = prog.ext - programsList[listIndex].img = NSWorkspace.shared.icon(forFile: URL(fileURLWithPath: prog.path).appendingPathComponent(prog.name+prog.ext).path) + programsList[listIndex].ext = prog.ext + programsList[listIndex].img = + NSWorkspace.shared + .icon(forFile: URL( + fileURLWithPath: prog.path + ) + .appendingPathComponent( + prog.name+prog.ext).path + ) listIndex += 1 } } @@ -328,25 +418,37 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele reloadProgramsTableViewData() programsTableViewSelection = 0 - programsTableView.selectRowIndexes(IndexSet(integer: programsTableViewSelection), byExtendingSelection: false) + programsTableView.selectRowIndexes( + IndexSet(integer: programsTableViewSelection), + byExtendingSelection: false + ) programsTableView.scrollRowToVisible(programsTableViewSelection) } - func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + func control(_ control: NSControl, textView: NSTextView, + doCommandBy commandSelector: Selector) -> Bool + { if commandSelector == #selector(NSResponder.insertNewline(_:)) { if listIndex > 0 { let program = programsList[programsTableViewSelection] openProgram(program) - NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) + NSApp.sendAction(#selector(NSResponder.selectAll(_:)), + to: nil, from: self) } return true } else if commandSelector == #selector(NSResponder.insertTab(_:)) { return true - } else if commandSelector == #selector(NSResponder.moveUp(_:)) || commandSelector == #selector(NSResponder.moveDown(_:)) { - // Ignore arrows up and down because we use those to navigate the programs list. + } else if commandSelector == #selector(NSResponder.moveUp(_:)) || + commandSelector == #selector(NSResponder.moveDown(_:)) + { + // Ignore arrows up and down because we use those to navigate + // the programs list. return true - } else if commandSelector == #selector(NSResponder.moveToBeginningOfDocument(_:)) { - // Ignore command plus up and down arrows because we use those to move to the beginning and end of the list. + } else if commandSelector == + #selector(NSResponder.moveToBeginningOfDocument(_:)) + { + // Ignore command plus up and down arrows because we use those + // to move to the beginning and end of the list. return true } @@ -365,18 +467,28 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele return listIndex } - func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { + func tableView(_ tableView: NSTableView, + rowViewForRow row: Int) -> NSTableRowView? + { return ProgramsTableRowView() } - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + func tableView(_ tableView: NSTableView, + viewFor tableColumn: NSTableColumn?, + row: Int) -> NSView? + { let cell = programsListCells[row] let program = programsList[row] let app = program.name + program.ext - let rangeToHighlight = (app.lowercased() as NSString).range(of: searchInput.stringValue.lowercased()) + let rangeToHighlight = + (app.lowercased() as NSString).range( + of: searchInput.stringValue.lowercased() + ) let attributedString = NSMutableAttributedString(string: app) - attributedString.addAttributes([.foregroundColor: NSColor.labelColor], range: rangeToHighlight) + attributedString + .addAttributes([.foregroundColor: NSColor.labelColor], + range: rangeToHighlight) cell.titleField.attributedStringValue = attributedString cell.progPathLabel.stringValue = program.path @@ -397,10 +509,14 @@ class SearchViewController: NSViewController, NSTextFieldDelegate, NSPopoverDele } @objc func updateViewsBasedOnOSTheme() { - if NSApp.windows.first?.effectiveAppearance.bestMatch(from: [.darkAqua, .vibrantDark]) == .darkAqua { // dark - backgroundView.layer?.borderColor = NSColor.white.withAlphaComponent(0.2).cgColor + if NSApp.windows.first?.effectiveAppearance + .bestMatch(from: [.darkAqua, .vibrantDark]) == .darkAqua + { // dark + backgroundView.layer?.borderColor = + NSColor.white.withAlphaComponent(0.2).cgColor } else { // light - backgroundView.layer?.borderColor = NSColor.black.withAlphaComponent(0.2).cgColor + backgroundView.layer?.borderColor = + NSColor.black.withAlphaComponent(0.2).cgColor } } } diff --git a/src/SettingsViewController.swift b/src/SettingsViewController.swift index 9c8912a..15e0176 100644 --- a/src/SettingsViewController.swift +++ b/src/SettingsViewController.swift @@ -3,8 +3,9 @@ import Carbon import ServiceManagement class SettingsViewController: NSViewController, - NSTextFieldDelegate, KeyDetectorButtonDelegate, NSTableViewDataSource, - NSTableViewDelegate, PathsTableCellViewDelegate + NSTextFieldDelegate, KeyDetectorButtonDelegate, + NSTableViewDataSource, NSTableViewDelegate, + PathsTableCellViewDelegate { private var recording = false @@ -15,8 +16,10 @@ class SettingsViewController: NSViewController, private var paths: [String] = [] - // PERF: This is very slow to initialize because it creates a new process. This also cannot be done on a separate - // thread. This sucks because the program now takes considerably longer to launch. + // PERF: This is very slow to initialize because it creates a new + // process. This also cannot be done on a separate thread. This + // sucks because the program now takes considerably longer to + // launch. private let dirPicker: NSOpenPanel = { let panel = NSOpenPanel() panel.message = "Select a directory to search applications in . . ." @@ -28,14 +31,18 @@ class SettingsViewController: NSViewController, private var shortcutsLabel: NSTextField = { let textField = NSTextField(labelWithString: "Shortcut") - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .title2).pointSize, weight: .bold) + textField.font = + NSFont.systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .title2).pointSize, + weight: .bold) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() private var aboutButton: NSButton = { let button = NSButton() - button.image = systemImage("info.circle.fill", .title2, .large, .init(paletteColors: [.white, .systemGray])) + button.image = systemImage("info.circle.fill", .title2, .large, + .init(paletteColors: [.white, .systemGray])) button.isBordered = false button.action = #selector(showAbout) button.sizeToFit() @@ -95,7 +102,10 @@ class SettingsViewController: NSViewController, textField.isBezeled = false textField.drawsBackground = false textField.alignment = .center - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .body).pointSize, weight: .bold) + textField.font = NSFont + .systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .body).pointSize, + weight: .bold) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -110,8 +120,12 @@ class SettingsViewController: NSViewController, }() private var pathsLabel: NSTextField = { - let textField = NSTextField(labelWithString: "Application Directories") - textField.font = NSFont.systemFont(ofSize: NSFontDescriptor.preferredFontDescriptor(forTextStyle: .title2).pointSize, weight: .bold) + let textField = + NSTextField(labelWithString: "Application Directories") + textField.font = NSFont + .systemFont(ofSize: NSFontDescriptor + .preferredFontDescriptor(forTextStyle: .title2).pointSize, + weight: .bold) textField.translatesAutoresizingMaskIntoConstraints = false return textField }() @@ -135,7 +149,11 @@ class SettingsViewController: NSViewController, table.allowsColumnReordering = false table.allowsColumnResizing = false table.allowsColumnSelection = false - table.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Paths"))) + table.addTableColumn( + NSTableColumn( + identifier: NSUserInterfaceItemIdentifier("Paths") + ) + ) table.translatesAutoresizingMaskIntoConstraints = false return table @@ -146,8 +164,10 @@ class SettingsViewController: NSViewController, control.segmentCount = 2 control.segmentStyle = .roundRect - control.setImage(NSImage(systemSymbolName: "plus", accessibilityDescription: nil), forSegment: 0) - control.setImage(NSImage(systemSymbolName: "minus", accessibilityDescription: nil), forSegment: 1) + control.setImage(NSImage(systemSymbolName: "plus", + accessibilityDescription: nil), forSegment: 0) + control.setImage(NSImage(systemSymbolName: "minus", + accessibilityDescription: nil), forSegment: 1) control.setToolTip("Add Path", forSegment: 0) control.setToolTip("Remove Path", forSegment: 1) @@ -212,52 +232,99 @@ class SettingsViewController: NSViewController, private func setConstraints() { NSLayoutConstraint.activate([ - shortcutsLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: ViewConstants.spacing10), - shortcutsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing10), + shortcutsLabel.topAnchor + .constraint(equalTo: view.topAnchor, + constant: ViewConstants.spacing10), + shortcutsLabel.leadingAnchor + .constraint(equalTo: view.leadingAnchor, + constant: ViewConstants.spacing10), - aboutButton.firstBaselineAnchor.constraint(equalTo: shortcutsLabel.firstBaselineAnchor), - aboutButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing10), + aboutButton.firstBaselineAnchor + .constraint(equalTo: shortcutsLabel.firstBaselineAnchor), + aboutButton.trailingAnchor + .constraint(equalTo: view.trailingAnchor, + constant: -ViewConstants.spacing10), - ctrlButton.topAnchor.constraint(equalTo: shortcutsLabel.bottomAnchor, constant: ViewConstants.spacing10), - ctrlButton.leadingAnchor.constraint(equalTo: shortcutsLabel.leadingAnchor), + ctrlButton.topAnchor + .constraint(equalTo: shortcutsLabel.bottomAnchor, + constant: ViewConstants.spacing10), + ctrlButton.leadingAnchor + .constraint(equalTo: shortcutsLabel.leadingAnchor), - cmdButton.centerYAnchor.constraint(equalTo: ctrlButton.centerYAnchor), - cmdButton.leadingAnchor.constraint(equalTo: ctrlButton.trailingAnchor, constant: ViewConstants.spacing5), + cmdButton.centerYAnchor + .constraint(equalTo: ctrlButton.centerYAnchor), + cmdButton.leadingAnchor + .constraint(equalTo: ctrlButton.trailingAnchor, + constant: ViewConstants.spacing5), - optButton.centerYAnchor.constraint(equalTo: ctrlButton.centerYAnchor), - optButton.leadingAnchor.constraint(equalTo: cmdButton.trailingAnchor, constant: ViewConstants.spacing5), + optButton.centerYAnchor + .constraint(equalTo: ctrlButton.centerYAnchor), + optButton.leadingAnchor + .constraint(equalTo: cmdButton.trailingAnchor, + constant: ViewConstants.spacing5), - shiftButton.centerYAnchor.constraint(equalTo: ctrlButton.centerYAnchor), - shiftButton.leadingAnchor.constraint(equalTo: optButton.trailingAnchor, constant: ViewConstants.spacing5), + shiftButton.centerYAnchor + .constraint(equalTo: ctrlButton.centerYAnchor), + shiftButton.leadingAnchor + .constraint(equalTo: optButton.trailingAnchor, + constant: ViewConstants.spacing5), - plusLabel.centerYAnchor.constraint(equalTo: ctrlButton.centerYAnchor), - plusLabel.leadingAnchor.constraint(equalTo: shiftButton.trailingAnchor, constant: ViewConstants.spacing5), + plusLabel.centerYAnchor + .constraint(equalTo: ctrlButton.centerYAnchor), + plusLabel.leadingAnchor + .constraint(equalTo: shiftButton.trailingAnchor, + constant: ViewConstants.spacing5), recordButton.widthAnchor.constraint(equalToConstant: 40), - recordButton.centerYAnchor.constraint(equalTo: ctrlButton.centerYAnchor), - recordButton.leadingAnchor.constraint(equalTo: plusLabel.trailingAnchor, constant: ViewConstants.spacing5), + recordButton.centerYAnchor + .constraint(equalTo: ctrlButton.centerYAnchor), + recordButton.leadingAnchor + .constraint(equalTo: plusLabel.trailingAnchor, + constant: ViewConstants.spacing5), - pathsLabel.topAnchor.constraint(equalTo: ctrlButton.bottomAnchor, constant: ViewConstants.spacing20), - pathsLabel.leadingAnchor.constraint(equalTo: shortcutsLabel.leadingAnchor), + pathsLabel.topAnchor + .constraint(equalTo: ctrlButton.bottomAnchor, + constant: ViewConstants.spacing20), + pathsLabel.leadingAnchor + .constraint(equalTo: shortcutsLabel.leadingAnchor), tableScrollView.widthAnchor.constraint(equalToConstant: 350), tableScrollView.heightAnchor.constraint(equalToConstant: 150), - tableScrollView.topAnchor.constraint(equalTo: pathsLabel.bottomAnchor), - tableScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + tableScrollView.topAnchor + .constraint(equalTo: pathsLabel.bottomAnchor), + tableScrollView.leadingAnchor + .constraint(equalTo: view.leadingAnchor), + tableScrollView.trailingAnchor + .constraint(equalTo: view.trailingAnchor), - pathsControl.topAnchor.constraint(equalTo: tableScrollView.bottomAnchor, constant: ViewConstants.spacing10), - pathsControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ViewConstants.spacing10), + pathsControl.topAnchor + .constraint(equalTo: tableScrollView.bottomAnchor, + constant: ViewConstants.spacing10), + pathsControl.leadingAnchor + .constraint(equalTo: view.leadingAnchor, + constant: ViewConstants.spacing10), - launchAtLoginLabel.topAnchor.constraint(equalTo: pathsControl.bottomAnchor, constant: ViewConstants.spacing10), - launchAtLoginLabel.trailingAnchor.constraint(equalTo: launchAtLoginToggle.leadingAnchor, constant: -ViewConstants.spacing10), + launchAtLoginLabel.topAnchor + .constraint(equalTo: pathsControl.bottomAnchor, + constant: ViewConstants.spacing10), + launchAtLoginLabel.trailingAnchor + .constraint(equalTo: launchAtLoginToggle.leadingAnchor, + constant: -ViewConstants.spacing10), - launchAtLoginToggle.firstBaselineAnchor.constraint(equalTo: launchAtLoginLabel.firstBaselineAnchor), - launchAtLoginToggle.trailingAnchor.constraint(equalTo: resetAllButton.leadingAnchor, constant: -ViewConstants.spacing15), + launchAtLoginToggle.firstBaselineAnchor + .constraint(equalTo: launchAtLoginLabel.firstBaselineAnchor), + launchAtLoginToggle.trailingAnchor + .constraint(equalTo: resetAllButton.leadingAnchor, + constant: -ViewConstants.spacing15), - resetAllButton.firstBaselineAnchor.constraint(equalTo: launchAtLoginLabel.firstBaselineAnchor), - resetAllButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -ViewConstants.spacing10), - resetAllButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -ViewConstants.spacing10), + resetAllButton.firstBaselineAnchor + .constraint(equalTo: launchAtLoginLabel.firstBaselineAnchor), + resetAllButton.trailingAnchor + .constraint(equalTo: view.trailingAnchor, + constant: -ViewConstants.spacing10), + resetAllButton.bottomAnchor + .constraint(equalTo: view.bottomAnchor, + constant: -ViewConstants.spacing10), ]) } @@ -298,10 +365,14 @@ class SettingsViewController: NSViewController, super.viewWillAppear() // Fetch the saved key codes and modifiers. - if let code = UserDefaults.standard.object(forKey: "keyCode") as? Int { + if let code = + UserDefaults.standard.object(forKey: "keyCode") as? Int + { keyCode = code } - if let mods = UserDefaults.standard.object(forKey: "keyModifiers") as? Int { + if let mods = + UserDefaults.standard.object(forKey: "keyModifiers") as? Int + { modifiers = mods } @@ -317,7 +388,8 @@ class SettingsViewController: NSViewController, override func viewWillDisappear() { super.viewWillDisappear() - HotKeyManager.shared.registerHotKey(key: keyCode, modifiers: modifiers) + HotKeyManager.shared.registerHotKey(key: keyCode, + modifiers: modifiers) UserDefaults.standard.set(keyCode, forKey: "keyCode") UserDefaults.standard.set(modifiers, forKey: "keyModifiers") @@ -377,7 +449,8 @@ class SettingsViewController: NSViewController, private func reset() { keyCode = Int(kVK_Space) modifiers = Int(optionKey) - HotKeyManager.shared.registerHotKey(key: keyCode, modifiers: modifiers) + HotKeyManager.shared.registerHotKey(key: keyCode, + modifiers: modifiers) UserDefaults.standard.set(keyCode, forKey: "keyCode") UserDefaults.standard.set(modifiers, forKey: "keyModifiers") syncModifierButtons() @@ -449,11 +522,16 @@ class SettingsViewController: NSViewController, case 0: let row = paths.count paths.append("") - pathsTableView.insertRows(at: IndexSet(integer: row), withAnimation: []) + pathsTableView.insertRows(at: IndexSet(integer: row), + withAnimation: []) pathsTableView.scrollRowToVisible(row) - pathsTableView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false) - (pathsTableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? PathsTableCellView)?.startEditing() + pathsTableView.selectRowIndexes(IndexSet(integer: row), + byExtendingSelection: false) + ( + pathsTableView + .view(atColumn: 0, row: row, makeIfNecessary: false + ) as? PathsTableCellView)?.startEditing() break case 1: if pathsTableView.selectedRow > -1 { @@ -484,9 +562,16 @@ class SettingsViewController: NSViewController, @objc private func editItem(_ sender: NSTableView) { pathsTableView.deselectAll(nil) - pathsTableView.selectRowIndexes(IndexSet(integer: pathsTableView.clickedRow), byExtendingSelection: false) + pathsTableView.selectRowIndexes( + IndexSet(integer: pathsTableView.clickedRow), + byExtendingSelection: false + ) - if let cell = pathsTableView.view(atColumn: 0, row: pathsTableView.clickedRow, makeIfNecessary: false) as? PathsTableCellView { + if let cell = pathsTableView.view(atColumn: 0, + row: pathsTableView.clickedRow, + makeIfNecessary: false) as? + PathsTableCellView + { cell.startEditing() } } @@ -521,7 +606,9 @@ class SettingsViewController: NSViewController, delegate.window.level = .statusBar delegate.window.makeKeyAndOrderFront(nil) - if let controller = delegate.window.contentViewController as? SearchViewController { + if let controller = + delegate.window.contentViewController as? SearchViewController + { controller.openSettings() } } @@ -533,7 +620,8 @@ class SettingsViewController: NSViewController, func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - let rect = NSRect(x: 0, y: 0, width: tableColumn!.width, height: 20) + let rect = NSRect(x: 0, y: 0, width: tableColumn!.width, + height: 20) let cell = PathsTableCellView(frame: rect) cell.titleField.stringValue = paths[row] cell.delegate = self