macOS deprecated APIs - objective-c

The company I work for has developed a program and the last time the code was touched was 2 years ago. Now the program needs to become notarized so I was asked to take care of the program.
I ported the code to latest Xcode (10.2.1) and latest macOS. But Xcode warns me about the deprecation of a few API calls:
/Users/rowelz/Documents/Develop/Code/ThinPrint/in GIT/myProject/osx-client/src/com.myProject.bootstrap/EZPBootstrapper.m:116:51: 'SMJobCopyDictionary' is deprecated: first deprecated in macOS 10.10
NSDictionary *plist = (__bridge NSDictionary *) SMJobCopyDictionary(
kSMDomainSystemLaunchd, (__bridge CFStringRef) (label));
/Users/rowelz/Documents/Develop/Code/ThinPrint/in GIT/myProject/osx-client/src/com.myProject.bootstrap/EZPBootstrapper.m:193:21: 'SMJobRemove' is deprecated: first deprecated in macOS 10.10
result = (BOOL) SMJobRemove(kSMDomainSystemLaunchd,
(__bridge CFStringRef) label, self->_authRef, FALSE, &cfError);
/Users/rowelz/Documents/Develop/Code/ThinPrint/in GIT/myProject/osx-client/src/com.myProject.bootstrap/EZPAppDelegate.m:193:15: 'SMJobSubmit' is deprecated: first deprecated in macOS 10.10
submitted = SMJobSubmit(kSMDomainUserLaunchd, (__bridge CFDictionaryRef)(plist), NULL, &cfError);
/Users/rowelz/Documents/Develop/Code/ThinPrint/in GIT/ezeep/osx-client/src/com.myProject.bootstrap/EZPAppDelegate.m:214:13: 'SMJobRemove' is deprecated: first deprecated in macOS 10.10
removed = SMJobRemove(kSMDomainUserLaunchd, (__bridge CFStringRef)kEzeepServiceNameUpdaterBstrap, NULL, false, NULL);
My Supervisor in this project gave the following boundary conditions:
Program should work during the next one year only. After that It will be replaced by another program of our house.
During this year it should work without limitations, crashes and so on.
I would prefer not to make any changes to the code, since the warnings regarding SMJobCopyDictionary, SMJobRemove and SMJobSubmit means that there is a big change to do - the whole program has to be written anew. And this would be a big effort for a one year life-span.
My Question:
I would like to write a little tool that checks for the availability of these API calls. I will then execute this tool on every beta of macOS until the final release of macOS 10.15. Of course if that tool shows a problem I will then rewrite the "now defective" program.
Would that be sufficient to detect a problem with the existence of the API? And what function can I use to detect the availability without actually calling them trying to install a launchd binary? I guess the above APIs are CoreFoundation ?
Thanks in advance for you help.
I found this here at stack overflow and it seems to work:
#include <dlfcn.h>
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
BOOL notFound = NO;
void *lib = dlopen("/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement", RTLD_LAZY);
if(lib == NULL)
{
printf("Library not found. (/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement).\n");
exit(1);
}
void *function1 = dlsym(lib, "SMJobCopyDictionary");
if(function1 == NULL)
{
printf("Function not found: SMJobCopyDictionary\n");
notFound = YES;
}
// .... and so on
dlclose(lib);
if(notFound)
{
exit(1);
}
printf("Ok, all functions found.\n");
}
return 0;
}
will that be sufficient to show a call to a deprecated and removed API?

This is speculation, but I feel confident in it: Apple will not remove these APIs from 10.15. Deprecating APIs is done regularly, but actually removing them breaks existing applications and is done very rarely.
It is possible (though unlikely I think) that they will remove the headers from the 10.15 SDK, in which case you will need to keep building against the 10.14 SDK (using Xcode 10).
And if the 10.15 SDK still contains the headers, the situation is the same as it is now and you won't have any problems.

Related

Enable access for assistive devices programmatically on 10.9

