Setting the desktop background on all Spaces in Cocoa - objective-c

I'm writing a small app to change your desktop background. If the user only uses one space, then it's all fine, but when he has multiple spaces the app only works on the currently active space.
I'm using this code
[[NSWorkspace sharedWorkspace] setDesktopImageURL:currentImageURL
forScreen:screenToChange
options:screenOptions
error:&error]
to change the desktop background, and it looks like there's no way to change the background of another space.
I only found answers from several years ago, and nobody asked this specific question. Is there a way to do it in objective-c?

Although there is no public API for changing spaces background there are ways to do it.
The keyword you are looking for is com.apple.desktop.plist which is inside ~/Library/Preferences/ That's the plist that stores all the current background for all the current spaces. If you want to use objective-c you can change this file to your liking or you can use one of the suggested solutions here and here. If you are targeting Mavericks the wallpapers data is here: ~/Library/Application\ Support/Dock/desktoppicture.db"

Setting the desktop background on all Spaces in Cocoa
If user wants to set the desktop background for multiple spaces then try the below code.:-
For more information refer this
NSString* path = #"/Users/abc/Desktop/yourImg.png";
NSUserDefaults* def = [NSUserDefaults standardUserDefaults];
NSMutableDictionary* desktopDict = [NSMutableDictionary dictionaryWithDictionary:[def persistentDomainForName:#"com.apple.desktop"]];
NSMutableDictionary* bgDict = [desktopDict objectForKey:#"Background"];
NSMutableDictionary* spaces = [bgDict objectForKey:#"spaces"];
[spaces enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSMutableDictionary* obj, BOOL *stop) {
[obj enumerateKeysAndObjectsUsingBlock:^(id key, NSMutableDictionary* prefs, BOOL *stop) {
[prefs setObject:path forKey:#"ImageFilePath"];
[prefs setObject:path forKey:#"NewImageFilePath"];
[prefs setObject:#"Never" forKey:#"Change"];
}];
}];
[def setPersistentDomain:desktopDict forName:#"com.apple.desktop"];

Related

Redraw desktop background without restarting dock in OS X

I am updating the wallpaper using the following function:
- (void)updateWallpaper: (NSString *)path {
NSError * aerror;
NSURL *url = [[NSURL alloc] init];
url = [NSURL fileURLWithPath:path];
[[NSWorkspace sharedWorkspace] setDesktopImageURL:url forScreen:[NSScreen mainScreen] options:[NSDictionary dictionary] error:&aerror];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary* desktopDict = [NSMutableDictionary dictionaryWithDictionary:[defaults persistentDomainForName:#"com.apple.desktop"]];
NSMutableDictionary* bgDict = [desktopDict objectForKey:#"Background"];
NSMutableDictionary* spaces = [bgDict objectForKey:#"spaces"];
[spaces enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSMutableDictionary* obj, BOOL *stop) {
[obj enumerateKeysAndObjectsUsingBlock:^(id key, NSMutableDictionary* prefs, BOOL *stop) {
[prefs setObject:path forKey:#"ImageFilePath"];
[prefs setObject:path forKey:#"NewImageFilePath"];
[prefs setObject:#"Never" forKey:#"Change"];
}];
}];
//NSLog(#"%#", desktopDict);
[defaults setPersistentDomain:desktopDict forName:#"com.apple.desktop"];
if ([defaults synchronize] == NO) {
NSLog(#"synchronize failed");
}
}
However, the update is not always rendered, in the sense that the old image continues to remain. I have tried various workarounds, the best I could come up with is writing the image to a new file (new file path) for every update. This works when I am working in the desktop space, but not if I am working in some other full-screen app space. The only thing which fixes this is to reload the dock (by system ("/usr/bin/killall Dock");). This somehow redraws the wallpaper in between. But this causes the un-minimizing of all the minimized windows for all applications, which is not okay for my use. Is there some other way to reinforce the update?
As a workaround, you can consider using AppleScript. It may not be the most elegant solution, but integrating AppleScript into apps is a documented technique, so it should be safe.
In OS X 10.11 you can change the wallpaper with a single line of script. Notice that you have to use a POSIX file, aliases do not seem to work.
To test it from the command line, try this in Terminal:
osascript -e 'tell application "Finder" to set desktop picture to POSIX file "<full path to image file>"'
This doc explains how to use AppleScript from an app.

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

Access iTunes user preferences in OSX Lion

I want to access iTunes user preferences such as playlists programmatically.
I use to do it with the following code, however since OSX Lion, I get a nil in response.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userPreferences = [userDefaults persistentDomainForName:#"com.apple.iApps"];
NSArray *databasePaths = [userPreferences objectForKey:#"iTunesRecentDatabasePaths"];
I've also made sure my app has all of its entitlements enabled.
Any suggestions on how I can fix this?
Long story made short: You just can not do it using a Sandboxed app. Turn off Sandboxing and you will see that it works. Why? Well, that's because of containers. A sandbox lives in its own container and so when you call [NSUserDefaults standardUserDefaults] Cocoa uses the path of your container rather than the POSIX path of ~/Library/Preferences which is where com.apple.iApps.plist resides. That sums up why you get nil. Also, there is a blurb on this here in NSUserDefaults: link
How to fix this?
It's really not to bad. First you have to do a little bit of work to get the POSIX path of your home directory. I setup a bunch of NSURL category methods. However the root path is POSIX based. Here is the code snippet to get you started.
1.
+ (NSURL *) homePOSIXURL {
struct passwd *pwUser = getpwuid(getuid());
const char *homeDir = pwUser->pw_dir;
return [NSURL fileURLWithPath:[NSString stringWithUTF8String:homeDir] isDirectory:YES]; }
When all is said and done, construct a full path to the plist. It hasn't changed for years so you can consider it sticky.
So you might get something that looks like this now:
2.
NSURL * prefURL = [[NSURL libraryPOSIXURL] URLByAppendingPathComponent:#"Preferences" isDirectory:YES];
prefURL = [prefURL URLByAppendingPathComponent:#"com.apple.iApps.plist"];
Now let's turn this plist, which is fairly small into something we can play with. Perhaps NSData? That sounds good, right?
3.
NSData * prefData = [NSData dataWithContentsOfURL:prefURL];
Ok, finally we can use this now to get an NSDictionary. Here is how I do it.
4.
NSDictionary * prefDict = [NSDictionary collectionFromPropertyList:prefData];
Yeah, yeah another Category on NSDictionary. I must have a million of them.
Because I'm in a sharing mood, here ya go:
+ (id) collectionFromPropertyList:(NSData *)pList {
if ( [pList length] > 0 )
return [NSPropertyListSerialization propertyListWithData:pList
options:NSPropertyListImmutable
format:nil error:nil];
return nil;
}
So, you think we are done? Well, almost. If you get this far, you will get a deny like so:
deny file-read-data
/Users/UserName/Library/Preferences/com.apple.iApps.plist
Are we loving our Sandboxed app?! Basically add your temporary entitlement and you should be off to the races again. Best of luck to ya!

How to know if window is minimizable when titlebar was double clicked?

This image is from SystemPreferences > Appearance
I want to know How do I get that value programmatically?
I ask because I am drawing a window with a customized titlebar and I want it to resemble (in behavior) as much as possible to normal (non-customized) cocoa windows.
Maybe a terminal command I can pipe or is there an cocoa API that does this?
EDIT:
Answer (thanks to NSGod)
- (void)mouseUp:(NSEvent *)event{
if ([event clickCount] == 2) {
//Get settings from "System Preferences" > "Appearance" > "Double-click on windows title bar to minimize"
NSString *const MDAppleMiniaturizeOnDoubleClickKey = #"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
BOOL shouldMiniaturize = [[userDefaults objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];
if (shouldMiniaturize) {
[self miniaturize:self];
}
}
}
Later I found that Appearance (Aqua/Graphite) can be found:
NSString * const kAppleAquaColorVariant = #"AppleAquaColorVariant";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
NSNumber *color = [userDefaults objectForKey:kAppleAquaColorVariant];
if ([color intValue] == 6) {//graphite is 6
imageName = [imageName stringByAppendingFormat:#"_graphite"];
}else{//defaults to aqua, (aqua is 1)
imageName = [imageName stringByAppendingFormat:#"_colorsryg"];
}
Which can be helpful too :)
The way I would do it is probably read the value in from user defaults.
NSString * const MDAppleMiniaturizeOnDoubleClickKey = #"AppleMiniaturizeOnDoubleClick";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
// [userDefaults addSuiteNamed:NSGlobalDomain]; // unnecessary
NSNumber *miniaturize = [userDefaults
objectForKey:MDAppleMiniaturizeOnDoubleClickKey];
NSLog(#"AppleMiniaturizeOnDoubleClick == %#",
([miniaturize boolValue] ? #"YES" : #"NO"));
(This preference setting is stored in the invisible .GlobalPreferences.plist in your ~/Library/Preferences/ folder).
Note that by default, the "double-click to minimize" option is turned off, so if you check for the presence of the AppleMiniaturizeOnDoubleClick and it returns nil, that means it is off. (User defaults only start to store values if they differ from the defaults).
This key is the same in Leopard as it is in Snow Leopard. (It hasn't changed in Lion or Mountain Lion either).
Of course, there is a secret (private) method in NSWindow, -(BOOL)_shouldMiniaturizeOnDoubleClick, but I wouldn't recommend using private methods.
[UPDATE] Regarding Catfish_Man's comment: you are correct in that the line [userDefaults addSuiteNamed:NSGlobalDomain]; is unnecessary, as NSUserDefaults already has the ability to read global preferences. (I modified the code above to reflect this).
"Additionally, NSGlobalDomain is not translated to
.GlobalPreferences.plist for that method."
I'm not sure I follow you there. NSUserDefaults is built on top of CFPreferences which defines the following 6 constants:
Application:
kCFPreferencesAnyApplication,
kCFPreferencesCurrentApplication
Host:
kCFPreferencesAnyHost,
kCFPreferencesCurrentHost
User:
kCFPreferencesAnyUser,
kCFPreferencesCurrentUser
Given a fictional application bundle identifier of "com.markdouma.App" and a single host (based on your current network location that won't change for this example), there are generally 8 locations where preference information could be stored on your disk. (NOTE: The paths shown are for demonstration purposes only; the exact file path locations are subject to change). The 8 different locations arise from the different combination of the CFPreferences constants:
/Library/Preferences/.GlobalPreferences.plist
(kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesAnyHost)
/Library/Preferences/com.markdouma.App.plist
(kCFPreferencesCurrentApplication, kCFPreferencesAnyUser,
kCFPreferencesAnyHost)
/Library/Preferences/ByHost/.GlobalPreferences.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
/Library/Preferences/ByHost/com.markdouma.App.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesCurrentApplication,
kCFPreferencesAnyUser, kCFPreferencesCurrentHost)
~/Library/Preferences/.GlobalPreferences.plist
(kCFPreferencesAnyApplication, kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)
~/Library/Preferences/com.markdouma.App.plist
(kCFPreferencesCurrentApplication, kCFPreferencesCurrentUser,
kCFPreferencesAnyHost)
~/Library/Preferences/ByHost/.GlobalPreferences.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesAnyApplication,
kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
~/Library/Preferences/ByHost/com.markdouma.App.UNIQUE_HOST_IDENTIFIER.plist (kCFPreferencesCurrentApplication,
kCFPreferencesCurrentUser, kCFPreferencesCurrentHost)
While NSUserDefaults can only write to the domain combination shown in italics, it automatically has read access to the domain combinations shown in bold. In other words, without having to do anything, I can automatically run the following code and print the results:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSNumber *miniaturize = [userDefaults
objectForKey:#"AppleMiniaturizeOnDoubleClick"];
NSNumber *fastUserSwitching = [userDefaults
objectForKey:#"MultipleSessionEnabled"];
NSLog(#"AppleMiniaturizeOnDoubleClick == %#",
([miniaturize boolValue] ? #"YES" : #"NO"));
NSLog(#"MultipleSessionEnabled == %#",
([fastUserSwitching boolValue] ? #"YES" : #"NO"));
Running that code on my system prints the following results:
AppleMiniaturizeOnDoubleClick == YES
MultipleSessionEnabled == YES
This makes sense, since I have both Fast User Switching and Double-click to minimize options enabled. MultipleSessionsEnabled is stored in the local domain at /Library/Preferences/.GlobalPreferences.plist, and AppleMiniaturizeOnDoubleClick is stored in the user domain at ~/Library/Preferences/.GlobalPreferences.plist.
Sample project: NSUserDefaultsFinagler.zip
"Additionally additionally, that's slow. Please don't do this."
Sorry, but that makes no sense (assuming that we've agreed that we're no longer using addSuiteNamed:). User defaults are cached by the application, making calls take in the matter of milliseconds. Wouldn't there be little noticeable difference between asking user defaults for the value for a key that represents a local application value or one that represents a global value?
AFAIK, this is the only "legal" (App-store-compatible) way to achieve the OP's goal. If there's another more efficient means, then please elaborate.

Extracting a URL from within a string

My app receives numerous text strings which may or may not contain a URL anywhere within the string. What would be the best method to extract a URL from within a string? Thank you.
If you are working on a Mac application, Mac OS X 10.6 offers a new API to let you detect URLs with the spell checker. You may do it this way.
NSString *s = #"http://example.com"
NSInteger wordCount = 0;
NSOrthography *orthography = nil;
NSArray *checkResults = [[NSSpellChecker sharedSpellChecker] checkString:s range:NSMakeRange(0, [s length]) types:NSTextCheckingTypeLink options:nil inSpellDocumentWithTag:0 orthography:&orthography wordCount:&wordCount];
for (NSTextCheckingResult *result in checkResults) {
NSRange range = result.range;
NSURL *URL = result.URL;
}
The BSD-licensed AutoHyperlinks framework provides classes to scan text for URLs and either return them to you or mark them up as links in an attributed string.
I don't think it builds out of the box for the iPhone, but you could always add preprocessor directives to cut out any AppKit-dependent code. The scan-and-return interface should just work, once you get it to build. (Make sure to run the tests.)
Mike Abdullah wrote a patch for iPhone support. You might try that.