Cocoa button opens a System Preference page - cocoa

In a OSX Cocoa app, I would like a button that would open the "speech" preference pane. Is that possible? I'm just trying to save them the time to go System Preferences > Speech > Text to Speech

One can even select specific sub item inside a pref pane.
Here is example to select Camera under Privacy pane:
NSURL *URL = [NSURL URLWithString:#"x-apple.systempreferences:com.apple.preference.security?Privacy_Camera"];
[[NSWorkspace sharedWorkspace] openURL:URL];
Some credit goes to following site: https://macosxautomation.com/system-prefs-links.html
List of URLs for reference (*updated for macOS Big Sur & Catalina & Mojave):
Accessibility Preference Pane
Main x-apple.systempreferences:com.apple.preference.universalaccess
Display x-apple.systempreferences:com.apple.preference.universalaccess?Seeing_Display
Zoom x-apple.systempreferences:com.apple.preference.universalaccess?Seeing_Zoom
VoiceOver x-apple.systempreferences:com.apple.preference.universalaccess?Seeing_VoiceOver
Descriptions x-apple.systempreferences:com.apple.preference.universalaccess?Media_Descriptions
Captions x-apple.systempreferences:com.apple.preference.universalaccess?Captioning
Audio x-apple.systempreferences:com.apple.preference.universalaccess?Hearing
Keyboard x-apple.systempreferences:com.apple.preference.universalaccess?Keyboard
Mouse & Trackpad x-apple.systempreferences:com.apple.preference.universalaccess?Mouse
Switch Control x-apple.systempreferences:com.apple.preference.universalaccess?Switch
Dictation x-apple.systempreferences:com.apple.preference.universalaccess?SpeakableItems
Security & Privacy Preference Pane
Main x-apple.systempreferences:com.apple.preference.security
General x-apple.systempreferences:com.apple.preference.security?General
FileVault x-apple.systempreferences:com.apple.preference.security?FDE
Firewall x-apple.systempreferences:com.apple.preference.security?Firewall
Advanced x-apple.systempreferences:com.apple.preference.security?Advanced
Privacy x-apple.systempreferences:com.apple.preference.security?Privacy
Privacy-Camera x-apple.systempreferences:com.apple.preference.security?Privacy_Camera
Privacy-Microphone x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone
Privacy-Automation x-apple.systempreferences:com.apple.preference.security?Privacy_Automation
Privacy-AllFiles x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles
Privacy-Accessibility x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility
Privacy-Assistive x-apple.systempreferences:com.apple.preference.security?Privacy_Assistive
Privacy-Location Services x-apple.systempreferences:com.apple.preference.security?Privacy_LocationServices
Privacy-SystemServices x-apple.systempreferences:com.apple.preference.security?Privacy_SystemServices
Privacy-Advertising x-apple.systempreferences:com.apple.preference.security?Privacy_Advertising
Privacy-Contacts x-apple.systempreferences:com.apple.preference.security?Privacy_Contacts
Privacy-Diagnostics & Usage x-apple.systempreferences:com.apple.preference.security?Privacy_Diagnostics
Privacy-Calendars x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars
Privacy-Reminders x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders
Privacy-Facebook x-apple.systempreferences:com.apple.preference.security?Privacy_Facebook
Privacy-LinkedIn x-apple.systempreferences:com.apple.preference.security?Privacy_LinkedIn
Privacy-Twitter x-apple.systempreferences:com.apple.preference.security?Privacy_Twitter
Privacy-Weibo x-apple.systempreferences:com.apple.preference.security?Privacy_Weibo
Privacy-Tencent Weibo x-apple.systempreferences:com.apple.preference.security?Privacy_TencentWeibo
macOS Catalina 10.15:
Privacy-ScreenCapture x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture
Privacy-DevTools x-apple.systempreferences:com.apple.preference.security?Privacy_DevTools
Privacy-InputMonitoring x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent
Privacy-DesktopFolder x-apple.systempreferences:com.apple.preference.security?Privacy_DesktopFolder
Privacy-DocumentsFolder x-apple.systempreferences:com.apple.preference.security?Privacy_DocumentsFolder
Privacy-DownloadsFolder x-apple.systempreferences:com.apple.preference.security?Privacy_DownloadsFolder
Privacy-NetworkVolume x-apple.systempreferences:com.apple.preference.security?Privacy_NetworkVolume
Privacy-RemovableVolume x-apple.systempreferences:com.apple.preference.security?Privacy_RemovableVolume
Privacy-SpeechRecognition x-apple.systempreferences:com.apple.preference.security?Privacy_SpeechRecognition
macOS Big Sur 10.11/10.16:
Privacy-Bluetooth x-apple.systempreferences:com.apple.preference.security?Privacy_Bluetooth
Privacy-Music x-apple.systempreferences:com.apple.preference.security?Privacy_Media
Privacy-Home x-apple.systempreferences:com.apple.preference.security?Privacy_HomeKit
macOS Monterey 12.0:
Privacy-DoNotDisturb x-apple.systempreferences:com.apple.preference.security?Privacy_Focus
macOS Ventura 13.0:
Privacy-AppBundles x-apple.systempreferences:com.apple.preference.security?Privacy_AppBundles
Privacy-ListenEvent x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent
Dictation & Speech Preference Pane
Dictation x-apple.systempreferences:com.apple.preference.speech?Dictation
Text to Speech x-apple.systempreferences:com.apple.preference.speech?TTS
Sharing Preference Pane
Main x-apple.systempreferences:com.apple.preferences.sharing
Screen Sharing x-apple.systempreferences:com.apple.preferences.sharing?Services_ScreenSharing
File Sharing x-apple.systempreferences:com.apple.preferences.sharing?Services_PersonalFileSharing
Printer Sharing x-apple.systempreferences:com.apple.preferences.sharing?Services_PrinterSharing
Remote Login x-apple.systempreferences:com.apple.preferences.sharing?Services_RemoteLogin
Remote Management x-apple.systempreferences:com.apple.preferences.sharing?Services_ARDService
Remote Apple Events x-apple.systempreferences:com.apple.preferences.sharing?Services_RemoteAppleEvent
Internet Sharing x-apple.systempreferences:com.apple.preferences.sharing?Internet
Bluetooth Sharing x-apple.systempreferences:com.apple.preferences.sharing?Services_BluetoothSharing
Software update x-apple.systempreferences:com.apple.preferences.softwareupdate?client=softwareupdateapp
All this is possible thanks to key in Info.plist in preferencePane + CFBundleURLTypes (CFBundleURLSchemes) x-apple.systempreferences (Info.plist) in System Preferences.app
NSPrefPaneAllowsXAppleSystemPreferencesURLScheme
As of 10.15 some of the keys are located in PrivacyTCCServices.plist (Security.prefPane)
As of 10.14 Mojave some Privacy keys ceased to exists. Mojave privacy list:
Catalina privacy list:

The following is a fairly easy (and reliable) way to at least get System Preferences open to the Speech.prefPane:
- (IBAction)openSpeechPrefs:(id)sender {
[[NSWorkspace sharedWorkspace] openURL:
[NSURL fileURLWithPath:#"/System/Library/PreferencePanes/Speech.prefPane"]];
}
However, it won't necessarily be switched to the Text to Speech tab, but rather the last tab the user had selected.
It is possible to actually switch to the Text to Speech tab as well, but it's a bit more involved. You can use AppleScript to send commands to the System Preferences application, but using the ScriptingBridge.framework (See Scripting Bridge Programming Guide) is much faster.
You'll need to add the ScriptingBridge.framework to your project, and then use a command like the following in Terminal to generate a SBSystemPreferences.h header file to work with:
sdef "/Applications/System Preferences.app" | sdp -fh --basename SBSystemPreferences -o ~/Desktop/SBSystemPreferences.h
Add that SBSystemPreferences.h header to your project, then change -openSpeechPrefs: to the following:
- (IBAction)openSpeechPrefs:(id)sender {
SBSystemPreferencesApplication *systemPrefs =
[SBApplication applicationWithBundleIdentifier:#"com.apple.systempreferences"];
[systemPrefs activate];
SBElementArray *panes = [systemPrefs panes];
SBSystemPreferencesPane *speechPane = nil;
for (SBSystemPreferencesPane *pane in panes) {
if ([[pane id] isEqualToString:#"com.apple.preference.speech"]) {
speechPane = pane;
break;
}
}
[systemPrefs setCurrentPane:speechPane];
SBElementArray *anchors = [speechPane anchors];
for (SBSystemPreferencesAnchor *anchor in anchors) {
if ([anchor.name isEqualToString:#"TTS"]) {
[anchor reveal];
}
}
}
EDIT:
Sample project using the ScriptingBridge.framework method:
http://github.com/NSGod/OpenSystemPrefsTTS

For the guys who run into the same issue that I mentioned in the comment, just go to the ~/Desktop (cos I specify this position) and you'll see the SBSystemPreferences.h has been created.
However, in this header, some class declarations are missing. So you have to add this declarations explicitly.
In my case, class "item" is undefined. So add this:
#class SBSystemPreferencesItem;
Then compile it and see what's still missing, which declaration needs to be added.

Related

Sandbox suppressing accessibility prompt

My app implements a hotkey so I'm using AXIsProcessTrustedWithOptions to check and prompt for accessibility permissions so I can set up the hotkey with CGEventTapCreate/CFMachPortCreateRunLoopSource/CFRunLoopAddSource.
It all works fine unless the app is sandboxed, in which case the prompt doesn't appear and the app isn't added to the list in the accessibility section of the Privacy and Security dialog in system preferences.
I've tried all combinations that I can think of in terms of signing (Mac App Distribution, Apple Distribution, Developer etc.)
This is the code which enables/disables the hotkey - it works if sandbox is disabled. If sandbox is enabled, the call to AXIsProcessTrustedWithOptions returns false and doesn't show the prompt (or add the app to the list in system preferences).
- (void)setup_hotkey
{
LOG(#"setup_hotkey: enabled = %d", settings.hotkey_enabled);
if (settings.hotkey_enabled) {
if (!hotkey_installed) {
NSDictionary *options_prompt = #{(__bridge id)kAXTrustedCheckOptionPrompt : #YES};
if (AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options_prompt)) {
LOG(#"permissions OK");
hotkey_tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventKeyDown), on_hotkey, (__bridge void *)self);
hotkey_runloop_source_ref = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, hotkey_tap, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), hotkey_runloop_source_ref, kCFRunLoopCommonModes);
hotkey_installed = true;
} else {
LOG(#"still need permissions");
settings.hotkey_enabled = false;
[self show_options_window];
}
}
} else if (hotkey_installed) {
LOG(#"remove hotkey");
CFRunLoopRemoveSource(CFRunLoopGetMain(), hotkey_runloop_source_ref, kCFRunLoopCommonModes);
hotkey_tap = nil;
hotkey_runloop_source_ref = nil;
hotkey_installed = false;
}
}
The project settings look like this (as mentioned, I've tried all the certificate types, the behaviour is the same regardless of which one I use). I've tried adding User Selected File/Read-Only but that made no difference. I've also tried Automatic Signing and basically every other combination of settings on this page that I can find. The only way that I've found so far to make the prompt appear is to remove the Sandbox setting.
The entitlements file looks like this:
and it is being correctly referenced in the project settings.
I've tried Debug/Release (the settings are the same for both) and running the app in the debugger or from Finder. Same results regardless.
I'm building with XCode 12.4, running on Catalina 10.15.7 (Build 19H2026). These are the most recent versions I can use on the Mac that I have.
So the question is: Am I doing something wrong in the setup_hotkey function? Is there some flag or parameter which needs to be changed? And if not, is there some official signing/entitlement setting which is wrong or missing?
I've looked at accessibility-api-stops-working-after-sandboxing which references some ancient Apple documentation but I can't find any more recent docs on this. My understanding is that Sandboxed app can be granted the accessibility privilege in Catalina but am I mistaken there?
I've tried notarizing it and then running the notarized app from the Applications folder - still no prompt...

Automatically allow app (built & run by Xcode) to control your computer

I am developing an assistive MacOS app in Xcode / ObjC.
It intercepts keystrokes using event-taps.
When I run it from Xcode, I get:
2019-07-05 06:20:32.423783+0300 mapper[8108:1191874] unable to create event tap. must run as root or add privileges for assistive devices to this app.
2019-07-05 06:20:32.423809+0300 mapper[8108:1191874] No Event tap in place! You will need to call listen after tapEvents to get events.
I have to go to system preferences -> security & privacy -> privacy -> {unlock the padlock putting my admin password} -> {locate, uncheck and recheck my executable from the list}, like so:
Every time I modify a line of code and run again I have to go through this process of checking and unchecking.
It is a very clumsy development cycle.
Is there any way to avoid this?
This project was using a 4-year-old Xcode project file.
Starting with a fresh Cocoa App template and copying the files in, the first time I build and run I am now automatically presented with:
... which takes me to the preferences pane and previous screenshot. Subsequent rebuilds do NOT require toggling the setting in this dialog. It is only if I "Clean Build Folder" that this process repeats.
My guess is that the improved behaviour may be because the new project is derived from a Cocoa App template (which inherits its base object from NSApplicationDelegate, whereas the old one inherited from a Commandline App template (and I had to manually manage the run loop with CFRunloop).
Getting mouse coordinates on Mojave contains an answer offering instructions on how to add a row to entitlements.plist:
The entitlement that allows an application to use the Accessibility API is the com.apple.private.tcc.allow entitlement (with a value of kTCCServiceAccessibility). As you can probably guess from the name it is only allowed on Apple signed binaries.

Open Notification Center programmatically on OS X

Is there a way to open Notification Center on OS X from a Cocoa app?
We tried launching the Notification Center app, but that doesn't work.
Our app is not sandboxed, so we can do whatever is required.
You can open it using ScriptingBridge, but it's not an official API, so it might break someday.
It's a bit odd, but this only works with sandboxed apps. If your app is not sandboxed, you need Accessibility access.
1. Generate Header file
Open Terminal and run the following commands:
cd ~/Desktop/
sdef /System/Library/CoreServices/System\ Events.app | sdp -fh --basename SystemEvents
Copy the file "SystemEvents.h" from your Desktop into your project.
Xcode will show some errors or warnings for a few lines in this file. Just remove these lines.
2. Import ScriptingBridge framework and generated header file.
#import <ScriptingBridge/ScriptingBridge.h>
and
#import "SystemEvents.h"
3. Add code
SystemEventsApplication *systemEventsApp = (SystemEventsApplication *)[[SBApplication alloc] initWithBundleIdentifier:#"com.apple.systemevents"];
SystemEventsApplicationProcess *sysUIServer = [systemEventsApp.applicationProcesses objectWithName:#"SystemUIServer"];
SystemEventsMenuBarItem *item = nil;
for (SystemEventsMenuBar *menuBar in sysUIServer.menuBars) {
item = [menuBar.menuBarItems objectWithName:#"Notification Center"];
if (item != nil && [item.name isEqualToString:#"Notification Center"])
break;
}
[item clickAt:nil];
You can also replace line 4-9 with this code, if you're sure the menu bars won't change in the future:
SystemEventsMenuBarItem *item = [[[sysUIServer.menuBars objectAtLocation:#2] menuBarItems] objectWithName:#"Notification Center"];
4. Add temporary exception for sandbox
Open your projects .entitlements file and add "com.apple.security.temporary-exception.apple-events" as an array. Add "com.apple.systemevents" as String.
Switching to Today view
This requires your app to have Accessibility access, so it won't work in sandboxed apps. You can simply call this script instead of using ScriptingBridge:
tell application "System Events"
click menu bar item "Notification Center" of menu bar 2 of application process "SystemUIServer"
click radio button "Today" of radio group 1 of window "NotificationTableWindow" of application process "NotificationCenter"
end tell
No, unfortunately there is no support for opening the Notification Center. Supposedly because it wouldn't be annoying for the user to have it open without them wanting it to open

Open document by dropping it on document-based application icon in OS X

I'm making a document-based application where documents are application bundles.
I can open them through "File → Open" menu fine. But when I try to drop them on the Dock icon they get rejected and the icon doesn't highlight.
I've searched this issue and it seems like it should just work given that LSItemContentTypes property is specified. Here is my Info.plist excerpt:
CFBundleDocumentTypes = (
{ CFBundleTypeName = "Example";
CFBundleTypeRole = "Editor";
LSHandlerRank = "Alternate";
LSItemContentTypes = ( "com.apple.application-bundle" );
NSDocumentClass = "Example";
NSExportableTypes = ( "com.apple.application-bundle" );
},
);
(I am developing outside of Xcode, this is a TextMate-formatted plist file.)
Also I tried implementing application:openFile: and application:openFiles: on my app delegate but they never get called.
There are no warnings or other related messages in the log. What am I missing?
Perhaps you need to set LSTypeIsPackage to YES.
That's the only difference I see with your plist stuff and mine that opens packages.
After that, maybe you need to logout/login, restart the app, or whatever to "refresh the system/launch services"?

Screensaver doesn't install in other machines Preferences Pane

I am writing a simple screensaver app. I run Product > Archive from Xcode, Distribute, Save built products and I get the .saver file. I then can install it with no problem on my development machine, while on other machines (64 bit, 10.7 as well) it doesn't install it.
Actually if I move the .saver file inside /Users/user/Library/Screen Savers/ and go to Preferences Pane, it complains saying: "You can't use this screensaver on this computer. Contact the developer for an up-to-date version."
I have been experimenting with writing a screensaver and get this warning scrolling in the screensaver preview panel if I use depreciated code - I usually find that looking into the Objective C reference there are suggestions of replacements to use
e.g. These are both declared in NSString.h
+ (id)stringWithContentsOfURL:(NSURL *)url NS_DEPRECATED(10_0, 10_4, 2_0, 2_0);
causes the warning
+ (id)stringWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error;
doesn't
Ctrl+click for menu then select jump to definition and then highlight and Alt+click to get suggested replacements

Resources