feat: allow to run the app without screen-recording permissions
closes #1082
This commit is contained in:
1 parent
19f59b207f
commit
129d061434
9 files changed
+190
-97
No files matched your search
@@ -55,6 +55,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"AltTab is currently set to show Applications. This setting is only available when AltTab is set to show Windows." = "AltTab is currently set to show Applications. This setting is only available when AltTab is set to show Windows.";
|
||||
|
||||
/* Menubar callout */
|
||||
"AltTab is running without Screen Recording permissions. Thumbnails won’t show." = "AltTab is running without Screen Recording permissions. Thumbnails won’t show.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"AltTab needs some permissions" = "AltTab needs some permissions";
|
||||
|
||||
@@ -187,6 +190,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"General" = "General";
|
||||
|
||||
/* Menubar callout button */
|
||||
"Grant permission" = "Grant permission";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Hide" = "Hide";
|
||||
|
||||
@@ -226,9 +232,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Ignore shortcuts when active" = "Ignore shortcuts when active";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Language" = "Language";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Language Change" = "Language Change";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Large" = "Large";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Later" = "Later";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Latest releases" = "Latest releases";
|
||||
|
||||
@@ -310,6 +325,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Reset preferences and restart…" = "Reset preferences and restart…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Restart Now" = "Restart Now";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Screen including menu bar" = "Screen including menu bar";
|
||||
|
||||
@@ -427,6 +445,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Size" = "Size";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Skipped" = "Skipped";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Small" = "Small";
|
||||
|
||||
@@ -454,6 +475,12 @@
|
||||
/* No comment provided by engineer. */
|
||||
"System" = "System";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"System Default" = "System Default";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The application needs to restart to apply the language change." = "The application needs to restart to apply the language change.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Theme" = "Theme";
|
||||
|
||||
@@ -481,6 +508,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Updates policy" = "Updates policy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use the app without this permission. Thumbnails won’t show." = "Use the app without this permission. Thumbnails won’t show.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Version" = "Version";
|
||||
|
||||
@@ -525,15 +555,3 @@
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You didn’t write your email, thus can’t receive any response." = "You didn’t write your email, thus can’t receive any response.";
|
||||
|
||||
"Language" = "Language";
|
||||
|
||||
"System Default" = "System Default";
|
||||
|
||||
"Language Change" = "Language Change";
|
||||
|
||||
"The application needs to restart to apply the language change." = "The application needs to restart to apply the language change.";
|
||||
|
||||
"Restart Now" = "Restart Now";
|
||||
|
||||
"Later" = "Later";
|
||||
@@ -101,6 +101,7 @@ class Preferences {
|
||||
"hideWindowlessApps": "false",
|
||||
"hideThumbnails": "false",
|
||||
"previewFocusedWindow": "false",
|
||||
"screenRecordingPermissionSkipped": "false",
|
||||
]
|
||||
|
||||
// system preferences
|
||||
@@ -137,6 +138,7 @@ class Preferences {
|
||||
static var startAtLogin: Bool { defaults.bool("startAtLogin") }
|
||||
static var blacklist: [BlacklistEntry] { jsonDecode([BlacklistEntry].self, defaults.string("blacklist")) }
|
||||
static var previewFocusedWindow: Bool { defaults.bool("previewFocusedWindow") }
|
||||
static var screenRecordingPermissionSkipped: Bool { defaults.bool("screenRecordingPermissionSkipped") }
|
||||
|
||||
// macro values
|
||||
static var appearanceStyle: AppearanceStylePreference { defaults.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
|
||||
|
||||
@@ -2,20 +2,23 @@ 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 var timer: Timer!
|
||||
static var preStartupPermissionsPassed = false
|
||||
static var timerPermissionsToUpdatePermissionsWindow: Timer?
|
||||
static var timerPermissionsRemovedWhileAltTabIsRunning: Timer?
|
||||
|
||||
static func accessibilityIsGranted() -> Bool {
|
||||
static func accessibilityIsGranted() -> PermissionStatus {
|
||||
if #available(macOS 10.9, *) {
|
||||
return AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): false] as CFDictionary)
|
||||
return AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): false] as CFDictionary) ? .granted : .notGranted
|
||||
}
|
||||
return true
|
||||
return .granted
|
||||
}
|
||||
|
||||
static func screenRecordingIsGranted() -> Bool {
|
||||
static func screenRecordingIsGranted() -> PermissionStatus {
|
||||
if #available(macOS 10.15, *) {
|
||||
return screenRecordingIsGranted_()
|
||||
return screenRecordingIsGranted_() ? .granted :
|
||||
(Preferences.screenRecordingPermissionSkipped ? .skipped : .notGranted)
|
||||
}
|
||||
return true
|
||||
return .granted
|
||||
}
|
||||
|
||||
// workaround: public API CGPreflightScreenCaptureAccess and private API SLSRequestScreenCaptureAccess exist, but
|
||||
@@ -33,65 +36,83 @@ class SystemPermissions {
|
||||
) != nil
|
||||
}
|
||||
|
||||
static func observePermissionsPostStartup() {
|
||||
static func observePermissionsRemovedWhileAltTabIsRunning() {
|
||||
var counter = 0
|
||||
timer = Timer(timeInterval: 5, repeats: true, block: { _ in
|
||||
if !accessibilityIsGranted() {
|
||||
App.app.restart()
|
||||
}
|
||||
if !screenRecordingIsGranted() {
|
||||
// permission check may yield a false negative during wake-up
|
||||
// we restart after 2 negative checks
|
||||
if counter >= 2 {
|
||||
timerPermissionsRemovedWhileAltTabIsRunning = Timer(timeInterval: 5, repeats: true, block: { _ in
|
||||
DispatchQueue.main.async {
|
||||
let accessibility = accessibilityIsGranted()
|
||||
let screenRecording = screenRecordingIsGranted()
|
||||
logger.d(accessibility, screenRecording, preStartupPermissionsPassed)
|
||||
Menubar.permissionCalloutMenuItems?.forEach {
|
||||
$0.isHidden = screenRecording != .skipped
|
||||
}
|
||||
if accessibility == .notGranted {
|
||||
App.app.restart()
|
||||
}
|
||||
if screenRecording == .notGranted {
|
||||
// permission check may yield a false negative during wake-up
|
||||
// we restart after 2 negative checks
|
||||
if counter >= 2 {
|
||||
App.app.restart()
|
||||
} else {
|
||||
counter += 1
|
||||
}
|
||||
} else {
|
||||
counter += 1
|
||||
counter = 0
|
||||
}
|
||||
} else {
|
||||
counter = 0
|
||||
}
|
||||
})
|
||||
timer.tolerance = 4.9
|
||||
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timer, .defaultMode)
|
||||
timerPermissionsRemovedWhileAltTabIsRunning!.tolerance = 4.9
|
||||
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsRemovedWhileAltTabIsRunning!, .defaultMode)
|
||||
}
|
||||
|
||||
static func observePermissionsPreStartup(_ startupBlock: @escaping () -> Void) {
|
||||
if #available(macOS 10.15, *) {
|
||||
static func observePermissionsToUpdatePermissionsWindow(_ startupBlock: @escaping () -> Void) {
|
||||
if #available(macOS 10.15, *), !preStartupPermissionsPassed {
|
||||
// this call triggers the permission prompt, however it's the only way to force the app to be listed with a checkbox
|
||||
SLSRequestScreenCaptureAccess()
|
||||
}
|
||||
timer = Timer(timeInterval: 0.1, repeats: true) { _ in
|
||||
let accessibility = accessibilityIsGranted()
|
||||
let screenRecording = screenRecordingIsGranted()
|
||||
timerPermissionsToUpdatePermissionsWindow = Timer(timeInterval: 0.1, repeats: true) { _ in
|
||||
DispatchQueue.main.async {
|
||||
if accessibility && screenRecording {
|
||||
timer.invalidate()
|
||||
App.app.permissionsWindow?.close()
|
||||
startupBlock()
|
||||
} else {
|
||||
if accessibility != App.app.permissionsWindow.accessibilityView.isPermissionGranted {
|
||||
App.app.permissionsWindow.accessibilityView.updatePermissionStatus(accessibility)
|
||||
let accessibility = accessibilityIsGranted()
|
||||
let screenRecording = screenRecordingIsGranted()
|
||||
logger.d(accessibility, screenRecording, preStartupPermissionsPassed)
|
||||
Menubar.permissionCalloutMenuItems?.forEach {
|
||||
$0.isHidden = screenRecording != .skipped
|
||||
}
|
||||
if accessibility != App.app.permissionsWindow?.accessibilityView?.permissionStatus {
|
||||
App.app.permissionsWindow?.accessibilityView.updatePermissionStatus(accessibility)
|
||||
}
|
||||
|
||||
if #available(macOS 10.15, *), screenRecording != App.app.permissionsWindow?.screenRecordingView?.permissionStatus {
|
||||
App.app.permissionsWindow?.screenRecordingView?.updatePermissionStatus(screenRecording)
|
||||
}
|
||||
if !preStartupPermissionsPassed {
|
||||
if accessibility != .notGranted && screenRecording != .notGranted {
|
||||
preStartupPermissionsPassed = true
|
||||
App.app.permissionsWindow?.close()
|
||||
startupBlock()
|
||||
}
|
||||
if #available(macOS 10.15, *), screenRecording != App.app.permissionsWindow.screenRecordingView.isPermissionGranted {
|
||||
App.app.permissionsWindow.screenRecordingView.updatePermissionStatus(screenRecording)
|
||||
} else {
|
||||
if accessibility == .notGranted || screenRecording == .notGranted {
|
||||
App.app.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.tolerance = 0.09
|
||||
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timer, .defaultMode)
|
||||
timerPermissionsToUpdatePermissionsWindow!.tolerance = 0.09
|
||||
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsToUpdatePermissionsWindow!, .defaultMode)
|
||||
}
|
||||
|
||||
static func ensurePermissionsAreGranted(_ continueAppStartup: @escaping () -> Void) {
|
||||
let startupBlock = {
|
||||
observePermissionsPostStartup()
|
||||
observePermissionsRemovedWhileAltTabIsRunning()
|
||||
continueAppStartup()
|
||||
}
|
||||
if accessibilityIsGranted() && screenRecordingIsGranted() {
|
||||
if accessibilityIsGranted() != .notGranted && screenRecordingIsGranted() != .notGranted {
|
||||
preStartupPermissionsPassed = true
|
||||
startupBlock()
|
||||
} else {
|
||||
App.app.permissionsWindow.show()
|
||||
observePermissionsPreStartup(startupBlock)
|
||||
App.app.permissionsWindow.show(startupBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -18,7 +18,6 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
static let repository = "https://github.com/lwouis/alt-tab-macos"
|
||||
static let website = "https://alt-tab-macos.netlify.app"
|
||||
static var app: App!
|
||||
var menubar: Menubar!
|
||||
var thumbnailsPanel: ThumbnailsPanel!
|
||||
var previewPanel: PreviewPanel!
|
||||
var preferencesWindow: PreferencesWindow!
|
||||
@@ -59,14 +58,14 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
PFMoveToApplicationsFolderIfNecessary()
|
||||
#endif
|
||||
AXUIElement.setGlobalTimeout()
|
||||
Preferences.initialize()
|
||||
BackgroundWork.startSystemPermissionThread()
|
||||
permissionsWindow = PermissionsWindow()
|
||||
SystemPermissions.ensurePermissionsAreGranted { [weak self] in
|
||||
guard let self = self else { return }
|
||||
BackgroundWork.start()
|
||||
Preferences.initialize()
|
||||
Appearance.update()
|
||||
self.menubar = Menubar()
|
||||
Menubar.initialize()
|
||||
self.loadMainMenuXib()
|
||||
self.thumbnailsPanel = ThumbnailsPanel()
|
||||
self.previewPanel = PreviewPanel()
|
||||
@@ -80,7 +79,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
DispatchQueue.main.async { () -> () in Windows.sortByLevel() }
|
||||
self.preloadWindows()
|
||||
#if DEBUG
|
||||
self.showPreferencesWindow()
|
||||
// self.showPreferencesWindow()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -117,6 +116,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
func restart() {
|
||||
// we use -n to open a new instance, to avoid calling applicationShouldHandleReopen
|
||||
// we use Bundle.main.bundlePath in case of multiple AltTab versions on the machine
|
||||
printStackTrace()
|
||||
Process.launchedProcess(launchPath: "/usr/bin/open", arguments: ["-n", Bundle.main.bundlePath])
|
||||
App.shared.terminate(self)
|
||||
}
|
||||
@@ -180,7 +180,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
}
|
||||
|
||||
@objc func checkPermissions(_ sender: NSMenuItem) {
|
||||
permissionsWindow.show()
|
||||
permissionsWindow.show({})
|
||||
}
|
||||
|
||||
@objc func supportProject() {
|
||||
|
||||
+38
-10
@@ -1,14 +1,21 @@
|
||||
import Cocoa
|
||||
|
||||
class Menubar {
|
||||
var statusItem: NSStatusItem!
|
||||
var menu: NSMenu!
|
||||
static var statusItem: NSStatusItem!
|
||||
static var menu: NSMenu!
|
||||
static var permissionCalloutMenuItems: [NSMenuItem]?
|
||||
|
||||
init() {
|
||||
static func initialize() {
|
||||
menu = NSMenu()
|
||||
menu.title = App.name // perf: prevent going through expensive code-path within appkit
|
||||
let permissionCalloutMenuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
|
||||
permissionCalloutMenuItem.view = PermissionCallout()
|
||||
let calloutSeparator = NSMenuItem.separator()
|
||||
permissionCalloutMenuItems = [permissionCalloutMenuItem, calloutSeparator]
|
||||
menu.addItem(permissionCalloutMenuItem)
|
||||
menu.addItem(calloutSeparator)
|
||||
menu.addItem(
|
||||
withTitle: String(format: NSLocalizedString("About %@", comment: "Menubar option. %@ is AltTab"), App.name),
|
||||
withTitle: String(format: NSLocalizedString("About %@", comment: "Menubar option. %@ is AltTab"), App.name),
|
||||
action: #selector(App.app.showAboutTab),
|
||||
keyEquivalent: "")
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
@@ -48,16 +55,16 @@ class Menubar {
|
||||
menubarIconCallback(nil)
|
||||
}
|
||||
|
||||
@objc func statusItemOnClick() {
|
||||
@objc static func statusItemOnClick() {
|
||||
// NSApp.currentEvent == nil if the icon is "clicked" through VoiceOver
|
||||
if let type = NSApp.currentEvent?.type, type != .leftMouseDown {
|
||||
App.app.showUi()
|
||||
} else {
|
||||
statusItem.popUpMenu(App.app.menubar.menu)
|
||||
statusItem.popUpMenu(Menubar.menu)
|
||||
}
|
||||
}
|
||||
|
||||
func menubarIconCallback(_: NSControl?) {
|
||||
static func menubarIconCallback(_: NSControl?) {
|
||||
if Preferences.menubarIcon == .hidden {
|
||||
statusItem.isVisible = false
|
||||
} else {
|
||||
@@ -65,7 +72,7 @@ class Menubar {
|
||||
}
|
||||
}
|
||||
|
||||
private func loadPreferredIcon() {
|
||||
static private func loadPreferredIcon() {
|
||||
let i = imageIndexFromPreference()
|
||||
let image = NSImage(named: "menubar-" + i)!
|
||||
image.isTemplate = i == "3" ? false : true
|
||||
@@ -74,7 +81,7 @@ class Menubar {
|
||||
statusItem.button!.imageScaling = .scaleProportionallyUpOrDown
|
||||
}
|
||||
|
||||
private func imageIndexFromPreference() -> String {
|
||||
static private func imageIndexFromPreference() -> String {
|
||||
switch Preferences.menubarIcon {
|
||||
case .outlined: return "1"
|
||||
case .filled: return "2"
|
||||
@@ -82,4 +89,25 @@ class Menubar {
|
||||
default: return "4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionCallout: StackView {
|
||||
convenience init() {
|
||||
let label = NSTextField(wrappingLabelWithString: NSLocalizedString("AltTab is running without Screen Recording permissions. Thumbnails won’t show.", comment: "Menubar callout"))
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label.textColor = .white
|
||||
label.preferredMaxLayoutWidth = 250
|
||||
label.isSelectable = false
|
||||
label.widthAnchor.constraint(equalToConstant: 250).isActive = true
|
||||
let button = NSButton()
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.attributedTitle = NSAttributedString(string: NSLocalizedString("Grant permission", comment: "Menubar callout button"), attributes: [NSAttributedString.Key.foregroundColor: NSColor.white])
|
||||
button.onAction = { _ in
|
||||
Preferences.remove("screenRecordingPermissionSkipped")
|
||||
App.app.restart()
|
||||
}
|
||||
self.init([label, button], .vertical, true, top: 8, right: 15, bottom: 10, left: 15)
|
||||
wantsLayer = true
|
||||
layer!.backgroundColor = NSColor.purple.cgColor
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@ 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)
|
||||
static let yellowColor = NSColor(srgbRed: 0.83, green: 0.66, blue: 0.07, alpha: 0.2)
|
||||
|
||||
var status: NSTextField!
|
||||
var isPermissionGranted = false
|
||||
var permissionStatus = PermissionStatus.notGranted
|
||||
|
||||
convenience init(_ iconName: String, _ title: String, _ justification: String, _ buttonText: String, _ buttonUrl: String, _ checkFunction: @escaping () -> Bool) {
|
||||
convenience init(_ iconName: String, _ title: String, _ justification: String, _ buttonText: String, _ buttonUrl: String, _ checkFunction: @escaping () -> PermissionStatus, _ skipCheckbox: NSView? = nil) {
|
||||
let icon = NSImageView(image: NSImage.initCopy(iconName))
|
||||
icon.translatesAutoresizingMaskIntoConstraints = false
|
||||
icon.fit()
|
||||
@@ -29,25 +30,41 @@ class PermissionView: StackView {
|
||||
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)
|
||||
var views = [titleStack, justification, buttonStack]
|
||||
if let skipCheckbox = skipCheckbox {
|
||||
views.append(skipCheckbox)
|
||||
}
|
||||
self.init(views, .vertical, top: GridView.interPadding, right: GridView.interPadding, bottom: GridView.interPadding, left: GridView.interPadding)
|
||||
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
|
||||
status.stringValue = "● " + NSLocalizedString("Allowed", comment: "")
|
||||
status.textColor = color.withAlphaComponent(1)
|
||||
layer!.backgroundColor = color.cgColor
|
||||
} else {
|
||||
let color = PermissionView.redColor
|
||||
status.stringValue = "● " + NSLocalizedString("Not allowed", comment: "")
|
||||
status.textColor = color.withAlphaComponent(1)
|
||||
layer!.backgroundColor = color.cgColor
|
||||
func updatePermissionStatus(_ permissionStatus: PermissionStatus) {
|
||||
self.permissionStatus = permissionStatus
|
||||
var color: NSColor
|
||||
var label: String
|
||||
switch permissionStatus {
|
||||
case .granted:
|
||||
color = PermissionView.greenColor
|
||||
label = NSLocalizedString("Allowed", comment: "")
|
||||
case .notGranted:
|
||||
color = PermissionView.redColor
|
||||
label = NSLocalizedString("Not allowed", comment: "")
|
||||
|
||||
case .skipped:
|
||||
color = PermissionView.yellowColor
|
||||
label = NSLocalizedString("Skipped", comment: "")
|
||||
}
|
||||
status.stringValue = "● " + label
|
||||
status.textColor = color.withAlphaComponent(1)
|
||||
layer!.backgroundColor = color.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
enum PermissionStatus {
|
||||
case granted
|
||||
case notGranted
|
||||
case skipped
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class PermissionsWindow: NSWindow, NSWindowDelegate {
|
||||
setupView()
|
||||
}
|
||||
|
||||
func show() {
|
||||
func show(_ startupBlock: @escaping () -> Void) {
|
||||
accessibilityView.updatePermissionStatus(SystemPermissions.accessibilityIsGranted())
|
||||
if #available(macOS 10.15, *) {
|
||||
screenRecordingView.updatePermissionStatus(SystemPermissions.screenRecordingIsGranted())
|
||||
@@ -19,17 +19,24 @@ class PermissionsWindow: NSWindow, NSWindowDelegate {
|
||||
center()
|
||||
App.shared.activate(ignoringOtherApps: true)
|
||||
makeKeyAndOrderFront(nil)
|
||||
SystemPermissions.observePermissionsToUpdatePermissionsWindow(startupBlock)
|
||||
}
|
||||
|
||||
func windowShouldClose(_ sender: NSWindow) -> Bool {
|
||||
if !SystemPermissions.accessibilityIsGranted() || !SystemPermissions.screenRecordingIsGranted() {
|
||||
logger.e("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)
|
||||
|
||||
|
||||
func windowWillClose(_ notification: Notification) {
|
||||
logger.d("PermissionsWindow windowWillClose", SystemPermissions.preStartupPermissionsPassed)
|
||||
if !SystemPermissions.preStartupPermissionsPassed {
|
||||
if SystemPermissions.accessibilityIsGranted() == .notGranted || SystemPermissions.screenRecordingIsGranted() == .notGranted {
|
||||
logger.e("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 {
|
||||
SystemPermissions.timerPermissionsToUpdatePermissionsWindow?.invalidate()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func setupWindow() {
|
||||
@@ -66,7 +73,8 @@ class PermissionsWindow: NSWindow, NSWindowDelegate {
|
||||
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
|
||||
SystemPermissions.screenRecordingIsGranted,
|
||||
StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Use the app without this permission. Thumbnails won’t show.", comment: ""), "screenRecordingPermissionSkipped", labelPosition: .right))
|
||||
)
|
||||
rows.append([screenRecordingView])
|
||||
}
|
||||
|
||||
@@ -107,7 +107,6 @@ class LabelAndControl: NSObject {
|
||||
return views
|
||||
}
|
||||
|
||||
// periphery:ignore
|
||||
static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String, extraAction: ActionClosure? = nil, labelPosition: LabelPosition = .leftWithSeparator) -> [NSView] {
|
||||
let checkbox = NSButton(checkboxWithTitle: labelPosition == .right ? labelText : " ", target: nil, action: nil)
|
||||
checkbox.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
@@ -7,7 +7,7 @@ class GeneralTab {
|
||||
let startAtLogin = TableGroupView.Row(leftTitle: NSLocalizedString("Start at login", comment: ""),
|
||||
rightViews: [LabelAndControl.makeSwitch("startAtLogin", extraAction: startAtLoginCallback)])
|
||||
let menubarIcon = TableGroupView.Row(leftTitle: NSLocalizedString("Menubar icon", comment: ""),
|
||||
rightViews: [LabelAndControl.makeDropdown("menubarIcon", MenubarIconPreference.allCases, extraAction: App.app.menubar.menubarIconCallback)])
|
||||
rightViews: [LabelAndControl.makeDropdown("menubarIcon", MenubarIconPreference.allCases, extraAction: Menubar.menubarIconCallback)])
|
||||
let language = TableGroupView.Row(leftTitle: NSLocalizedString("Language", comment: ""),
|
||||
rightViews: [LabelAndControl.makeDropdown("language", LanguagePreference.allCases, extraAction: setLanguageCallback)])
|
||||
let resetPreferences = NSButton(title: NSLocalizedString("Reset preferences and restart…", comment: ""), target: self, action: #selector(GeneralTab.resetPreferences))
|
||||
@@ -40,8 +40,8 @@ class GeneralTab {
|
||||
}
|
||||
|
||||
private static func enableDraggingOffMenubarIcon(_ menubarIconDropdown: NSPopUpButton) {
|
||||
App.app.menubar.statusItem.behavior = .removalAllowed
|
||||
menubarIsVisibleObserver = App.app.menubar.statusItem.observe(\.isVisible, options: [.old, .new]) { _, change in
|
||||
Menubar.statusItem.behavior = .removalAllowed
|
||||
menubarIsVisibleObserver = Menubar.statusItem.observe(\.isVisible, options: [.old, .new]) { _, change in
|
||||
if change.oldValue == true && change.newValue == false {
|
||||
let hiddenIndex = Int(MenubarIconPreference.hidden.rawValue)!
|
||||
menubarIconDropdown.selectItem(at: hiddenIndex)
|
||||
|
||||
Reference in new issue
Block a user