// // AppDelegate.swift // ScrollSwitcher // // Created by Shadowfacts on 8/31/21. // import Cocoa let defaultsKey = "com.apple.swipescrolldirection" class AppDelegate: NSObject, NSApplicationDelegate { // things that need to be retained to keep them from disappearing private var prefPanesSupport: Bundle! private var item: NSStatusItem! func applicationDidFinishLaunching(_ aNotification: Notification) { item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) item.button!.target = self item.button!.action = #selector(menuItemClicked) updateIcon() // update the icon when system prefs changes DistributedNotificationCenter.default().addObserver(self, selector: #selector(updateIcon), name: .swipeScrollDirectionDidChangeNotification, object: nil) } @objc private func updateIcon() { item.button!.image = Direction.current.image } @objc private func menuItemClicked() { if let event = NSApp.currentEvent, event.modifierFlags.contains(.shift) { item.menu = createMenu() item.button!.performClick(nil) } else { toggleScrollDirection() } } private func createMenu() -> NSMenu { let menu = NSMenu() menu.delegate = self let state = Direction.current == .natural ? "On" : "Off" let status = NSMenuItem(title: "Natural Scrolling: \(state)", action: nil, keyEquivalent: "") menu.addItem(status) let verb = Direction.current == .natural ? "Disable" : "Enable" let toggleItem = NSMenuItem(title: "\(verb) Natural Scrolling", action: #selector(toggleScrollDirection), keyEquivalent: "") menu.addItem(toggleItem) menu.addItem(NSMenuItem.separator()) menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") return menu } @objc private func toggleScrollDirection() { setDirection(.current == Direction.natural ? .normal : .natural) } private func setDirection(_ new: Direction) { // can't construct a UserDefaults with NSGlobalDomain, so set it via the command line let proc = Process() // let out = Pipe() // let err = Pipe() proc.launchPath = "/usr/bin/env" let newVal = new == .normal ? "NO" : "YES" proc.arguments = ["defaults", "write", "-g", "com.apple.swipescrolldirection", "-bool", newVal] // proc.standardOutput = out // proc.standardError = err proc.launch() proc.waitUntilExit() // let outData = out.fileHandleForReading.readDataToEndOfFile() // let errData = err.fileHandleForReading.readDataToEndOfFile() if proc.terminationStatus != 0 { fatalError("uh oh, exit code: \(proc.terminationStatus)") } // makes system preferences update DistributedNotificationCenter.default().postNotificationName(.swipeScrollDirectionDidChangeNotification, object: nil, userInfo: nil, deliverImmediately: false) // make the actual input behavior update setSwipeScrollDirection(new == .natural) } } extension AppDelegate: NSMenuDelegate { func menuDidClose(_ menu: NSMenu) { // remove the menu so the next time the item is clicked, it performs the primary action item.menu = nil } } enum Direction: Equatable { case normal, natural static var current: Direction { let value = UserDefaults.standard.bool(forKey: defaultsKey) if value { return .natural } else { return .normal } } var image: NSImage { switch self { case .normal: return NSImage(systemSymbolName: "scroll", accessibilityDescription: nil)! case .natural: return NSImage(systemSymbolName: "scroll.fill", accessibilityDescription: nil)! } } } extension Notification.Name { static let swipeScrollDirectionDidChangeNotification = Notification.Name(rawValue: "SwipeScrollDirectionDidChangeNotification") }