Change the wallpaper on all desktops in OS X 10.7 Lion? - cocoa

I would like to change the wallpaper of all desktops (formerly "spaces") on a screen. As of OS X 10.6 there is a category to NSWorkspace which allows the setting of the wallpaper, however, when I use this function only the wallpaper of the current desktop gets changed and all the other desktops remain unchanged.
I then looked at the desktop preferences plist and wrote a class that modifies it to reflect the changes I want (basically set a new image file path). After the new file was saved I sent the com.apple.desktop "BackgroundChanged" notification - Google if you don't know what I am talking about, this was how people changed wallpapers in pre 10.6 days. At first this didn't produce any result, so instead of "nil" as userInfo dictionary I sent the exact same userInfo dictionary along as Apple does when you change the wallpaper in your settings (subscribe to the notification in an app and change the wallpaper in the settings app and you will see what it looks like). Luck helped me here, when I sent the notification this way for some reason the Dock crashed and when it reloaded, it loaded the settings from the preferences file thus displaying my changes.
This works on 10.7.1, however, I would a) rather not have the bad user experience of the dock crashing and reloading, and b) use a path that is more or less guaranteed to work in future releases as well. Exploiting a bug doesn't seem like a stable path.
Any other ideas on how to change the wallpaper of all desktops? I am also unsure whether the current behaviour of the NSWorkspace wallpaper category is intended or a bug, however, judging from the behaviour of the wallpaper preferences pane it seems that the former is the case.

