OSX: How to filter out the menu bar from CGWindowListCopyWindowInfo - macos

I'm using CGWindowListCopyWindowInfo to grab the list of windows on the desktop. I'd like to filter out only the visible windows using values of the Window List keys. The one window I'm having trouble is the OSX menu bar. One solution I've thought of using is looking at the X and Y of the window bounds. No visible windows seem to have both of those equal to 0, but I'm not sure how reliable this method is.
One other way to do this would be to compare the PID number to that of the OSX Window Server, but I'm not sure how to get that. Can anyone point me towards the right API or know of a reliable way to filter out the menu bar?
Edit: I have the same code as kondy below with the following additions since the listOptions themselves aren't good enough:
CGRect windowBounds;
CGRectMakeWithDictionaryRepresentation((CFDictionaryRef) [windowInfo objectForKey:(id)kCGWindowBounds], &windowBounds);
if (!(windowBounds.origin.x == 0 && windowBounds.origin.y == 0))
{
// Work with windows that aren't the Menubar
}

I have found an answer to filter out the "Window Server":
CGWindowListOption listOptions = kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements;
CFArrayRef windowList = CGWindowListCopyWindowInfo(listOptions, kCGNullWindowID);
Using these ORed options, I get a result as same as mac's "Windowed processes" in "Activity Manager"
I hope it will help you!

Related

How to position a Cocoa window to minimize overlap with other windows?

I'm working on a Cocoa app that has a main window and a preview window. I'd like the preview window to automatically position itself to minimize overlap with other windows — it definitely shouldn't overlap my app's main window, and it should try not to overlap other applications' windows.
How should I do this?
Apple has a sample application called Son of Grab that shows you how to consume all current windows.
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); will give you the current windows. You can interrogate the kCGWindowBounds value to get the bounds of the returned windows.

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.

Cocoa CGWindowListCopyWindowInfo & AXUIElementSetAttributeValue

I'm using the following piece of code to get all windows:
CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
This gives me an array of dictionaries as found here: Front most window using CGWindowListCopyWindowInfo
Then I check their bounds to see if the mouse is in it, and the first I find is is the one my mouse is over.
Then I would like to be able to move it. I know how to use AXUIElementSetAttributeValue to move a window, though then I need a AXUIElementRef, which I can't figure out how to get out of the dictionary.
How can I solve this?
There is no way to go from a window number to an AXUIElementRef.
The closest you can get is to find the owner of the window, then ask it through Accessibility for its windows and look for one with the same title—but an app may have more than one window with the same title, so you need to figure out what you'll do in that case.
The only ways to move another application's windows are by AppleScript or by Accessibility. Using Accessibility is much more reliable than AppleScript (many, if not most, apps have incomplete and/or flaky scripting support). You can get the process ID and window title from the same window dictionary that gives the window number, but that's as specific as you can get. With AppleScript, you could only use the window's index within its application's window list, and hope that the list hasn't changed order in the split-second between you computing the index and trying to use it.

Window resizable - kAXGrowAreaAttribute always return NULL

In my app I want to check if windows from others apps are resizable.
I´m using the accessibility API to test if the window has the kAXGrowAreaAttribute attribute (if NULL is not resizable) as Peter Hosey answered in this question.
The problem is that the kAXGrowAreaAttribute returned value is always NULL it doesn´t matter if the window is resizable or not. Note: to retrieve the value I´m using the UIElementUtilities class from the Apple UIElementInspector example (I also have tried using AXUIElementCopyAttributeValue directly with the same result).
Any idea? I´m working in Lion, could be this the problem? Thanks in advance.
EDITED:
Playing around with the UIElementUtilities class methods I found a solution.
Just use the method
+ (BOOL)canSetAttribute:(NSString *)attributeName ofUIElement:(AXUIElementRef)element
with the kAXSizeAttribute and the focused window. It returns YES or NO depending if the window is sizable or not...
It probably is because you're in Lion. The size box was killed off; resizable windows are resizable at every edge now.
And yes, testing whether the size can be changed is probably the right way. It seems to work for me in Snow Leopard.
Swift 5 version:
func isResizable(axElement: AXUIElement) -> Bool {
var resizable: DarwinBoolean = true
let status = AXUIElementIsAttributeSettable(axElement, kAXSizeAttribute as CFString, &resizable)
if status != .success {
print("unable to determine if window is resizable")
}
return resizable.boolValue
}

get CGWindow id of focussed window

Do you know how I can get the CGWindow Id of any focussed window (belonging or not to the current application) ?
Thanks in advance for your help :)
Regards,
One way is to use CGWindowListCopyWindowInfo to get the list of all windows like this:
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
Examine this list to find all the windows at window level 0 (these are the normal windows).
I'm fairly certain that the array returned will be in the order the windows are layered on the screen. If not, you can sort by the "windowOrder" key. Look at the SonOfGrab sample code for more on how to use this API.

Resources