Is it possible to "unpair" a Bluetooth device in Cocoa/ObjC? - objective-c

I've paired an IOBluetoothDevice in my Mac/Cocoa app and would like to "unpair" it programmatically. That is, I would like to remove the device from the left pane of the Bluetooth section of System Preferences.
I've seen [IOBluetoothDevice removeFromFavorites], but that just removes the heart icon next to the "Favorite" attribute of the device -- the device is still listed in the left pane.
Is this possible through Cocoa?
In the above picture, I would like to programmatically remove "Apple Mighty Mouse" from the left pane.

Paired devices are a part of System Preferences.
You can find the file with the bluetooth preferences in /Library/Preferences, its name is com.apple.Bluetooth.plist.
However, you cannot edit the file directly. You should use SCPreferences class from System Configuration framework.
Note the API for accessing/modifying system preferences is pretty low level.
EDIT: The following code works if run in superuser mode. I am not a Mac OS developer myself but it should be possible to init it with an AuthorizationRef and run it with user mode (the user will confirm access to system configuration).
SCPreferencesRef prefs = SCPreferencesCreate(kCFAllocatorDefault,
CFSTR("Test"),
CFSTR("/Library/Preferences/com.apple.Bluetooth.plist"));
const CFStringRef PAIRED_DEVICES_KEY = CFSTR("PairedDevices");
NSArray *pairedDevices = (__bridge NSArray *) SCPreferencesGetValue(prefs, PAIRED_DEVICES_KEY);
NSLog(#"Paired devices: %#", pairedDevices);
NSString *deviceToRemove = #"e4-32-cb-da-ca-2f";
NSMutableArray *newPairedDevices = [pairedDevices mutableCopy];
[newPairedDevices removeObject:deviceToRemove];
Boolean valueSet = SCPreferencesSetValue(prefs, PAIRED_DEVICES_KEY, (__bridge CFPropertyListRef) [NSArray arrayWithArray:newPairedDevices]);
NSLog(#"Value set: %#", (valueSet) ? #"YES" : #"NO");
if (!valueSet) {
NSLog(#"Error: %#", SCCopyLastError());
}
Boolean saved = SCPreferencesCommitChanges(prefs);
if (!saved) {
NSLog(#"Error: %#", SCCopyLastError());
}
NSLog(#"Saved: %#", (saved) ? #"YES" : #"NO");
CFRelease(prefs);

Related

How to set an external application always being frontmost on OS X?

I am trying to launch the built-in calculator.app on my Mac(which means it is external to my application) within my application and force the calculator to stay frontmost permanently on screen.
Here is my process. Firstly, I launch the calculator and place it frontmost temporarily.
if ([[NSWorkspace sharedWorkspace] respondsToSelector:#selector(launchApplicationAtURL:options:configuration:error:)])
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL fileURLWithPath:#"/Applications/Calculator.app/Contents/MacOS/Calculator" isDirectory:NO]
options:NSWorkspaceLaunchDefault
configuration:nil
error:NULL];
After that, I recognize the Calculator by it's owner name and try to pin Calculator.app frontmost. I was stuck here. What I would like to do is either these two ways:
1.Set an attribute to place it always frontmost. (Can't find suitable
attribute, only found attributes to resize or position)
2.Get the NSWindow of Calculator and set the level to frontmost. (Seems to be non-viable: How to convert a Carbon AXUIElementRef to Cocoa NSWindow)
But seems that both of them are not available.
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
NSArray *arr = CFBridgingRelease(windowList);
for (NSMutableDictionary *entry in arr){
NSString *ownerName = [entry objectForKey:(id)kCGWindowOwnerName];
if([ownerName isEqualToString:#"Calculator"]){
pid_t pid = [[entry objectForKey:(id)kCGWindowOwnerPID] intValue];
AXUIElementRef appRef = AXUIElementCreateApplication(pid);
CFArrayRef windowList;
AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute, (CFTypeRef *)&windowList);
AXUIElementRef windowRef = (AXUIElementRef) CFArrayGetValueAtIndex( windowList, 0);
CFTypeRef role;
AXUIElementCopyAttributeValue(windowRef, kAXRoleAttribute, (CFTypeRef *)&role);
/*Would like to get the window of the application or assign some attribute to set Calculator frontmost*/
}
Are there any ways to achieve the two aspects I've mentioned above? Or are there any suggestions for setting an external application always being frontmost?

My OSX app is sandboxed and I am not able to read data from file by specifying the absolute path

I am completely new to objective C and currently I am trying to advance the functionality of an already existing project.
There is a finder extension in the project which on getting clicked performs an action inside (IBAction) Share(id) sender.
Inside this action , I want to read a file from a particular location (the file contains the port number) and using that port I want to connect to the server.
But what I found was when I click on this extension , nothing happens because it tries to go and read data from the file and is not able to read anything.
I tried to debug this by printing out whatever it has read to some other file but all it printed was blank confirming that it is not able to read the data. Below is my code trying to read the port from a temporary location :
- (IBAction)privateShareAction:(id)sender {
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: #"/var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties" ] == YES)
{
//create file handle
NSFileHandle *file;
file = [NSFileHandle fileHandleForReadingAtPath:#"/var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties"];
//read data into file in NSData format
NSData *filedata;
filedata = [file readDataToEndOfFile];
NSLog(#"fileDATA = %#", filedata);
//convert NSData to NSString
NSString *string;
string = [[NSString alloc] initWithData:filedata encoding:NSASCIIStringEncoding];
NSMutableString *directoryPath1 = [NSMutableString stringWithString: #"share1>"];
[directoryPath1 appendString: string];
NSData *dataToWrite3 = [directoryPath1 dataUsingEncoding: NSUTF8StringEncoding];
NSFileHandle* outputFile = [NSFileHandle fileHandleForWritingAtPath:#"/Users/yp/Downloads/a.txt"];
[outputFile seekToEndOfFile];
[outputFile writeData:dataToWrite3];
//convert from string to array
NSArray *lines = [string componentsSeparatedByString:#"="];
NSLog(#"arrau = %#", lines);
//take one of the string and store it in sword
NSString *sword = [lines objectAtIndex:1];
NSLog(#"port : %#", sword);
int port1=[sword intValue];
Communicator *c = [[Communicator alloc ]init];
c.host=#"http:127.0.0.1";
c.port=port1;
[c setup];
}
else
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:#"Error"];
[alert setInformativeText:#"You are not logged in.Kindly login to start performing the operations"];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
}
}
The above code, on the action performed first tries to check if the file is present at the /var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties location or not.
This works perfectly fine , If the file is present , it shows a popup alert (which happens).
But if the file is present , it goes inside the if condition and tries to read the file where it fails .It always prints a blank string showing that nothing is being read.
So then I went and checked the entitlements in App Sandbox.
I tried to add an entitlement named com.apple.security.temporary-exception.files.absolute-path.read-only with a string value set to /var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties so that it gets the permission to read the file from this location but still it doesn't solve my problem.
Could anyone please suggest how to get this file reading permission accessible in my app because the same code works completely fine in a newly created test project.
Following steps : Original client app running -login with user name and password Once logged in -it writes the port in a file At the same time ,once you are logged in with your application , if now you right click on any file in your system you will see certain extra extensions like share ,grant access etc. (This is because a finder project used to add extensions is merged with the original client) Now when I click on say share (on right clicking a file) , I want an action to be performed.The logic for action is written in (IBAction)Share (id) sender method This app used to add extensions is sandboxed because of which the permissions are restricted. So while I clicked on share , my logic was to read that file ,get the port and then connect to server using that port. I want to do everything inside action but I am unable to do so . It is not able to find the file data from /var/folder/y3/jv117_755fdlvfldsvgr/T/com.aprivacy.xmlcorePort.properties
Sandboxed apps (all in iOS) are only allowed access to specific directories. Use NSSearchPathForDirectoriesInDomainsto obtain paths to available directories.
Ex:
Objective-C:
NSArray *documentDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
NSError *error;
BOOL status = [string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (status == NSError) {
NSLog(#"error: %#", error)
}
Swift:
let filePath = "path/file.txt";
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as! String
let path = documentDirectoryPath + filePath
Note: Sandboxed paths is not consistent across clean builds.
Don't use absolute paths in sandboxed applications.
In OS X there is the NSTemporaryDirectory() function to have access to the temporary directory for this specific application in the container. Entitlements are not needed.
From the documentation
Some path-finding APIs (above the POSIX layer) refer to app-specific
locations outside of the user’s home directory. In a sandboxed app,
for example, the NSTemporaryDirectory function provides a path to a
directory that is outside of the user’s home directory but specific to
your app and within your sandbox; you have unrestricted read/write
access to it for the current user. The behavior of these path-finding
APIs is suitably adjusted for App Sandbox and no code change is
needed.
Source: App Sandbox in Depth

iOS 9.2 - unable to see defaults registered from Settings.bundle in device Settings app

I have a project which uses Settings.bundle including root.plist containing a list of key value pairs I want to register with user defaults. Until recently, these values were visible and editable from the device's "Settings" App. Now I can't see anything when tapping on my app in settings - the details panel is empty.
How can I make sure my key-value pairs from the Settings.bundle provided with app properly display in the device's settings app?
Edit: It seems that restarting the settings app fixes the issue, but the details pane goes blank again if I redeploy the app from Xcode. Is it something with the new version of iOS that I'm not aware of?
Here's my code to register defaults:
- (void)registerDefaultsFromSettingsBundle {
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
if(!settingsBundle) {
DLog(#"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:#"Root.plist"]];
NSArray *preferences = [settings objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:#"Key"];
if(key && [[prefSpecification allKeys] containsObject:#"DefaultValue"]) {
id object = [prefSpecification objectForKey:#"DefaultValue"];
if(object != nil)
{
[defaultsToRegister setObject:object forKey:key];
}
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}
This is an Apple bug in iOS 9.2 and Simulator 9.2

Issues in using CNCopyCurrentNetworkInfo

I am using the below code to retrieve the SSID of the WiFi network the iPod is connected.
NSArray *ifs = (id)CNCopySupportedInterfaces();
NSLog(#"%s: Supported interfaces: %#", __func__, ifs);
id info = nil;
for (NSString *ifnam in ifs) {
info = (id)CNCopyCurrentNetworkInfo((CFStringRef)ifnam);
NSLog(#"%s: %# => %#", __func__, ifnam, info);
if (info && [info count]) {
break;
}
[info release];
}
Sometimes this code is not returning the proper SSID of the network my device is connected.Any pointers on why the SSID is not retrieved correctly? Does CNCopyCurrentNetworkInfo package dependent on the iOS version of the device?
Thanks.
add SystemConfiguration.framework to project.
import < SystemConfiguration/CaptiveNetwork.h >
CFArrayRef myArray = CNCopySupportedInterfaces();
CFStringRef interfaceName = CFArrayGetValueAtIndex(myArray, 0);
CFDictionaryRef captiveNtwrkDict = CNCopyCurrentNetworkInfo(interfaceName);
NSDictionary *dict = ( NSDictionary*) captiveNtwrkDict;
NSString* ssid = [dict objectForKey:#"SSID"];
NSLog(#"%s ssid : %#",__FUNCTION__, [ssid description]);
For iOS 12 and later, you must enable it from capabilities.
Important
To use this function in iOS 12 and later, enable the Access WiFi Information capability for your app in Xcode. When you enable this capability, Xcode automatically adds the Access WiFi Information entitlement to your entitlements file and App ID. Documentation link
Yes. CNCopyCurrentNetworkInfo is available only in iOS 4.1 and later.
For more info ,please look at the developer.apple SystemConfiguration Reference
you can check the sample code here

Get list of installed apps on iPhone

Is there a way (some API) to get the list of installed apps on an iPhone device.
While searching for similar questions, I found some thing related to url registration, but I think there must be some API to do this, as I don't want to do any thing with the app, I just want the list.
No, apps are sandboxed and Apple-accepted APIs do not include anything that would let you do that.
You can, however, test whether a certain app is installed:
if the app is known to handle URLs of a certain type
by using [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"thisapp://foo"]
You can get a list of apps and URL schemes from here.
For jailbroken devices you can use next snipped of code:
-(void)appInstalledList
{
static NSString* const path = #"/private/var/mobile/Library/Caches/com.apple.mobile.installation.plist";
NSDictionary *cacheDict = nil;
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir)
{
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
NSDictionary *system = [cacheDict objectForKey: #"System"]; // First check all system (jailbroken) apps
for (NSString *key in system)
{
NSLog(#"%#",key);
}
NSDictionary *user = [cacheDict objectForKey: #"User"]; // Then all the user (App Store /var/mobile/Applications) apps
for (NSString *key in user)
{
NSLog(#"%#",key);
}
return;
}
NSLog(#"can not find installed app plist");
}
for non jailbroken device, we can use third party framework which is called "ihaspp", also its free and apple accepted. Also they given good documentation how to integrate and how to use. May be this would be helpful to you. Good luck!!
https://github.com/danielamitay/iHasApp
You could do this by using the following:
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector = NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSMutableArray *Allapps = [workspace performSelector:selectorALL];
NSLog(#"apps: %#", Allapps);
And then by accessing each element and splitting it you can get your app name, and even the Bundle Identifier, too.
Well, not sure if this was available back when the last answer was given or not (Prior to iOS 6)
Also this one is time intensive, yet simple:
Go into settings > Gen. >usage. The first category under usage at least right now is Storage.
It will show a partial list of apps. At the bottom of this partial list is a button that says "show all apps".
Tap that and you'll have to go through screen by screen, and take screenshots (Quick lock button and home button takes a screenshot).
I'm doing this now and I have hundreds of apps on my iPhone. So it's going to take me a while. But at least at the end of the process I'll have Images of all my apps.