It seems you want to make an external process's window stay on top of all other applications, while the code I provide here does not accomplish exactly what you are looking for, it is at least somewhat similar, and might be good enough for what you need, depending on your use case. In this example I demonstrate how to keep a CGWindowID
on top of a specific NSWindow *
. Note - the NSWindow *
is the parent window, and it will need to be owned by your app, but the CGWindowID
used for the child window can belong to any application). If you want the NSWindow *
to be the child window, change the NSWindowBelow
option to NSWindowAbove
.
There is a tiny problem with this solution and that is some minor flickering here and there, when the parent window is attempting to gain focus but then loses it immediately - the flicker happens very quickly and intermittently, perhaps it can be overlooked if you are super desperate.
Anyway, the code is...
cocoa.mm
#import "subclass.h"
#import <Cocoa/Cocoa.h>
#import <sys/types.h>
NSWindow *cocoa_window_from_wid(CGWindowID wid) {
return [NSApp windowWithWindowNumber:wid];
}
CGWindowID cocoa_wid_from_window(NSWindow *window) {
return [window windowNumber];
}
bool cocoa_wid_exists(CGWindowID wid) {
bool result = false;
const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
CFIndex windowCount = 0;
if ((windowCount = CFArrayGetCount(windowArray))) {
for (CFIndex i = 0; i < windowCount; i++) {
NSDictionary *windowInfoDictionary =
(__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
if (level.integerValue < kScreensaverWindowLevel) {
NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
if (wid == windowID.integerValue) {
result = true;
break;
}
}
}
}
CFRelease(windowArray);
return result;
}
pid_t cocoa_pid_from_wid(CGWindowID wid) {
pid_t pid;
const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
CFIndex windowCount = 0;
if ((windowCount = CFArrayGetCount(windowArray))) {
for (CFIndex i = 0; i < windowCount; i++) {
NSDictionary *windowInfoDictionary =
(__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
if (level.integerValue < kScreensaverWindowLevel) {
NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
if (wid == windowID.integerValue) {
pid = ownerPID.integerValue;
break;
}
}
}
}
CFRelease(windowArray);
return pid;
}
unsigned long cocoa_get_wid_or_pid(bool wid) {
unsigned long result;
const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
CFIndex windowCount = 0;
if ((windowCount = CFArrayGetCount(windowArray))) {
for (CFIndex i = 0; i < windowCount; i++) {
NSDictionary *windowInfoDictionary =
(__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
if (level.integerValue == 0) {
NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
result = wid ? windowID.integerValue : ownerPID.integerValue;
break;
}
}
}
CFRelease(windowArray);
return result;
}
void cocoa_wid_to_top(CGWindowID wid) {
CFIndex appCount = [[[NSWorkspace sharedWorkspace] runningApplications] count];
for (CFIndex i = 0; i < appCount; i++) {
NSWorkspace *sharedWS = [NSWorkspace sharedWorkspace];
NSArray *runningApps = [sharedWS runningApplications];
NSRunningApplication *currentApp = [runningApps objectAtIndex:i];
if (cocoa_pid_from_wid(wid) == [currentApp processIdentifier]) {
NSRunningApplication *appWithPID = currentApp;
NSUInteger options = NSApplicationActivateAllWindows;
options |= NSApplicationActivateIgnoringOtherApps;
[appWithPID activateWithOptions:options];
break;
}
}
}
void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid) {
[cocoa_window_from_wid(pwid) setChildWindowWithNumber:wid];
}
subclass.mm
#import "subclass.h"
#import <Cocoa/Cocoa.h>
CGWindowID cocoa_wid = kCGNullWindowID;
CGWindowID cocoa_pwid = kCGNullWindowID;
@implementation NSWindow(subclass)
- (void)setChildWindowWithNumber:(CGWindowID)wid {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidBecomeKey:)
name:NSWindowDidUpdateNotification object:self];
cocoa_pwid = [self windowNumber]; cocoa_wid = wid;
[self orderWindow:NSWindowBelow relativeTo:wid];
}
- (void)windowDidBecomeKey:(NSNotification *)notification {
if (cocoa_wid_exists(cocoa_wid)) {
[self setCanHide:NO];
[self orderWindow:NSWindowBelow relativeTo:cocoa_wid];
} else {
cocoa_wid = kCGNullWindowID;
[self setCanHide:YES];
}
}
@end
subclass.h
#import <Cocoa/Cocoa.h>
bool cocoa_wid_exists(CGWindowID wid);
@interface NSWindow(subclass)
- (void)setChildWindowWithNumber:(CGWindowID)wid;
- (void)windowDidBecomeKey:(NSNotification *)notification;
@end
I went an extra mile and added some functions to help you retrieve the appropriate CGWindowID
based on the frontmost CGWindowID
, and if you know the correct CGWindowID
beforehand, via AppleScript, or however you prefer, you may bring it to the front using cocoa_wid_to_top(wid)
, (if the user permits), however this doesn't play well with processes owning multiple visible windows simultaneously, because it brings all windows owned by the process id associated with the given CGWindowID
to the top, so you might not have the CGWindowID
you wanted to be on the absolute top of the window stack necessarily. The reason you may want the window to be brought on top of the stack is due to the fact there are cases in which a window may open that you would want to make a child window but it appeared on screen underneath your parent window, thus forcing you to click it before the parent/child relationship of windows can effectively take place.
Documentation below...
NSWindow *cocoa_window_from_wid(CGWindowID wid);
Returns an NSWindow *
from a given CGWindowID
, provided the CGWindowID
belongs to the current app, otherwise an invalid CGWindowID
is returned, which can be represented with the constant kCGNullWindowID
.
CGWindowID cocoa_wid_from_window(NSWindow *window);
Returns a CGWindowID
from a given NSWindow *
, provided the NSWindow *
belongs to the current app, otherwise I believe you will get a segfault. That's what happens in my testing when you know the value of an NSWindow *
and attempt to use it in an app that it doesn't belong to, so don't even try.
bool cocoa_wid_exists(CGWindowID wid);
Returns true
if the a window based on a specified CGWindowID
exists, excluding your screensaver and desktop elements, false
if it doesn't.
pid_t cocoa_pid_from_wid(CGWindowID wid);
A helper function for cocoa_wid_to_top(wid)
which returns the process id, (or pid_t
), associated with the given CGWindowID
.
unsigned long cocoa_get_wid_or_pid(bool wid);
Returns the frontmost CGWindowID
if wid
is true
, otherwise the frontmost process id, (or pid_t
), is the result. Note the return type unsigned long
can be safely casted to and from a CGWindowID
or pid_t
as needed.
void cocoa_wid_to_top(CGWindowID wid);
Attempts to bring all windows that belong to the process id, (or pid_t
), associated with the given CGWindowID
to be the topmost app.
Now for the most important function...
void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid);
Assigns a parent window based on a specified CGWindowID
to the given child window associated with the proper CGWindowID
. The parent window id, (or pwid
), must be owned by the current app, while the child window id, (or wid
), may belong to any application, excluding the screensaver and desktop elements. If the parent or child window ceases to exist, they lose their parent and child relationship to avoid recycled CGWindowID
's from inheriting the relationship. If the parent or child CGWindowID
doesn't exist, they will be set to kCGNullWindowID
, which reliably ends the relationship.
Note this code has been tested in Catalina and indeed works as advertised at the time of writing.
To use the cocoa functions I provided in your C or C++ code you may do this in a header:
typedef void NSWindow;
typedef unsigned long CGWindowID;
extern "C" NSWindow *cocoa_window_from_wid(CGWindowID wid);
extern "C" CGWindowID cocoa_wid_from_window(NSWindow *window);
extern "C" bool cocoa_wid_exists(CGWindowID wid);
extern "C" pid_t cocoa_pid_from_wid(CGWindowID wid);
extern "C" unsigned long cocoa_get_wid_or_pid(bool wid);
extern "C" void cocoa_wid_to_top(CGWindowID wid);
extern "C" void cocoa_wid_set_pwid(CGWindowID wid, CGWindowID pwid);
CGWindowNumber
is of a window not owned by the calling application, you cannot get theNSWindow
– EscaladeSIMBL loads code via the InputManager system, which was developed to support foreign input methods.
– SorasetAttribute("Floating", value: true)
SetAttribute('Level', 3)
it didn't work at all. – Sora