There is no api for setting the same wallpaper to all screens or all spaces, NSWorkspace setDesktopImageURL it is implemented as such that it only sets the wallpaper for the current space on the current screen, this is how System Preferences does it too.
Besides the volatile method of manually modifying the ~/Library/Preferences/com.apple.desktop.plist (format could change) and using notifications to reload it (crashes you experienced) what you can do is set the wallpaper to spaces as the user switches to it , e.g. look for NSWorkspaceActiveSpaceDidChangeNotification (if your application is not always running you could tell the user to switch to all spaces he wants the wallpaper to apply to) , arguably these methods are not ideal but at least they are not volatile.
-(void)setWallpaper
{
NSWorkspace *sws = [NSWorkspace sharedWorkspace];
NSURL *image = [NSURL fileURLWithPath:#"/Library/Desktop Pictures/Andromeda Galaxy.jpg"];
NSError *err = nil;
for (NSScreen *screen in [NSScreen screens]) {
NSDictionary *opt = [sws desktopImageOptionsForScreen:screen];
[sws setDesktopImageURL:image forScreen:screen options:opt error:&err];
if (err) {
NSLog(#"%#",[err localizedDescription]);
}else{
NSNumber *scr = [[screen deviceDescription] objectForKey:#"NSScreenNumber"];
NSLog(#"Set %# for space %i on screen %#",[image path],[self spaceNumber],scr);
}
}
}
-(int)spaceNumber
{
CFArrayRef windowsInSpace = CGWindowListCopyWindowInfo(kCGWindowListOptionAll | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
for (NSMutableDictionary *thisWindow in (NSArray *)windowsInSpace) {
if ([thisWindow objectForKey:(id)kCGWindowWorkspace]){
return [[thisWindow objectForKey:(id)kCGWindowWorkspace] intValue];
}
}
return -1;
}

Related

Register for global file drag events in Cocoa

I'm trying to be notified when a OS X user is dragging any file in OS X, not only in my app.
My current approach was using addGlobalMonitorForEventsMatchingMask:handler: on NSEvent, as follows:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask handler:^(NSEvent* event) {
NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];
NSLog(#"%#", [pb propertyListForType:NSFilenamesPboardType]);
}];
This works partially - the handler is being called when I start dragging a file from my desktop or Finder, however it also is being called when I perform every other operation that contains a left-mouse-drag, e.g. moving a window. The issue is that the NSDragPboard still seems to contain the latest dragged file URL e.g. when I let off the file and start moving a window, which makes it hard to distinguish between these operations.
TL;DR - I am interested in file drag operations system-wide. I do not need any information about the dragged file itself, just the information that a file drag operation has been started or stopped. I would appreciate any hint to a possible solution for this question.
After having talked to Apple DTS, this is most likely a bug. I have filed rdar://25892115 for this issue. There currently seems to be no way to solve my original question with the given API.
To solve my problem, I am now using the Accessibility API to figure out if the item below the cursor is a file (kAXFilenameAttribute is not NULL).
NSPasteboard* pb = [NSPasteboard pasteboardWithName:NSDragPboard];
NSArray* filenames = [pb propertyListForType:NSFilenamesPboardType];
NSInteger changeCount = pb.changeCount;
//when moving a window. the changeCount is not changed, use it to distinguish
if (filenames.count > 0 && self.lastChangeCount != changeCount){
self.lastChangeCount = changeCount;
//your code here
}

How do you display multiple files in a gui field on Mac OSX

Using a Windows file dialog if multiple files are selected they are double quote limited and separated by a space as follows:
"C:\MusicMatched2\Gold Greatest Hits" "C:\MusicMatched2\The Trials of Van Occupanther"
But is this what OSX does as well, I can't seem to find an example with applications on my mac, but I need to do the correct way to display multiple filenames in a simple textfield of a Gui application on a mac.
You're not supposed to present file paths to the user on the Mac. First of all, the user's view of the file hierarchy is not the same as the low-level view represented by paths. The Finder and the standard file dialogs maintain a consistent illusion of a file system hierarchy which doesn't actually exist. For example, each volume is a separate root in the Finder, but volumes other than the boot volume appear under /Volumes in the Unix file system. The Finder has a Computer view which has no representation in the Unix file system. Often, the Finder will represent the user's home folder as a independent root, even though the user can also usually get there from (boot drive) > Users > (user's account name).
There are other subtleties. For example, the names of various folders can be localized for display. However, they are never localized at the Unix path level. So, there will always be a folder whose path is /Applications, but it would be displayed as "Programme" on a German system.
Your app should not pierce this veil and expose users to the Unix-style paths of files.
There are APIs for obtaining the "display name" of a file or the array of display components to display (which is not the same as obtaining the display name of each element of the path). You could concatenate the display components with some separator string to make a text string for a path, but there's not really a standard separator.
The more correct approach is to use a "path control" (NSPathControl). This is a GUI widget that displays a file's location in the standard manner, with the standard separator, etc.
If you need to display multiple file locations, you might consider doing what the Finder does when you do a search. It shows the matches in a list and as you select them it shows the location of each in a path control below the list. You could also show a table with the display names in one column and a path control showing the location in another column.
A typical use of NSOpenPanel looks like this
-(void)showFileSelector {
NSOpenPanel *openpanel = [NSOpenPanel openPanel];
[openpanel setDelegate:self];
[openpanel setCanCreateDirectories:NO];
[openpanel setCanChooseFiles:YES];
[openpanel setAllowsMultipleSelection:YES];
[openpanel setCanChooseDirectories:NO];
[openpanel beginSheetModalForWindow:self.window
completionHandler:^(NSInteger result) {
if (result == NSOKButton) {
[self doSomethingWithURLS:openpanel.URLs];
}
}];
}
so in your case the most important switch is [openpanel setAllowsMultipleSelection:YES];
and you are going to want to make your object which presents the panel conform to NSOpenSavePanelDelegate (or some other object) then call
[openpanel setDelegate:self];
which has the following method available
- (void)panelSelectionDidChange:(id)sender where the sender is the panel
- (void)panelSelectionDidChange:(NSOpenPanel *)sender {
NSArray *urlsSelected = sender.URLs;
NSMutableArray *final = [#[] mutableCopy];
for (NSURL *url in urlsSelected) {
[final addObject:[NSString stringWithFormat:#"\"%#\"",[url path]]];
}
[self.labelToPresentIn setStringValue:[final componentsJoinedByString:#","];
}
So each time the selection changes your label will change.
Thats a explicit answer to how to do what you are asking. Where you choose to present the label with that info is up to you. NSOpenPanel has a property accessoryView which allows you to glue an arbitrary view onto the panel. Like this…
So for a panel with multiple selection enabled.
As the other commenters mention don't try and make your OS X app look and act like Windows. Windows is Windows and OS X is OS X. Different OS's , Different UX.
What you are asking would look a bit weird in OS X. But maybe you have a better idea of what you want.
Try and observe how other OS X apps use the save and open panels.

Notification of active document change on OS X?

I'm using NSWorkspace's NSWorkspaceDidActivateApplicationNotification to detect when the active application changes. I get NSRunningApplication from the userInfo key of the notification.
I need to get a notification when the active document changes. I can get the active document by using the accessibility framework's NSAccessibilityDocumentAttribute key through AXUIElementCopyAttributeValue().
I need a more accurate way of detecting when the document changes other than polling. Some applications use multiple windows, while others use a single window with multiple tabs. With tabbed applications the window returns the currently viewed document.
I don't have to use the accessibility framework. AppleScript (scripting bridge) seems to also be able to get a window's document, but the accessibility framework seems to work with more applications.
I only care about the active document, of the active window, of the active application. What currently has focus on the system.
I've been testing with applications like Sublime Text 2, and Xcode. Sublime returns the currently selected tab, where Xcode returns the active project.
I was actually trying to achieve exactly the same thing and I think I've found a solution for it.
What I did was using CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
That will give you a list of all the active windows, including windows that you probably don't care about.
I only care about windows that have kCGWindowLayer = 0; so I filtered the windows that are on layer 0.
Here's how I did it:
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
NSMutableArray *data = [(__bridge NSArray *) windowList mutableCopy];
NSMutableArray *filteredData = [[NSMutableArray alloc] initWithCapacity:10];
for (NSMutableDictionary *theDict in data) {
id layer = [theDict objectForKey:(id)kCGWindowLayer];
if ([layer intValue] == 0) {
[filteredData addObject:theDict];
}
}
NSLog(#"window: %#", filteredData);
This might be the most elegant solution, so if anyone else has a better idea, please share. Also you should have a look at Apple's demo app Son of Grab.

Cocoa - go to foreground/background programmatically

I have an application with LSUIElement set to 1. It has a built-in editor, so I want the application to appear in Cmd+Tab cycle when the editor is open.
-(void)stepIntoForeground
{
if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) return;
if (counter == 0) {
ProcessSerialNumber psn = {0, kCurrentProcess};
OSStatus osstatus = TransformProcessType(&psn, kProcessTransformToForegroundApplication);
if (osstatus == 0) {
++counter;
} else {
//...
}
}
}
-(void)stepIntoBackground
{
if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) return;
if (counter == 0) return;
if (counter == 1) {
ProcessSerialNumber psn = {0, kCurrentProcess};
OSStatus osstatus = TransformProcessType(&psn, kProcessTransformToUIElementApplication);
if (osstatus == 0) {
--counter;
} else {
//..
}
}
}
The problems are:
there's also a Dock icon (not a big deal);
there's also Menu, that is not a big deal too, but they appear not always.
Is there any way to disable menu at all or to make it appear always in foreground? Thanks in advance.
This is how we do it.
(Works 10.7+)
DO NOT USE LSBackgroundOnly NOR LSUIElement in the app plist
Add and init your menu and NSStatusBar menu
After app initialized but not yet shown any window take a place where you might want to show the first window if any. We use applicationDidFinishLaunching.
If you do not want to show any window yet after app initialized use
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
on 10.9 you can use at last the otherwise much correct
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
If you should open any window after app init finished than simply show the main window
Maintain your list of windows
If last window closed, call
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
on 10.9 you can use at last the otherwise much correct
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
When your first window shown next time, call
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
[[self window] makeKeyAndOrderFront:nil];
This should do the trick, if at least one app window is visible you will have menu, dock icon with state signaled, and cmd+tab element with your app, if last app window closed only your NSStatusBar element stays.
Known issues:
The first step is important because without that if a system modal dialog suspends your startup (f.e. your app is downloaded from the net and become quarantined a confirmation dialog might appear at first startup depending on your security settings) your menubar might not be owned by your app after your first app window shown.
Workaround: Starting as normal app (step 1.) would solve this problem, but will cause another small one, your app icon might appear for a moment in the dock at startup even if you would like to startup without any window shown. (but we can deal with this, not owning the menubar was a bigger problem for us, so we chose this instead)
Changing between NSApplicationActivationPolicyRegular and NSApplicationActivationPolicyAccessory (or NSApplicationActivationPolicyProhibited on OSes bellow 10.9) will kill your tooltip of status bar menu element, the tooltip will be shown initially but will not ever after the second call of NSApplicationActivationPolicyAccessory -> NSApplicationActivationPolicyProhibited
Workaround: We could not find a working workaround for this and reported to Apple as a bug.
Changing from NSApplicationActivationPolicyRegular to NSApplicationActivationPolicyAccessory has other problems on some OS versions like there might be no more mouse events in visible app windows sometimes
Workaround: switch first to NSApplicationActivationPolicyProhibited (take care this leads to unwanted app messages, like NSApplicationWillResignActiveNotification, NSWindowDidResignMainNotification, etc. !)
Changing from NSApplicationActivationPolicyAccessory to NSApplicationActivationPolicyRegular is bogus as on some OS versions
the app main menu is frozen till the first app front status change
the app activated after this policy not always get placed front in the application order
Workaround: switch first to NSApplicationActivationPolicyProhibited, take care the final switch to the desired NSApplicationActivationPolicyRegular should be made delayed, use f.e. dispatch_async or similar
With swift 4, in applicationDidfinishLaunching(_:Notification)
NSApplication.shared.setActivationPolicy(.regular)
did the trick for me, but I was only trying to get keyboard focus to my programmatically created window. Thanks.
You can set App "Application is agent (UIElement)" to YES in your plist file.
EDIT:
I think there are some hacks to do this.
But it's really not the way it's meant to be.
Cmd+tab is for getting an application to foreground, but if you don't have a menu bar, it doesn't look like foreground to the user.
I'd rather make a menu bar to access the app.

Setting printer-specific options on an NSPrintOperation without a panel

this question has bothered me on and off for about a year, and I thought perhaps someone else would have experience with a similar situation.
Goal: on Mac OS X 10.6-7, to print multiple NSViews to EPSON Stylus Pro 4880 printers using a defined resolution and 'high speed' setting, without showing a print panel.
Current situation: I can create successful NSPrintOperations for each NSView, but if I do not show a print panel, it appears the printer's default resolution is used, which is far too high, and slow, for my needs.
Best solution I have so far: I have tried showing the print panel and defining a Mac OS 'preset' which has the correct print resolution and high speed settings already enabled. The downside here is that the Mac preset overrides the number of copies I have set via NSCopies, which is a problem. The other difficulty of course is having someone always around to press the 'OK' button a few thousand times a day.
Where I'm up to
When the NSPrintOperation runs its panel, it has to set the EPSON-specific printer settings somewhere, but I cannot find where it is saved. They don't appear to be set in [NSPrintInfo printSettings].
I have looked at the PPD for the printer, but I can't find the high speed setting anywhere, and the default resolution defined in the PPD is not actually used as the default when printing. It appears EPSON has their own driver settings which are not taken from the PPD I have, and I am not sure how to set them manually.
Basically, running the NSPrintOperation with a print panel and preset overrides all the settings, including the ones I don't want to override. Running it without the print panel leaves all the settings as default, which is not what I want. Can anyone point me in the right direction to find a solution in between those two?
After the NSPrintOperation's runOperation runs with the dialog, look in PMPrintSettings, the printer-specific parameters may be there. I suppose you could persist PMPrintSettings for the future somehow and load the via updateFromPMPrintSettings.
This is unfortunately the best solution I have found so far though I hate to call it 'best', or even a 'solution'. It comes back to this: run an operation with a panel, and then programmatically 'click' the Print button.
[op runOperationModalForWindow: self.window delegate: self didRunSelector: nil contextInfo: nil];
NSPanel *panel = (NSPanel*)self.window.attachedSheet;
for (NSView *view in ((NSView*)panel.contentView).subviews)
{
if (view.class == [NSButton class])
{
NSButton *button = (NSButton*)view;
if ([button.title isEqualToString: #"Print"])
[button performClick: self];
}
}
or
op.runOperationModalForWindow(window, delegate: nil, didRunSelector: nil, contextInfo: nil)
(window.attachedSheet?.contentView.subviews.filter({ $0 is NSButton }) as [NSButton]).filter({ $0.title == "Print" }).first?.performClick(self)
The downside obviously is a window is needed, while I was hoping to run this as a headless server application. I have tried working with Core Printing and PMPrinter/PMPrintSettings and so forth to no avail. The only thing I have not yet tried is talking to CUPS directly. Maybe I'll save that for a rainy day!

Resources