I want to enable access for assistive devices programatically on 10.9. On 10.8 and lower I was using following Applescript to enable access for assistive devices:
tell application "System Events"
if UI elements enabled is false then
set UI elements enabled to true
end if
end tell
With 10.9, Apple has moved the accessibility options to System Preferences ➞ Security & Privacy ➞ Privacy ➞ Accessibility. Unlike previous versions of OS X, which used a universal checkbox for all applications, the new functionality in 10.9 allows users to individually choose which apps can gain control of the system to perform their various scripted functions.
Apple has NOT provided any API to developers to programmatically enable accessibility for an app. So Mac OS 10.9 will prompt a dialog for end user permission to enable Accessibility when application uses accessibility APIs. Additionally User has to Relaunch the application after enabling Accessibility.
Can we enable access for assistive devices programmatically on 10.9 using Applescript or any other APIs? Any help to fix this issue would be greatly appreciated.
This doesn’t answer your question, but it’s good to know about a new API call that appeared in 10.9 and lets you display the authorization screen or bypass it:
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
Passing YES will force the authorization screen to appear, passing NO will silently skip it. The return value is the same as the one returned by AXAPIEnabled(), which is getting deprecated in 10.9. To make sure that the function is available on your system, just compare it to NULL:
if (AXIsProcessTrustedWithOptions != NULL) {
// 10.9 and later
} else {
// 10.8 and older
}
You'll need to add ApplicationServices.framework to your project, and import to your .m or .h file:
#import <ApplicationServices/ApplicationServices.h>
It’s quite a pity that the authorization screen doesn’t let the user to authorize the app directly, it just opens the right part of the System Preferences. Which, by the way, you can do directly without going through the useless system dialogue:
tell application "System Preferences"
set securityPane to pane id "com.apple.preference.security"
tell securityPane to reveal anchor "Privacy_Accessibility"
activate
end tell
or using Objective C:
NSString *urlString = #"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
This can be paired with the first code snippet to test whether accessibilityEnabled by passing #NO to kAXTrustedCheckOptionPrompt while preventing the system pop-up to appear and instead opening the Accessibility preferences pane directly:
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #NO};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
if (!accessibilityEnabled) {
NSString *urlString = #"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
For a native approach I'd recommend against using all the sqlite3 and AppleScript hacks as they might stop working in the future, there's also just a proper api for this.
To add on to this, you can actually monitor if the user clicks the accessibility setting for your app so you can do some actions when the user grants the permission.
(Swift 5, tested on Mojave, Catalina, Big Sur)
reading privileges:
private func readPrivileges(prompt: Bool) -> Bool {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: prompt]
let status = AXIsProcessTrustedWithOptions(options)
return status
}
Monitoring for changes in accessibility:
DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name("com.apple.accessibility.api"), object: nil, queue: nil) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.updatePrivileges()
}
}
It is best to read the privileges again after getting the notification as the notification itself doesn't really work in my experience. So inside the updatePrivileges(), run readPrivileges() to get the new status.
You need the delay because it takes some time for the changes to be reflected.
Another thing you need to keep in mind while monitoring is that a notification will be fired for any app that gets different permissions, so if the user grants or revokes a different app you'll still get a notification.
Also, don't forget to remove the observer when you don't need it anymore.
Source: Accessbility Testbench by Piddlesoft
While #user2865860's answer works well, I though I'd post the entire code sample that works perfectly on 10.9 to save others some time. You need to get root privileges, so it will prompt a user to enter the password.
char *command= "/usr/bin/sqlite3";
char *args[] = {"/Library/Application Support/com.apple.TCC/TCC.db", "INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.yourapp',0,1,0,NULL);", nil};
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status == errAuthorizationSuccess) {
status = AuthorizationExecuteWithPrivileges(authRef, command, kAuthorizationFlagDefaults, args, NULL);
AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
if(status != 0){
//handle errors...
}
}
You can edit the TCC.db file in directly. I had to do this in order to make Divvy install without user interaction. Just replace com.mizage.divvy with your program.
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES('kTCCServiceAccessibility','com.mizage.divvy',0,1,1,NULL);"
To remove the entry:
sudo sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "delete from access where client='com.mizage.divvy';"
I have found the following code snippet which properly requests Accessibility permissions in OS X 10.9:
if (AXIsProcessTrustedWithOptions != NULL) {
// 10.9 and later
const void * keys[] = { kAXTrustedCheckOptionPrompt };
const void * values[] = { kCFBooleanTrue };
CFDictionaryRef options = CFDictionaryCreate(
kCFAllocatorDefault,
keys,
values,
sizeof(keys) / sizeof(*keys),
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
return AXIsProcessTrustedWithOptions(options);
}
// OS X 10.8 and older
I was struggling with this myself and after a bit of a research I found the following:
Hacking the sqlite DB has the major drawback in using authorization services. First this will pop-up a dialog telling user that an application wants to install a utility helper (even though it is just one off launchd submission using SMJobSubmit). Second, it does not work for sandboxed apps and thus no app store.
#Max Al Faeakh uses AuthorizationExecuteWithPrivileges which is deprecated. You need to use launchd with the above SMJobSubmit. Anyway, this still requires authorization. It also requires an auxiliary application like this one.
I guess the best is to use either:
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
or
NSDictionary *options = #{(id)kAXTrustedCheckOptionPrompt: #NO};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
and open preference pane manually using for example scripting bridge framework:
SBSystemPreferencesApplication *prefs = [SBApplication applicationWithBundleIdentifier:#"com.apple.systempreferences"];
[prefs activate];
SBSystemPreferencesPane *pane = [[prefs panes] find:^BOOL(SBSystemPreferencesPane *elem) {
return [[elem id] isEqualToString:#"com.apple.preference.security"];
}];
SBSystemPreferencesAnchor *anchor = [[pane anchors] find:^BOOL(SBSystemPreferencesAnchor *elem) {
return [[elem name] isEqualToString:#"Privacy_Accessibility"];
}];
[anchor reveal];
The SBSystemPreferencesPane class comes form a SBSystemPreferences.h file which can be generated:
sdef "/Applications/System Preferences.app" | sdp -fh --basename SBSystemPreferences -o SBSystemPreferences.h
Thanks for this shell script samples from #NightFlight, which are really helpful. I used this with AppleScript in a Python application, like the following:
set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \\"/Library/Application Support/com.apple.TCC/TCC.db\\" \\"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\\""
do shell script sh with administrator privileges
It worked well for me in Python code as a string.
Edit (Nov 7, 2014):
If you want to try this in AppleScript Editor, use a slightly different character escape as below:
set sh to "touch /private/var/db/.AccessibilityAPIEnabled && sqlite3 \"/Library/Application Support/com.apple.TCC/TCC.db\" \"INSERT or REPLACE INTO access VALUES('kTCCServiceAccessibility','com.godevnode',0,1,0,NULL);\""
do shell script sh with administrator privileges
For Mac OS X before 10.9, it's even simpler:
accessibility_api_file = "/private/var/db/.AccessibilityAPIEnabled"
def __enable_accessibility_api():
try:
script = 'do shell script "touch %s" with administrator ' \
'privileges' % accessibility_api_file
result = applescript.AppleScript(script).run()
log.debug("Tried to enable accessibility api, result=" + result)
return True
except applescript.ScriptError as err:
log.error(str(err))
return False
Just need to touch one file. The AppleScript mentioned in the Python code above can also be used in other languages.
Thanks everyone.
I issue the following triggered from the login window to ensure control is given only to the items we want every session:
# Enable Service Accessibility for Textpander and others
# Clear the acess table.
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "DELETE FROM access"
# Enter the access we wish to have.
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES ('kTCCServiceAccessibility','com.apple.systempreferences',0,1,1,NULL)"
sqlite3 /Library/Application\ Support/com.apple.TCC/TCC.db "INSERT INTO access VALUES ('kTCCServiceAccessibility','de.petermaurer.textpanderdaemon',0,1,1,NULL)"
The sqlite3 "hack" is great.
I had to use permissions "1,1,1" (whatever that means) to make this work.
Note that the permission combination, not the client (ie. program name) is the unique database key.

Cocoa check if function exists

I'd like to use a function that's only available on OS X 10.9, but WITHOUT compiling with the 10.9 SDK. Is that possible?
I've tried weak linking, but the compiler just gives out an error that the function is not defined.
You say you don't want to compile against 10.9, but give no reason. Just in case you can:
If you set your target to 10.9 and your deployment to something lower then Xcode will weak link the 10.9 frameworks. You can then test for a C function being available by comparing its name to NULL. This fragment is taken from this document:
extern int MyWeakLinkedFunction() __attribute__((weak_import));
int main()
{
int result = 0;
if (MyWeakLinkedFunction != NULL)
{
result = MyWeakLinkedFunction();
}
return result;
}
(BTW: no sandbox issues this way.)
Assuming you are talking about a C function, you can do this with the dlopen function:
#include <dlfcn.h>
int main() {
void *lib = dlopen("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", RTLD_LAZY);
void *function = dlsym(lib, "CGColorGetConstantColor");
// cast the function to the right format
CGColorRef (*dynamic_getConstantColor)(CFStringRef colorName) = function;
NSLog(#"%#", dynamic_getConstantColor(CFSTR("kCGColorBlack")));
dlclose(lib);
}
Output:
2013-06-20 12:43:13.510 TestProj[1699:303] [ (kCGColorSpaceICCBased; kCGColorSpaceModelMonochrome; Generic Gray Profile)] ( 0 1 )
You will need to figure out the dylib in which the function you want resides, first, though.
This will break the sandbox limitations on iOS, and Mac most likely as well. It is the price you pay for trying to get around the linker.
If you are dealing with Objective-C methods, maybe you could do it with selectors..
So first check if the selector is available with:
[object respondsToSelector:#selector(osxMavericksFun)]
And if this test is correct try firing the Method via selectors
[object performSelector:#selector(osxMavericksFun)];
If you want to call c functions there is no way to do this.
You should do it like this
if (AXIsProcessTrustedWithOptions != NULL){
NSDictionary *options = #{(__bridge id)kAXTrustedCheckOptionPrompt: #YES};
accessibilityEnabled = AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options);
}else{
accessibilityEnabled = AXIsProcessTrusted();
}
This method is described in apple's documentation Listing 3-2. It is much simpler than the method described by Richard J. Ross III which you accepted as correct.

NSDataWritingFileProtectionComplete in OS X

Looking the documentation:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html
I see that NSDataWritingFileProtectionComplete is "Available in OS X v10.8 and later" but when I try to use it in my code I have a compilation error. Checking the NSData header I see that this feature is only available in iOS:
NSDataWritingFileProtectionComplete NS_ENUM_AVAILABLE_IOS(4_0)
I'm making something wrong or the documentation is incorrect?
Looking at the enums as defined in the MacOS 10.8 SDK, I see:
typedef NS_OPTIONS(NSUInteger, NSDataWritingOptions) {
NSDataWritingAtomic = 1UL << 0, // Hint to use auxiliary file when saving; equivalent to atomically:YES
NSDataWritingWithoutOverwriting NS_ENUM_AVAILABLE(10_8, 6_0) = 1UL << 1, // Hint to return prevent overwriting an existing file. Cannot be combined with NSDataWritingAtomic.
NSDataWritingFileProtectionNone NS_ENUM_AVAILABLE_IOS(4_0) = 0x10000000,
NSDataWritingFileProtectionComplete NS_ENUM_AVAILABLE_IOS(4_0) = 0x20000000,
"NS_ENUM_AVAILABLE_IOS" is a macro that expands to
#define NS_ENUM_AVAILABLE_IOS(_ios) __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_##_ios)
The "__MAC_NA" bit means "not applicable", which means that the documentation is currently wrong. This functionality is only available in iOS.
You should file a documentation bug with Apple about this.

Detect screen on/off from iOS service

I am developing a network monitor app that runs in background as a service. Is it possible to get a notification/call when the screen is turned on or off?
It exists in Android by using the following code:
private void registerScreenOnOffReceiver()
{
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(screenOnOffReceiver, filter);
}
screenOnOffReceiver is then called when screen is turned on/off. Is there a similar solution for iOS?
Edit:
The best I've found so far is UIApplicationProtectedDataWillBecomeUnavailable ( Detect if iPhone screen is on/off ) but it require the user to enable Data Protection (password protection) on the device.
You can use Darwin notifications, to listen for the events. I'm not 100% sure, but it looks to me, from running on a jailbroken iOS 5.0.1 iPhone 4, that one of these events might be what you need:
com.apple.iokit.hid.displayStatus
com.apple.springboard.hasBlankedScreen
com.apple.springboard.lockstate
Update: also, the following notification is posted when the phone locks (but not when it unlocks):
com.apple.springboard.lockcomplete
To use this, register for the event like this (this registers for just one event, but if that doesn't work for you, try the others):
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
displayStatusChanged, // callback
CFSTR("com.apple.iokit.hid.displayStatus"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
where displayStatusChanged is your event callback:
static void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(#"event received!");
// you might try inspecting the `userInfo` dictionary, to see
// if it contains any useful info
if (userInfo != nil) {
CFShow(userInfo);
}
}
If you really want this code to run in the background as a service, and you're jailbroken, I would recommend looking into iOS Launch Daemons. As opposed to an app that you simply let run in the background, a launch daemon can start automatically after a reboot, and you don't have to worry about iOS rules for apps running tasks in the background.
Let us know how this works!
Using the lower-level notify API you can query the lockstate when a notification is received:
#import <notify.h>
int notify_token;
notify_register_dispatch("com.apple.springboard.lockstate", &notify_token, dispatch_get_main_queue(), ^(int token) {
uint64_t state = UINT64_MAX;
notify_get_state(token, &state);
NSLog(#"com.apple.springboard.lockstate = %llu", state);
});
Of course your app will have to start a UIBackgroundTask in order to get the notifications, which limits the usefulness of this technique due to the limited runtime allowed by iOS.
While iPhone screen is locked appdelegate method
"- (void)applicationWillResignActive:(UIApplication *)application"
will be called you can check that. Hope it may help you.

Registering for Display Reconfiguration Callbacks

I'm putting together a Mac OS X Application and I'm trying to register to receive Display Reconfiguration notices, but I'm very lost right now. I've been reading Apple's documentation and some forums posts, etc., but everything seems to assume a better knowledge of things than I apparently possess. I understand that I have to request the callback inside a run loop for it to work properly. I don't know how to set up a basic run loop for it, though. I also feel like the example Apple has in their documentation is missing stuff they are expecting me to already know. To display my ignorance here is what I feel like things should look like.
NSRunLoop *rLoop = [NSRunLoop currentRunLoop];
codeToStartRunLoop
void MyDisplayReconfigurationCallBack (
CGDirectDisplayID display,
CGDisplayChangeSummaryFlags flags,
void *userInfo);
{
if (flags & kCGDisplayAddFlag) {
NSLog (#"Display Added");
}
else if (kCGDisplayRemoveFlag) {
NSLog (#"Display Removed");
}
}
CGDisplayRegisterReconfigurationCallback(MyDisplayReconfigurationCallBack, NULL);
The actual code I got was from Apple's Example, but it tells me that flags is an undeclared identifier at this point and won't compile. Not that it would work right since I don't have it in a run loop. I was hoping to find a tutorial somewhere that explains registering for system callback in a run loop but have not been successful. If anyone could point me in the right direction I'd super appreciate it.
(I'm sure that you'll be able to tell from my question that I'm very green. I taught myself Objective-C out of a book as my first programming language. I skipped C, so every once in a while I hit a snag somewhere that I can't figure out.)
If you're writing a Mac OS X application, the AppKit has already set up a run loop for you, so you don't need to worry about that part. You really only need to create your own run loop in Cocoa when you are also creating your own thread.
For the "undeclared identifier" part, it looks like it's due to a typo/syntax mistake:
void MyDisplayReconfigurationCallBack (CGDirectDisplayID display,
CGDisplayChangeSummaryFlags flags,
void *userInfo);
// Semicolon makes this an invalid function definition^^
{
// This is an anonymous block,* and flags wasn't declared in it
if (flags & kCGDisplayAddFlag) {
// etc.
}
Also, unlike some other languages, you can't declare or define functions inside of other functions, methods, or blocks* -- they have to be at the top level of the file. You can't put this in the same place where you call CGDisplayRegisterReconfigurationCallback.
Just as an sample (I have no idea what the rest of your code really looks like):
// MyClassThatIsInterestedInDisplayConfiguration.m
#import "MyClassThatIsInterestedInDisplayConfiguration.h"
// Define callback function at top level of file
void MyDisplayReconfigurationCallBack (
CGDirectDisplayID display,
CGDisplayChangeSummaryFlags flags,
void *userInfo)
{
if (flags & kCGDisplayAddFlag) {
NSLog (#"Display Added");
}
else if (kCGDisplayRemoveFlag) {
NSLog (#"Display Removed");
}
}
#implementation MyClassThatIsInterestedInDisplayConfiguration
- (void) comeOnBabyAndDoTheRegistrationWithMe {
// Register callback function inside a method
CGDisplayRegisterReconfigurationCallback(MyDisplayReconfigurationCallBack,
NULL);
}
#end
*The basic C curly-brace-delimited thing, not the new cool Obj-C ad hoc function thing.