Redraw desktop background without restarting dock in OS X - objective-c

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.

Related

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

How to launch Apps, from my App, with a custom parameter so I can check whether the app was launched by me?

I'm working on this app that launches other apps. I'm listening to app launches using:
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(appLaunched:) name:NSWorkspaceDidLaunchApplicationNotification
object:nil];
And I launch them using (Mail is just an example):
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObject:#"lalalala"], NSWorkspaceLaunchConfigurationArguments, nil];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:[NSURL URLWithString:#"/Applications/Mail.app"] options:NSWorkspaceLaunchWithoutActivation configuration:dict error:nil];
I did some research, and I saw that you can send an argument when you launch an app (that's why I used the var dict in the code above), but I'm having an issue with this: even using NSWorkspaceLaunchWithoutActivation, the Mail.app is launched and becomes focused with a new composing window. I don't know why it's doing that.
Another thing, if I manage to successfully send a custom argument without focusing the app, how can I check if the app was launched by me (check if the argument is there)?
PS: I'm looking for App Store-ready methods.
Send the timestamp (UTC) together with the app name you started to your server or a local file if possible.
Then you can track it.
Firstly, I'd try NSWorkspaceLaunchAndHide if NSWorkspaceLaunchWithoutActivation isn't "working". Not ideal, no.. but a kludge...
Secondly... here's a "full, running example" that does the trick..
#import <Cocoa/Cocoa.h>
NSString *psAUX(NSString*grep) {
FILE *read_f; char buff[BUFSIZ+1]; int char_rd; NSString *res, *cmnd;
memset(buff, '\0', sizeof(buff));
cmnd = [NSString stringWithFormat:#"/bin/ps aux|grep -i %#",grep];
read_f = popen(cmnd.UTF8String, "r");
if (read_f == NULL) return nil;
char_rd = fread(buff, sizeof(char), BUFSIZ, read_f);
if (!char_rd) return nil;
return res = [NSString stringWithUTF8String:buff], pclose(read_f), res;
}
int main(int argc, char *argv[]) { #autoreleasepool {
NSString* secretStr; NSURL *mailURL; NSDictionary *cfg; NSWorkspace *ws; NSApplication.sharedApplication;
secretStr = #"TAMPAX";
mailURL = [NSURL URLWithString:#"file:///Applications/Mail.app"];
cfg = #{NSWorkspaceLaunchConfigurationArguments:#[secretStr]};
ws = NSWorkspace.sharedWorkspace;
[ws launchApplicationAtURL:mailURL options:0 configuration:cfg error:nil];
fprintf(stderr,"%s",
[psAUX(#"Mail.app") containsString:secretStr]
? "You ARE Mail's baby's daddy!"
: "Hands off, she's NOT yours!");
[NSApp run]; } }
NSLog -> You ARE Mail's baby's daddy!
Congratulations!
You can create a new Task using NSTask. With NSTask you can set arguments as well as some environment variables to app so that you can check if it is launched by you or by someone else.
Here is the sample code sniffet to do so:
NSTask* taskApp = [[NSTask alloc] init];
[taskApp setLaunchPath:#"App path goes here"];
[taskApp setArguments:[NSArray arrayWithObjects:#"Arg1",#"arg2", nil]];
[taskApp setEnvironment: [[NSProcessInfo processInfo] environment]];
[taskApp launch];

Why my program can run in Xcode, but cannot running as a separate app?

My program loads some data from a file and then draws them.
The file-reading part is like this:
- (void)load_file
{
NSFileHandle *inFile = [NSFileHandle fileHandleForReadingAtPath:#"map_data"];
NSData *myData=[inFile readDataToEndOfFile];
NSString *myText=[[NSString alloc]initWithData:myData encoding:NSASCIIStringEncoding];
NSArray *values = [myText componentsSeparatedByString:#"\n"];
for (NSString *string in values) {
NSArray *lines=[string componentsSeparatedByString:#" "];
if ([lines count] != 2) break;
NSPoint point= NSMakePoint([lines[0] floatValue], [lines[1] floatValue]);
[points addObject:[NSValue valueWithPoint:point]];
}
[self setNeedsDisplay:YES];
}
When debugging, I put the data file in the directory of [NSBundle mainBundle], and the program works fine.
However, when I use achieve to take the app out, it never runs. I put the data file in the same path with the app, but it seems fail to load it.
Update
I tried to use c++, but still fails.
- (void)load_file
{
ifstream inf("map_data");
double x, y;
while (inf >> x >> y) [points addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
inf.close();
}
I tried to change the build scheme to release and run, which is fine. But whenever I go directly into the finder of app and double click it, it does not work and seems nothing is loaded.
add the file to the project as a Resource (this will cause it to be copied into the app wrapper in the right spot)
use `[[NSBundle mainBundle] pathForResource:#"map_data" ofType:nil];
That should give you the path to the file. The file should not be manually copied, it should not be next to the app wrapper, nor should you [conjecture] ever try changing or replacing the file once it is in your app wrapper.
The reason why it seems to work sometimes is mere coincidence. You are passing a partial path to NSFileHandle and it happens that the current working directory of your app sometimes points to the right spot such that the data file is available.
I'm not sure how relative paths are handled by NSFileHandle, but usually you set up paths using the NSBundle class.
NSString *path = [[NSBundle mainBundle] pathForResource:#"myfile" ofType:#"ext"];
You can also simply initialize an NSString from the contents of a file, you don't need to first read it into an NSData using NSFileHandle.
NSString *text = [[NSString alloc] initWithContentsOfFile:path
encoding:NSASCIIStringEncoding error:nil];
(Use the error parameter, if you want proper error handling)

Setting the desktop background on all Spaces in Cocoa

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"];

Calling -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] never returns

I have a straightforward NSDocument-based Mac OS X app in which I am trying to implement iCloud Document storage. I'm building with the 10.7 SDK.
I have provisioned my app for iCloud document storage and have included the necessary entitlements (AFAICT). The app builds, runs, and creates the local ubiquity container Documents directory correctly (this took a while, but that all seems to be working). I am using the NSFileCoordinator API as Apple recommended. I'm fairly certain I am using the correct UbiquityIdentifier as recommended by Apple (it's redacted below tho).
I have followed Apple's iCloud Document storage demo instructions in this WWDC 2011 video closely:
Session 107 AutoSave and Versions in Lion
My code looks almost identical to the code from that demo.
However, when I call my action to move the current document to the cloud, I experience liveness problems when calling the -[NSFileManager setUbiquitous:itemAtURL:destinationURL:error:] method. It never returns.
Here is the relevant code from my NSDocument subclass. It is almost identical to Apple's WWDC demo code. Since this is an action, this is called on the main thread (as Apple's demo code showed). The deadlock occurs toward the end when the -setUbiquitous:itemAtURL:destinationURL:error: method is called. I have tried moving to a background thread, but it still never returns.
It appears that a semaphore is blocking while waiting for a signal that never arrives.
When running this code in the debugger, my source and destination URLs look correct, so I'm fairly certain they are correctly calculated and I have confirmed the directories exist on disk.
Am I doing anything obviously wrong which would lead to -setUbiquitous never returning?
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSString *bundleID = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
NSString *appID = [NSString stringWithFormat:#"XXXXXXX.%#.macosx", bundleID];
BOOL makeUbiquitous = 1 == [sender tag];
NSURL *destURL = nil;
NSFileManager *mgr = [NSFileManager defaultManager];
if (makeUbiquitous) {
// get path to local ubiquity container Documents dir
NSURL *dirURL = [[mgr URLForUbiquityContainerIdentifier:appID] URLByAppendingPathComponent:#"Documents"];
if (!dirURL) {
NSLog(#"cannot find URLForUbiquityContainerIdentifier %#", appID);
return;
}
// create it if necessary
[mgr createDirectoryAtURL:dirURL withIntermediateDirectories:NO attributes:nil error:nil];
// ensure it exists
BOOL exists, isDir;
exists = [mgr fileExistsAtPath:[dirURL relativePath] isDirectory:&isDir];
if (!(exists && isDir)) {
NSLog(#"can't create local icloud dir");
return;
}
// append this doc's filename
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// get path to local Documents folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
// append this doc's filename
destURL = [[dirs objectAtIndex:0] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSFileCoordinator *fc = [[[NSFileCoordinator alloc] initWithFilePresenter:self] autorelease];
[fc coordinateWritingItemAtURL:fileURL options:NSFileCoordinatorWritingForMoving writingItemAtURL:destURL options:NSFileCoordinatorWritingForReplacing error:nil byAccessor:^(NSURL *fileURL, NSURL *destURL) {
NSError *err = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
[fc itemAtURL:fileURL didMoveToURL:destURL];
} else {
NSWindow *win = ... // get my window
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
}
}];
}
I don't know if these are the source of your problems, but here are some things I'm seeing:
-[NSFileManager URLForUbiquityContainerIdentifier:] may take a while, so you shouldn't invoke it on the main thread. see the "Locating the Ubiquity Container" section of this blog post
Doing this on the global queue means you should probably use an allocated NSFileManager and not the +defaultManager.
The block passed to the byAccessor portion of the coordinated write is not guaranteed to be called on any particular thread, so you shouldn't be manipulating NSWindows or presenting modal dialogs or anything from within that block (unless you've dispatched it back to the main queue).
I think pretty much all of the iCloud methods on NSFileManager will block until things complete. It's possible that what you're seeing is the method blocking and never returning because things aren't configured properly. I'd double and triple check your settings, maybe try to simplify the reproduction case. If it still isn't working, try filing a bug or contacting DTS.
Just shared this on Twitter with you, but I believe when using NSDocument you don't need to do any of the NSFileCoordinator stuff - just make the document ubiquitous and save.
Hmm,
did you try not using a ubiquity container identifier in code (sorry - ripped out of a project so I've pseudo-coded some of this):
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *iCloudDocumentsURL = [[fm URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:#"Documents"];
NSURL *iCloudFileURL = [iCloudDocumentsURL URLByAppendingPathComponent:[doc.fileURL lastPathComponent]];
ok = [fm setUbiquitous:YES itemAtURL:doc.fileURL destinationURL:iCloudRecipeURL error:&err];
NSLog(#"doc moved to iCloud, result: %d (%#)",ok,doc.fileURL.fileURL);
And then in your entitlements file:
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>[devID].com.yourcompany.appname</string>
</array>
Other than that, your code looks almost identical to mine (which works - except I'm not using NSDocument but rolling it all myself).
If this is the first place in your code that you are accessing iCloud look in Console.app for a message like this:
taskgated: killed yourAppID [pid 13532] because its use of the com.apple.developer.ubiquity-container-identifiers entitlement is not allowed
Anytime you see this message delete your apps container ~/Library/Containers/<yourAppID>
There may also be other useful messages in Console.app that will help you solve this issue.
I have found that deleting the app container is the new Clean Project when working with iCloud.
Ok, So I was finally able to solve the problem using Dunk's advice. I'm pretty sure the issue I was having is as follows:
Sometime after the WWDC video I was using as a guide was made, Apple completed the ubiquity APIs and removed the need to use an NSFileCoordinator object while saving from within an NSDocument subclass.
So the key was to remove both the creation of the NSFileCoordinator and the call to -[NSFileCoordinator coordinateWritingItemAtURL:options:writingItemAtURL:options:error:byAccessor:]
I also moved this work onto a background thread, although I'm fairly certain that was not absolutely required to fix the issue (although it was certainly a good idea).
I shall now submit my completed code to Google's web crawlers in hopes of assisting future intrepid Xcoders.
Here's my complete solution which works:
- (IBAction)moveToOrFromCloud:(id)sender {
NSURL *fileURL = [self fileURL];
if (!fileURL) {
NSBeep();
return;
}
BOOL makeUbiquitous = 1 == [sender tag];
if (makeUbiquitous) {
[self displayMoveToCloudDialog];
} else {
[self displayMoveFromCloudDialog];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doMoveToOrFromCloud:makeUbiquitous];
});
}
- (void)doMoveToOrFromCloud:(BOOL)makeUbiquitous {
NSURL *fileURL = [self fileURL];
if (!fileURL) return;
NSURL *destURL = nil;
NSFileManager *mgr = [[[NSFileManager alloc] init] autorelease];
if (makeUbiquitous) {
NSURL *dirURL = [[MyDocumentController instance] ubiquitousDocumentsDirURL];
if (!dirURL) return;
destURL = [dirURL URLByAppendingPathComponent:[fileURL lastPathComponent]];
} else {
// move to local Documentss folder
NSArray *dirs = [mgr URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
if (![dirs count]) return;
destURL = [[dirs firstObject] URLByAppendingPathComponent:[fileURL lastPathComponent]];
}
NSError *err = nil;
void (^completion)(void) = nil;
if ([mgr setUbiquitous:makeUbiquitous itemAtURL:fileURL destinationURL:destURL error:&err]) {
[self setFileURL:destURL];
[self setFileModificationDate:nil];
completion = ^{
[self hideMoveToFromCloudDialog];
};
} else {
completion = ^{
[self hideMoveToFromCloudDialog];
NSWindow *win = [[self canvasWindowController] window];
[self presentError:err modalForWindow:win delegate:nil didPresentSelector:nil contextInfo:NULL];
};
}
dispatch_async(dispatch_get_main_queue(), completion);
}