From c3f7a27bddb2f008c8c33baecdcd173aee6364e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20H=C3=BClsmann?= Date: Mon, 24 Oct 2022 00:06:35 +0200 Subject: [PATCH] Clean up permission handling --- SaneSideButtons.xcodeproj/project.pbxproj | 4 +- SaneSideButtons/AppDelegate.swift | 57 ++++---- SaneSideButtons/PermissionView.swift | 164 +++++++++++----------- SaneSideButtons/SwipeSimulator.swift | 13 +- 4 files changed, 122 insertions(+), 116 deletions(-) diff --git a/SaneSideButtons.xcodeproj/project.pbxproj b/SaneSideButtons.xcodeproj/project.pbxproj index 3ad0ced..8e729cf 100644 --- a/SaneSideButtons.xcodeproj/project.pbxproj +++ b/SaneSideButtons.xcodeproj/project.pbxproj @@ -312,7 +312,7 @@ CODE_SIGN_ENTITLEMENTS = SaneSideButtons/SaneSideButtons.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = B7HSFEB698; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; @@ -345,7 +345,7 @@ CODE_SIGN_ENTITLEMENTS = SaneSideButtons/SaneSideButtons.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 12; + CURRENT_PROJECT_VERSION = 16; DEVELOPMENT_TEAM = B7HSFEB698; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/SaneSideButtons/AppDelegate.swift b/SaneSideButtons/AppDelegate.swift index 9f9f1fd..5c74696 100644 --- a/SaneSideButtons/AppDelegate.swift +++ b/SaneSideButtons/AppDelegate.swift @@ -56,7 +56,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { // MARK: - NSApplicationDelegate func applicationDidFinishLaunching(_ aNotification: Notification) { - self.setupPermissions() + self.setupTapWithPermissions() self.setupMenuBarExtra() } @@ -123,29 +123,29 @@ private extension AppDelegate { SwipeSimulator.shared.removeIgnoredApplication(bundleID: currentFrontAppBundleID) } - // MARK: - Permissions + // MARK: - Setup & Permissions - private func setupPermissions() { - if self.hasPermissions() { - SwipeSimulator.shared.setupEventTap() - } else { - if self.getEventPermission() { - SwipeSimulator.shared.setupEventTap() - } - Task { - await self.promptPermissions() + private func setupTapWithPermissions() { + self.getEventPermission() + do { + try SwipeSimulator.shared.setupEventTap() + } catch { + if window == nil { + Task { + await self.promptPermissions() + } } } } - private func hasPermissions() -> Bool { - if getEventPermission() && getAccessibilityPermission() { - return true - } - return false - } +// private func hasPermissions() -> Bool { +// if getEventPermission() && hasAccessibilityPermission() { +// return true +// } +// return false +// } - private func getEventPermission() -> Bool { + @discardableResult private func getEventPermission() -> Bool { if !CGPreflightListenEventAccess() { CGRequestListenEventAccess() return false @@ -153,26 +153,28 @@ private extension AppDelegate { return true } - private func getAccessibilityPermission() -> Bool { - let prompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String - let options = [prompt: true] as CFDictionary - return AXIsProcessTrustedWithOptions(options) - } +// private func hasAccessibilityPermission() -> Bool { +// let prompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String +// let options = [prompt: true] as CFDictionary +// return AXIsProcessTrustedWithOptions(options) +// } @MainActor @objc private func promptPermissions() { NSApplication.shared.activate(ignoringOtherApps: true) self.window = NSWindow( contentRect: NSRect(), - styleMask: [.closable, .titled], + styleMask: [.closable, .titled, .fullSizeContentView], backing: .buffered, defer: false) self.window?.isReleasedWhenClosed = false self.window?.titlebarAppearsTransparent = true self.window?.titleVisibility = .hidden + self.window?.toolbar = NSToolbar() self.window?.standardWindowButton(.miniaturizeButton)?.isHidden = true self.window?.standardWindowButton(.zoomButton)?.isHidden = true - let permissionView = PermissionView(closeWindow: self.closePermissionsPrompt, - hasPermissions: self.hasPermissions) + let permissionView = PermissionView(closeWindow: self.closePermissionsPrompt) self.window?.contentView = NSHostingView(rootView: permissionView) + self.window?.isOpaque = false + self.window?.backgroundColor = NSColor(white: 1, alpha: 0) self.window?.center() self.window?.makeKeyAndOrderFront(nil) self.window?.delegate = self @@ -181,7 +183,6 @@ private extension AppDelegate { func closePermissionsPrompt() { self.window?.close() self.window = nil - self.setupPermissions() } } @@ -190,7 +191,7 @@ private extension AppDelegate { extension AppDelegate: NSMenuDelegate { func menuWillOpen(_ menu: NSMenu) { // Permission Detection - if !self.hasPermissions() { + if !SwipeSimulator.shared.eventTapIsRunning { NSApplication.shared.activate(ignoringOtherApps: true) if self.window == nil { self.promptPermissions() diff --git a/SaneSideButtons/PermissionView.swift b/SaneSideButtons/PermissionView.swift index 74ae3da..aabb2e3 100644 --- a/SaneSideButtons/PermissionView.swift +++ b/SaneSideButtons/PermissionView.swift @@ -10,107 +10,105 @@ import SwiftUI struct PermissionView: View { private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() let closeWindow: () -> Void - let hasPermissions: () -> Bool var body: some View { VStack { + VStack { + Text("Authorize SaneSideButtons") + .font(.title) + .fontWeight(.medium) + Text("We'll have you up and running in just a minute!") + .font(.subheadline) + .fontWeight(.medium) + }.padding(.vertical, 10) ZStack { - Rectangle() - .frame(width: 500, height: 100) - .foregroundColor(Color("SecondaryColor")) - VStack { - Text("Authorize SaneSideButtons") - .font(.title) - .fontWeight(.medium) - Text("We'll have you up and running in just a minute!") - .font(.subheadline) - } - }.offset(y: -30) - ZStack { - Rectangle() - .frame(width: 500, height: 40) + RoundedRectangle(cornerRadius: 5, style: .continuous) + .frame(width: 600, height: 200) .foregroundColor(Color("BackgroundColor")) - .offset(y: -120) - VStack { - // swiftlint:disable line_length - Text("SaneSideButtons needs your permission to detect mouse events and trigger actions in applications. Follow these steps to authorize it:") - // swiftlint:enable line_length - .font(.subheadline) - .multilineTextAlignment(.center) - .padding(.horizontal, 40) - .padding(.bottom, 30) - HStack(alignment: .top) { - VStack { - Image(systemName: "1.circle.fill") - .font(.title) - .foregroundColor(Color.gray) - Text("Go to System Settings") - .font(.callout) - .padding(7) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(.blue, lineWidth: 1) - ) - .padding(.top, 30) - }.padding(.horizontal, 5) - - VStack { - Image(systemName: "2.circle.fill") - .font(.title) - .foregroundColor(Color.gray) - Text("Privacy & Security") - .font(.callout) - .padding(7) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(.blue, lineWidth: 1) - ) - .padding(.top, 30) - }.padding(.horizontal, 5) - - VStack { - Image(systemName: "3.circle.fill") - .font(.title) - .foregroundColor(Color.gray) - Text("Add SaneSideButtons to Accessibility & Input Monitoring") - .font(.callout) - .multilineTextAlignment(.center) - .padding(7) - .overlay( - RoundedRectangle(cornerRadius: 16) - .stroke(.blue, lineWidth: 1) - ) - .padding(.top, 15) - }.padding(.horizontal, 5) - } - } - .offset(y: -40) - .padding(.horizontal, 20) + .padding(10) + .padding(.bottom, 20) + PermissionContentView() } - .onReceive(timer) { _ in - self.pollPermissions() - } - .frame(width: 500, height: 200) - .background(Color("BackgroundColor")) + + } + .background(.thinMaterial) + .onReceive(timer) { _ in + self.pollPermissions() } } private func pollPermissions() { - if self.hasPermissions() { + do { + try SwipeSimulator.shared.setupEventTap() self.closeWindow() + } catch { + return + } + } +} + +struct PermissionContentView: View { + var body: some View { + VStack { + // swiftlint:disable line_length + Text("SaneSideButtons needs your permission to detect mouse events and trigger actions in applications. Follow these steps to authorize it:") + // swiftlint:enable line_length + .font(.subheadline) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + .padding(.bottom, 30) + HStack(alignment: .top) { + VStack { + Image(systemName: "1.circle.fill") + .font(.title) + .foregroundColor(Color.gray) + Text("Go to System Settings") + .font(.callout) + .padding(7) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(.blue, lineWidth: 1) + ) + .padding(.top, 30) + }.padding(.horizontal, 5) + + VStack { + Image(systemName: "2.circle.fill") + .font(.title) + .foregroundColor(Color.gray) + Text("Privacy & Security") + .font(.callout) + .padding(7) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(.blue, lineWidth: 1) + ) + .padding(.top, 30) + }.padding(.horizontal, 5) + + VStack { + Image(systemName: "3.circle.fill") + .font(.title) + .foregroundColor(Color.gray) + Text("Add SaneSideButtons to Accessibility & Input Monitoring") + .font(.callout) + .multilineTextAlignment(.center) + .padding(7) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(.blue, lineWidth: 1) + ) + .padding(.top, 15) + }.padding(.horizontal, 5) + } } } } struct PermissionView_Previews: PreviewProvider { static var previews: some View { - PermissionView(closeWindow: self.closeWindow, - hasPermissions: self.hasPermissions) + PermissionView(closeWindow: self.closeWindow) } static func closeWindow() { } - - static func hasPermissions() -> Bool { - return false - } } diff --git a/SaneSideButtons/SwipeSimulator.swift b/SaneSideButtons/SwipeSimulator.swift index cde71bb..5d0dc23 100644 --- a/SaneSideButtons/SwipeSimulator.swift +++ b/SaneSideButtons/SwipeSimulator.swift @@ -9,6 +9,7 @@ import AppKit final class SwipeSimulator { static let shared = SwipeSimulator() + private(set) var eventTapIsRunning: Bool = false private let swipeBegin = [ kTLInfoKeyGestureSubtype: kTLInfoSubtypeSwipe, @@ -29,6 +30,10 @@ final class SwipeSimulator { var ignoredApplications: [String] = UserDefaults.standard.stringArray(forKey: "ignoredApplications") ?? [] + enum EventTap: Error { + case failedSetup + } + private init() { } fileprivate func SBFFakeSwipe(direction: TLInfoSwipeDirection) { @@ -66,7 +71,8 @@ final class SwipeSimulator { UserDefaults.standard.set(self.ignoredApplications, forKey: "ignoredApplications") } - func setupEventTap() { + func setupEventTap() throws { + guard !eventTapIsRunning else { return } let eventMask = CGEventMask(1 << CGEventType.otherMouseDown.rawValue | 1 << CGEventType.otherMouseUp.rawValue) guard let eventTap = CGEvent.tapCreate(tap: .cghidEventTap, place: .headInsertEventTap, @@ -74,12 +80,13 @@ final class SwipeSimulator { eventsOfInterest: eventMask, callback: mouseEventCallBack, userInfo: nil) else { - print("Failed to create eventTap") - return + self.eventTapIsRunning = false + throw EventTap.failedSetup } let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0) CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) CGEvent.tapEnable(tap: eventTap, enable: true) + self.eventTapIsRunning = true } }