From 6ef0a6f9e86edd676976ba890844a8d03e502df0 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Tue, 14 Jul 2020 20:27:02 +0900 Subject: [PATCH] feat: better system permissions onboarding (closes #127) --- alt-tab-macos.xcodeproj/project.pbxproj | 29 ++++- resources/icons/accessibility@2x.png | Bin 0 -> 6770 bytes resources/icons/screen-recording@2x.png | Bin 0 -> 1129 bytes resources/l10n/Localizable.strings | 27 +++++ resources/l10n/ar.lproj/Localizable.strings | 27 +++++ resources/l10n/de.lproj/Localizable.strings | 35 +++++- resources/l10n/en.lproj/Localizable.strings | 27 +++++ resources/l10n/es.lproj/Localizable.strings | 27 +++++ resources/l10n/fi.lproj/Localizable.strings | 27 +++++ resources/l10n/fr.lproj/Localizable.strings | 27 +++++ resources/l10n/hu.lproj/Localizable.strings | 27 +++++ resources/l10n/it.lproj/Localizable.strings | 27 +++++ resources/l10n/ja.lproj/Localizable.strings | 27 +++++ resources/l10n/ko.lproj/Localizable.strings | 27 +++++ resources/l10n/nl.lproj/Localizable.strings | 27 +++++ .../l10n/pt-BR.lproj/Localizable.strings | 27 +++++ resources/l10n/ru.lproj/Localizable.strings | 27 +++++ resources/l10n/tr.lproj/Localizable.strings | 27 +++++ .../l10n/zh-CN.lproj/Localizable.strings | 27 +++++ .../l10n/zh-TW.lproj/Localizable.strings | 35 +++++- src/api-wrappers/PrivateApis.swift | 1 + src/logic/SystemPermissions.swift | 100 +++++++++++++----- src/ui/App.swift | 54 +++++----- src/ui/generic-components/StackView.swift | 6 +- .../generic-components/text/BoldLabel.swift | 6 +- .../generic-components/text/TitleLabel.swift | 9 ++ src/ui/permission-window/PermissionView.swift | 62 +++++++++++ .../permission-window/PermissionsWindow.swift | 72 +++++++++++++ 28 files changed, 748 insertions(+), 66 deletions(-) create mode 100644 resources/icons/accessibility@2x.png create mode 100644 resources/icons/screen-recording@2x.png create mode 100644 src/ui/generic-components/text/TitleLabel.swift create mode 100644 src/ui/permission-window/PermissionView.swift create mode 100644 src/ui/permission-window/PermissionsWindow.swift diff --git a/alt-tab-macos.xcodeproj/project.pbxproj b/alt-tab-macos.xcodeproj/project.pbxproj index 06317bc6..1ce5844c 100644 --- a/alt-tab-macos.xcodeproj/project.pbxproj +++ b/alt-tab-macos.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ D04BA004884A273D4D2D3EF1 /* HelperExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD91161791D42FEC4A60 /* HelperExtensions.swift */; }; D04BA0182F4734A5C8BB63DC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA5461DAEE9B5350C3020 /* Localizable.strings */; }; D04BA03B560B681AF96E4610 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA88ED495C54CFE0B7B87 /* InfoPlist.strings */; }; + D04BA079F61177D470F3E6C1 /* accessibility@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BA303761CFDD9BE8B6ECB /* accessibility@2x.png */; }; D04BA084CD1236EC78D90A01 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BACCBE5F97BE9B6CA645B /* Localizable.strings */; }; D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA5D9B4EBEB9E4333AE39 /* UserDefaultsEvents.swift */; }; D04BA0DD6819010987B578D1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA07BBF2237CC6C72D264 /* Localizable.strings */; }; @@ -29,6 +30,7 @@ D04BA26A691D56031FCCF00C /* Sysctl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8DB8AA7E5570DAC568A /* Sysctl.swift */; }; D04BA276B3241D440F65B149 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA5C2BB394F1624DD5B45 /* InfoPlist.strings */; }; D04BA2A6FF9DDDC5A1A68E36 /* Applications.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA282BB16C1554595A968 /* Applications.swift */; }; + D04BA2B4364250FA07EE5793 /* screen-recording@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BA98B2A0815540E319908 /* screen-recording@2x.png */; }; D04BA2D8099594E1593EC8EF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA88EDDDD6CB3FBE71FC5 /* InfoPlist.strings */; }; D04BA2E64C59D96F6EB27D9D /* FeedbackWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA26C75F76C277653C932 /* FeedbackWindow.swift */; }; D04BA34AC850A273AB288B1E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA3B51D05213404938366 /* Localizable.strings */; }; @@ -36,6 +38,7 @@ D04BA3C24F4F644EA91DE38C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA717693DA18CB74BAED1 /* Localizable.strings */; }; D04BA3CF766857381519B892 /* BackgroundWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAB74451B79FE18B8BEDF /* BackgroundWork.swift */; }; D04BA40CC1415DA69CCE5D89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA17FC84640580894400E /* InfoPlist.strings */; }; + D04BA446D702C5E252AF2319 /* TitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAED1C4D6BB8E24847E79 /* TitleLabel.swift */; }; D04BA4575B13F1A148C108E2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA459B8804ABFBDA50663 /* InfoPlist.strings */; }; D04BA48B00B4211A465C7337 /* DebugProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACABD048E62EBE4576CC /* DebugProfile.swift */; }; D04BA4A11F821548EE3C5E95 /* Bash.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA514F1C4B475B3CA6EB6 /* Bash.swift */; }; @@ -52,6 +55,7 @@ D04BA7B8D599E1A7A27FF5AE /* AcknowledgmentsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA788228BA86D9EFBD1ED /* AcknowledgmentsTab.swift */; }; D04BA7B9421871EDBD58F4E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BABA255CD25D8724A06CD /* Localizable.strings */; }; D04BA7BE7F3DD24D58ACE942 /* AppearanceTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA64F1F344007EA13BA05 /* AppearanceTab.swift */; }; + D04BA7E39FA539DD8316447A /* PermissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA0726EABD146753B9EF8 /* PermissionView.swift */; }; D04BA7F86F1926FBE31F44BF /* BaseLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA53992F116E5E704CAB3 /* BaseLabel.swift */; }; D04BA8092885B40CE3527370 /* UpdatesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAD60C97E609A759E721E /* UpdatesTab.swift */; }; D04BA83AF2B1C3C5166C119F /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAD58DF3FA085A45BEB50 /* InfoPlist.strings */; }; @@ -88,6 +92,7 @@ D04BAE8B16A06A10E2FA94DE /* AccessibilityEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA8FDCF137D892114F5F3 /* AccessibilityEvents.swift */; }; D04BAEE31B6FFCDC779E6C17 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D04BAC2FF99F629CD4ED20FC /* MainMenu.xib */; }; D04BAF12DF5D15B9D7D316A4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA61693F710CD7BD054D7 /* InfoPlist.strings */; }; + D04BAFB90E00AA5D662EAF24 /* PermissionsWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA9DF10D0CC4CA0C8F814 /* PermissionsWindow.swift */; }; D04BAFB973C3D28718FAEB87 /* Windows.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BACD976030676FD0761D5 /* Windows.swift */; }; D04BAFBC862BA5FE0294EA7A /* AXUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA6F823BC0EDA9AA4B80A /* AXUIElement.swift */; }; /* End PBXBuildFile section */ @@ -99,6 +104,7 @@ C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_alt_tab_macos.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D04BA015A45DE7AFDC9794FE /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; D04BA03200F5A8FC0CD03607 /* CGWindowID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGWindowID.swift; sourceTree = ""; }; + D04BA0726EABD146753B9EF8 /* PermissionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionView.swift; sourceTree = ""; }; D04BA075E359F36C27CD5BAE /* package-lock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "package-lock.json"; sourceTree = ""; }; D04BA084F638725465234830 /* favicon-16x16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "favicon-16x16.png"; sourceTree = ""; }; D04BA088B8A550D9367A4AAB /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = InfoPlist.strings; sourceTree = ""; }; @@ -130,6 +136,7 @@ D04BA2D022B64A876CCA6BB7 /* apple-touch-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "apple-touch-icon.png"; sourceTree = ""; }; D04BA2E723062A1DAC58618F /* setup_ci_pr.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = setup_ci_pr.sh; sourceTree = ""; }; D04BA2E8D5D5CC23456BC8F7 /* tr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = tr; path = Localizable.strings; sourceTree = ""; }; + D04BA303761CFDD9BE8B6ECB /* accessibility@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "accessibility@2x.png"; sourceTree = ""; }; D04BA32F25860B686DFE818A /* 3 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "3 windows - 1 line.jpg"; sourceTree = ""; }; D04BA3632A2EB5C64A3FEE59 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = InfoPlist.strings; sourceTree = ""; }; D04BA369B4D9FD6A13936F53 /* bare.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = bare.html; sourceTree = ""; }; @@ -201,7 +208,9 @@ D04BA93F02C9B26B05032B7A /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = Localizable.strings; sourceTree = ""; }; D04BA94A1695D4FF5337C84B /* import_certificate_into_main_keychain.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = import_certificate_into_main_keychain.sh; sourceTree = ""; }; D04BA954319227829B646315 /* android-chrome-192x192.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "android-chrome-192x192.png"; sourceTree = ""; }; + D04BA98B2A0815540E319908 /* screen-recording@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "screen-recording@2x.png"; sourceTree = ""; }; D04BA9B93823398A542FF7A0 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + D04BA9DF10D0CC4CA0C8F814 /* PermissionsWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionsWindow.swift; sourceTree = ""; }; D04BA9EF65B2E7AF9E3ADCA3 /* 2 windows - 1 line.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "2 windows - 1 line.jpg"; sourceTree = ""; }; D04BAA127E73B3114B75098A /* Gemfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Gemfile; sourceTree = ""; }; D04BAA4442D0CE3A4972937F /* nl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nl; path = InfoPlist.strings; sourceTree = ""; }; @@ -258,6 +267,7 @@ D04BAE5D665680CB4B13CA26 /* app-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "app-icon.icns"; sourceTree = ""; }; D04BAE93A5854C501639C640 /* update_homebrew_cask.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = update_homebrew_cask.sh; sourceTree = ""; }; D04BAE94A2FD4D9B72830B43 /* favicon-32x32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "favicon-32x32.png"; sourceTree = ""; }; + D04BAED1C4D6BB8E24847E79 /* TitleLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleLabel.swift; sourceTree = ""; }; D04BAED450528DC3C28FABDE /* Changelog.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Changelog.md; sourceTree = ""; }; D04BAEF1D194C2379C9970F1 /* setup_ci_master.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = setup_ci_master.sh; sourceTree = ""; }; D04BAF076A30A1BAFEDBEA66 /* 5 windows - 2 lines.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "5 windows - 2 lines.jpg"; sourceTree = ""; }; @@ -297,7 +307,6 @@ 672F94799CC7C90282AEB3EA /* Pods-alt-tab-macos.debug.xcconfig */, 59DD4BE63D42EEFF1182BA7F /* Pods-alt-tab-macos.release.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -392,6 +401,15 @@ name = Products; sourceTree = ""; }; + D04BA1C271F0DA573345B860 /* permission-window */ = { + isa = PBXGroup; + children = ( + D04BA9DF10D0CC4CA0C8F814 /* PermissionsWindow.swift */, + D04BA0726EABD146753B9EF8 /* PermissionView.swift */, + ); + path = "permission-window"; + sourceTree = ""; + }; D04BA229883E5D540D00232D /* ui */ = { isa = PBXGroup; children = ( @@ -401,6 +419,7 @@ D04BAD905546AA93E5117B0A /* Menubar.swift */, D04BA26C75F76C277653C932 /* FeedbackWindow.swift */, D04BA2CBF267F267225CA671 /* generic-components */, + D04BA1C271F0DA573345B860 /* permission-window */, ); path = ui; sourceTree = ""; @@ -604,6 +623,8 @@ D04BA1C3E42AC44CA2C5D3D8 /* app-icon.svg */, D04BAE5D665680CB4B13CA26 /* app-icon.icns */, D04BA4356B4F575E66B32298 /* app-icon.iconset */, + D04BA303761CFDD9BE8B6ECB /* accessibility@2x.png */, + D04BA98B2A0815540E319908 /* screen-recording@2x.png */, ); path = icons; sourceTree = ""; @@ -702,6 +723,7 @@ D04BA7C836A8CE8C0B8D128B /* TextArea.swift */, D04BA53992F116E5E704CAB3 /* BaseLabel.swift */, D04BA5BD11C365C19461458C /* TextField.swift */, + D04BAED1C4D6BB8E24847E79 /* TitleLabel.swift */, ); path = text; sourceTree = ""; @@ -929,6 +951,8 @@ D04BAC463D66BB8E19CF8E2E /* InfoPlist.strings in Resources */, D04BA7B9421871EDBD58F4E3 /* Localizable.strings in Resources */, D04BA530F00E5989D66C6A1F /* Contributors.md in Resources */, + D04BA079F61177D470F3E6C1 /* accessibility@2x.png in Resources */, + D04BA2B4364250FA07EE5793 /* screen-recording@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1054,6 +1078,9 @@ D04BA1766FBCB2E941D081A5 /* WorkspaceEvents.swift in Sources */, D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */, D04BA4A11F821548EE3C5E95 /* Bash.swift in Sources */, + D04BAFB90E00AA5D662EAF24 /* PermissionsWindow.swift in Sources */, + D04BA7E39FA539DD8316447A /* PermissionView.swift in Sources */, + D04BA446D702C5E252AF2319 /* TitleLabel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/resources/icons/accessibility@2x.png b/resources/icons/accessibility@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..edd92e3340e4137ccf3b365043ba5b72f4fbc915 GIT binary patch literal 6770 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 UELJJILjV8(07*qoM6N<$f&rEjG5`Po literal 0 HcmV?d00001 diff --git a/resources/icons/screen-recording@2x.png b/resources/icons/screen-recording@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..85cac044a69062392401fd5018678b087e5a732f GIT binary patch literal 1129 zcmV-v1eW`WP)P;^2CXm86xM2V{>Ru%*@QpUdBICU(2u%r>pez^z`)f^jLkqL#PlP0S<_6VL&i)&cETl!RH%qBRaq~KYVV$5WwQu zTO5`w`1)>O7T|z&!1@7Lm}$SnfFCwF`0S0=-(f#Ioy5#Q?F&PpNfIF%A1tgjzn7I$2EsD#B?!5=9^?0YFII8lVh78I_I=R0E(+4r`_W z9T3jc6o>)5NC9^U28ecI$Q?VVSvE?P~gwdw$h zyy(c_)Aj zMRIUjjpVsj0VoE7MX}r1garvK&?F|=e|pJefIw!VK}=4jl}xjM5ChA9egC-`$%>`L zA0mxfZs?BqZx}ELp~*`s{}5UD#ajj}wP>BW7pBX8y9*f5{LeAA^*HN=0RaaLDg~H7 zbw+N)bclaB$r3;`N1<_;X}`>}5$kNV!yJp|&9o?W8erD=z`lI(m*MH>Y_;hj2kiM& z9LMXqy8x4_*PwE?Se!3V2xdu=EP3YDcaDAZ(J#N-YLE2?3s((A11ODx(q9(=^9=}q zGXieA?Y8wd+;F2$<9KXF>ZwrSu4*M#)pq%EDTK&|!{Orj^MBnA_=OR0)m2x8I2UhC zTL1j>&o>~=1dPv^G1CTv0cn~P;;54Ar9c8R2q9uPTxCE0`29G*d+)s$-GBf6O{ap# z08Kvw@4D-*0U(ZJjgJpl9e}8+v*rSjgc;?sD2gbKksnjp;51Bp!wokuD#pfUSQN!X zaST`~w4mOuF3SQyCO$jHm;oe7!YGIt0oPxDz496`DanhE5ePKZ0~l7ip&kTxc_w_a zJmv^6NiJ_JhA|Q1Q;O4%LO3Mr{&0*Op@^Jci%1h v<5 Security & Privacy > Privacy > Screen Recording // returns 1 if checked or 0 if unchecked; also prompts the user the first time if unchecked +// the return value will be the same during the app lifetime; it will not reflect the actual status of the checkbox @_silgen_name("SLSRequestScreenCaptureAccess") @discardableResult func SLSRequestScreenCaptureAccess() -> UInt8 diff --git a/src/logic/SystemPermissions.swift b/src/logic/SystemPermissions.swift index ed7f3878..c588142f 100644 --- a/src/logic/SystemPermissions.swift +++ b/src/logic/SystemPermissions.swift @@ -2,33 +2,85 @@ import Cocoa // macOS has some privacy restrictions. The user needs to grant certain permissions, app by app, in System Preferences > Security & Privacy class SystemPermissions { - static func ensureAccessibilityCheckboxIsChecked() { - guard #available(OSX 10.9, *) else { return } - if !AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): true] as CFDictionary) { - debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Accessibility.", - "Please authorize and re-launch.", - "See https://help.rescuetime.com/article/59-how-do-i-enable-accessibility-permissions-on-mac-osx", - separator: "\n") - App.shared.terminate(self) - } else { - // if the user removes the permission while the app is running, it can break keyboard inputs - DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name("com.apple.accessibility.api"), object: nil, queue: nil) { _ in - // there is a delay between the notification and the permission being actually changed - DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { - ensureAccessibilityCheckboxIsChecked() - } - } + static var changeCallback: (() -> Void)! + static var screenRecordingObserver: Timer! + static var permissionsWindow: PermissionsWindow! + + static func accessibilityIsGranted() -> Bool { + if #available(OSX 10.9, *) { + return AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): false] as CFDictionary) } + return true } - static func ensureScreenRecordingCheckboxIsChecked() { - guard #available(OSX 10.15, *) else { return } - if SLSRequestScreenCaptureAccess() != 1 { - debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Screen Recording.", - "Please authorize and re-launch.", - "See https://dropshare.zendesk.com/hc/en-us/articles/360033453434-Enabling-Screen-Recording-Permission-on-macOS-Catalina-10-15-", - separator: "\n") - App.shared.terminate(self) + static func screenRecordingIsGranted() -> Bool { + if #available(OSX 10.15, *) { + return screenRecordingIsGranted_() + } + return true + } + + // there is no official API to check the status of the Screen Recording permission + // there is the private API SLSRequestScreenCaptureAccess, but its value is not updated during the app lifetime + // workaround: we check if we can get the title of at least one window, except from AltTab or the Dock + private static func screenRecordingIsGranted_() -> Bool { + let appPid = NSRunningApplication.current.processIdentifier + if let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [CGWindow], + let _ = windows.first(where: { (window) -> Bool in + if let windowPid = window.ownerPID(), + windowPid != appPid, + let windowRunningApplication = NSRunningApplication(processIdentifier: windowPid), + windowRunningApplication.executableURL?.lastPathComponent != "Dock", + let _ = window.title() { + return true + } + return false + }) { + return true + } + return false + } + + static func observePermissionsPostStartup() { + screenRecordingObserver = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { _ in + if !(accessibilityIsGranted() && screenRecordingIsGranted()) { + App.app.restart() + } + }) + } + + static func observePermissionsPreStartup(_ startupBlock: @escaping () -> Void) { + // this call triggers the permission prompt, however it's the only way to force the app to be listed with a checkbox + SLSRequestScreenCaptureAccess() + screenRecordingObserver = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { _ in + let accessibility = accessibilityIsGranted() + let screenRecording = screenRecordingIsGranted() + if accessibility && screenRecording { + permissionsWindow.close() + screenRecordingObserver.invalidate() + startupBlock() + } else { + if accessibility != permissionsWindow.accessibilityView.isPermissionGranted { + permissionsWindow.accessibilityView.updatePermissionStatus(accessibility) + } + if #available(OSX 10.15, *), screenRecording != permissionsWindow.screenRecordingView.isPermissionGranted { + permissionsWindow.screenRecordingView.updatePermissionStatus(screenRecording) + } + } + }) + } + + static func ensurePermissionsAreGranted(_ continueAppStartup: @escaping () -> Void) { + let startupBlock = { + observePermissionsPostStartup() + continueAppStartup() + } + if accessibilityIsGranted() && screenRecordingIsGranted() { + startupBlock() + } else { + permissionsWindow = PermissionsWindow() + permissionsWindow.show() + observePermissionsPreStartup(startupBlock) } } } diff --git a/src/ui/App.swift b/src/ui/App.swift index a4fa3c15..afe65cd1 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -35,34 +35,30 @@ class App: NSApplication, NSApplicationDelegate { PFMoveToApplicationsFolderIfNecessary() #endif AXUIElement.setGlobalTimeout() - SystemPermissions.ensureAccessibilityCheckboxIsChecked() - SystemPermissions.ensureScreenRecordingCheckboxIsChecked() - BackgroundWork.start() - Preferences.migratePreferences() - Preferences.registerDefaults() - App.statusItem = Menubar.make() - loadMainMenuXib() - thumbnailsPanel = ThumbnailsPanel() - Spaces.initialDiscovery() - Applications.initialDiscovery() - loadPreferencesWindow() - KeyboardEvents.observe() - // TODO: undeterministic; events in the queue may still be processing; good enough for now - DispatchQueue.main.async { () -> () in Windows.sortByLevel() } - preloadWindows() - - #if DEBUG - showPreferencesPanel() - #endif - + SystemPermissions.ensurePermissionsAreGranted { [weak self] in + guard let self = self else { return } + BackgroundWork.start() + Preferences.migratePreferences() + Preferences.registerDefaults() + App.statusItem = Menubar.make() + self.loadMainMenuXib() + self.thumbnailsPanel = ThumbnailsPanel() + Spaces.initialDiscovery() + Applications.initialDiscovery() + self.loadPreferencesWindow() + KeyboardEvents.observe() + // TODO: undeterministic; events in the queue may still be processing; good enough for now + DispatchQueue.main.async { () -> () in Windows.sortByLevel() } + self.preloadWindows() + } } - + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { NSApp.activate(ignoringOtherApps: true) showPreferencesPanel() return true } - + // pre-load some windows so they are faster on first display private func preloadWindows() { thumbnailsPanel.orderFront(nil) @@ -80,7 +76,7 @@ class App: NSApplication, NSApplicationDelegate { // pre-load tabs so we can interact with them before the user opens the preferences window tabs.forEach { (tab: NSViewController) in tab.loadView() } preferencesWindowController = PreferencesWindowController(preferencePanes: tabs as! [PreferencePane]) - + let window = preferencesWindowController.window! let quitButton = NSButton(title: NSLocalizedString("Quit", comment: ""), target: nil, action: #selector(NSApplication.terminate(_:))) let titleBarView = window.standardWindowButton(.closeButton)!.superview! @@ -89,7 +85,7 @@ class App: NSApplication, NSApplicationDelegate { let topConstraint = NSLayoutConstraint(item: quitButton, attribute: .top, relatedBy: .equal, toItem: titleBarView, attribute: .top, multiplier: 1, constant: 5) let rightConstraint = NSLayoutConstraint(item: quitButton, attribute: .right, relatedBy: .equal, toItem: titleBarView, attribute: .right, multiplier: 1, constant: -10) titleBarView.addConstraints([topConstraint, rightConstraint]) - + } // keyboard shortcuts are broken without a menu. We generated the default menu from XCode and load it @@ -106,6 +102,16 @@ class App: NSApplication, NSApplicationDelegate { thumbnailsPanel.thumbnailsView.layer!.cornerRadius = Preferences.windowCornerRadius } + func restart() { + let url = URL(fileURLWithPath: Bundle.main.resourcePath!) + let path = url.deletingLastPathComponent().deletingLastPathComponent().absoluteString + let task = Process() + task.launchPath = "/usr/bin/open" + task.arguments = [path] + task.launch() + exit(0) + } + func hideUi() { debugPrint("hideUi") appIsBeingUsed = false diff --git a/src/ui/generic-components/StackView.swift b/src/ui/generic-components/StackView.swift index d7201489..607dc640 100644 --- a/src/ui/generic-components/StackView.swift +++ b/src/ui/generic-components/StackView.swift @@ -1,11 +1,9 @@ import Cocoa class StackView: NSStackView { - var alignmentRectInsets_: NSEdgeInsets! - convenience init(_ views: [NSView], _ orientation: NSUserInterfaceLayoutOrientation = .horizontal, top: CGFloat = 0, right: CGFloat = 0, bottom: CGFloat = 0, left: CGFloat = 0) { self.init(views: views) - alignmentRectInsets_ = NSEdgeInsets(top: top, left: left, bottom: bottom, right: right) + edgeInsets = NSEdgeInsets(top: top, left: left, bottom: bottom, right: right) alignment = orientation == .horizontal ? .firstBaseline : .leading // workaround: for some reason, horizontal stackviews with a RecorderControl have extra fittingSize.height if orientation == .horizontal && (views.contains { $0 is CustomRecorderControl }) { @@ -14,6 +12,4 @@ class StackView: NSStackView { fit() } } - - override var alignmentRectInsets: NSEdgeInsets { alignmentRectInsets_ } } diff --git a/src/ui/generic-components/text/BoldLabel.swift b/src/ui/generic-components/text/BoldLabel.swift index 91cdc372..602a0fdf 100644 --- a/src/ui/generic-components/text/BoldLabel.swift +++ b/src/ui/generic-components/text/BoldLabel.swift @@ -3,9 +3,7 @@ import Cocoa class BoldLabel: NSTextField { convenience init(_ string: String) { self.init(labelWithString: string) - allowsEditingTextAttributes = true - attributedStringValue = NSAttributedString(string: string, attributes: [ - NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), - ]) + font = .boldSystemFont(ofSize: NSFont.systemFontSize) + stringValue = string } } diff --git a/src/ui/generic-components/text/TitleLabel.swift b/src/ui/generic-components/text/TitleLabel.swift new file mode 100644 index 00000000..db70b541 --- /dev/null +++ b/src/ui/generic-components/text/TitleLabel.swift @@ -0,0 +1,9 @@ +import Cocoa + +class TitleLabel: NSTextField { + convenience init(_ string: String) { + self.init(wrappingLabelWithString: string) + font = .systemFont(ofSize: NSFont.labelFontSize * 2) + stringValue = string + } +} diff --git a/src/ui/permission-window/PermissionView.swift b/src/ui/permission-window/PermissionView.swift new file mode 100644 index 00000000..1bdd9200 --- /dev/null +++ b/src/ui/permission-window/PermissionView.swift @@ -0,0 +1,62 @@ +import Cocoa + +class PermissionView: StackView { + static let greenColor = NSColor(srgbRed: 0.38, green: 0.75, blue: 0.33, alpha: 0.2) + static let redColor = NSColor(srgbRed: 0.90, green: 0.35, blue: 0.32, alpha: 0.2) + + var checkFunction: (() -> Bool)! + var button: NSButton! + var status: NSTextField! + var isPermissionGranted = false + + convenience init(_ iconName: String, _ title: String, _ justification: String, _ buttonText: String, _ buttonUrl: String, _ checkFunction: @escaping () -> Bool) { + let icon = NSImageView(image: NSImage(named: iconName)!) + icon.translatesAutoresizingMaskIntoConstraints = false + icon.fit() + let title = BoldLabel(title) + title.translatesAutoresizingMaskIntoConstraints = false + title.fit() + let titleStack = NSStackView(views: [icon, title]) + titleStack.translatesAutoresizingMaskIntoConstraints = false + titleStack.alignment = .centerY + titleStack.spacing = GridView.interPadding + titleStack.fit() + let justification = NSTextField(wrappingLabelWithString: justification) + justification.translatesAutoresizingMaskIntoConstraints = false + justification.preferredMaxLayoutWidth = 500 + justification.widthAnchor.constraint(equalToConstant: justification.fittingSize.width + 5).isActive = true + let button = NSButton(title: buttonText, target: nil, action: nil) + button.translatesAutoresizingMaskIntoConstraints = false + button.onAction = { _ in NSWorkspace.shared.open(URL(string: buttonUrl)!) } + button.fit() + let status = NSTextField(wrappingLabelWithString: "") + status.translatesAutoresizingMaskIntoConstraints = false + let buttonStack = NSStackView(views: [button, status]) + buttonStack.translatesAutoresizingMaskIntoConstraints = false + buttonStack.alignment = .centerY + self.init([titleStack, justification, buttonStack], .vertical, top: GridView.interPadding, right: GridView.interPadding, bottom: GridView.interPadding, left: GridView.interPadding) + self.checkFunction = checkFunction + self.button = button + self.status = status + wantsLayer = true + layer!.cornerRadius = GridView.interPadding / 2 + updatePermissionStatus(checkFunction()) + } + + func updatePermissionStatus(_ isPermissionGranted: Bool) { + self.isPermissionGranted = isPermissionGranted + if isPermissionGranted { + let color = PermissionView.greenColor + button.isEnabled = false + status.stringValue = "● " + NSLocalizedString("Allowed", comment: "") + status.textColor = color.withAlphaComponent(1) + layer!.backgroundColor = color.cgColor + } else { + let color = PermissionView.redColor + button.isEnabled = true + status.stringValue = "● " + NSLocalizedString("Not allowed", comment: "") + status.textColor = color.withAlphaComponent(1) + layer!.backgroundColor = color.cgColor + } + } +} diff --git a/src/ui/permission-window/PermissionsWindow.swift b/src/ui/permission-window/PermissionsWindow.swift new file mode 100644 index 00000000..79bcfdda --- /dev/null +++ b/src/ui/permission-window/PermissionsWindow.swift @@ -0,0 +1,72 @@ +import Cocoa + +class PermissionsWindow: NSWindow, NSWindowDelegate { + var accessibilityView: PermissionView! + var screenRecordingView: PermissionView! + + override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) { + super.init(contentRect: .zero, styleMask: style, backing: backingStoreType, defer: flag) + delegate = self + setupWindow() + setupView() + } + + func show() { + center() + App.shared.activate(ignoringOtherApps: true) + makeKeyAndOrderFront(nil) + } + + func windowShouldClose(_ sender: NSWindow) -> Bool { + debugPrint("Before using this app, you need to give permission in System Preferences > Security & Privacy > Privacy > Accessibility.", + "Please authorize and re-launch.", + "See https://help.rescuetime.com/article/59-how-do-i-enable-accessibility-permissions-on-mac-osx", + separator: "\n") + App.shared.terminate(self) + return true + } + + private func setupWindow() { + title = NSLocalizedString("AltTab needs some permissions", comment: "") + hidesOnDeactivate = false + styleMask.insert([.miniaturizable, .closable]) + } + + private func setupView() { + let appIcon = NSImageView(image: NSImage(named: "app-icon")!.resizedCopy(80, 80)) + appIcon.imageScaling = .scaleNone + let appText = TitleLabel(NSLocalizedString("AltTab needs some permissions", comment: "")) + appText.preferredMaxLayoutWidth = 380 + appText.font = .systemFont(ofSize: 25, weight: .regular) + let header = NSStackView(views: [appIcon, appText]) + header.spacing = GridView.interPadding + accessibilityView = PermissionView( + "accessibility", + NSLocalizedString("Accessibility", comment: ""), + NSLocalizedString("This permission is needed to focus windows after you release the shortcut", comment: ""), + NSLocalizedString("Open Accessibility Preferences…", comment: ""), + "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility", + SystemPermissions.accessibilityIsGranted + ) + var rows = [ + [header], + [accessibilityView], + ] + if #available(OSX 10.15, *) { + screenRecordingView = PermissionView( + "screen-recording", + NSLocalizedString("Screen Recording", comment: ""), + NSLocalizedString("This permission is needed to show screenshots and titles of open windows", comment: ""), + NSLocalizedString("Open Screen Recording Preferences…", comment: ""), + "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture", + SystemPermissions.screenRecordingIsGranted + ) + rows.append([screenRecordingView]) + } + let view = GridView(rows as! [[NSView]]) + view.fit() +// view.cell(atColumnIndex: 0, rowIndex: 4).xPlacement = .trailing + setContentSize(view.fittingSize) + contentView = view + } +}