Reputation: 1
I would like find a way to track changes to the onActiveSpace property of an NSWindow.
I used the KVO method to monitor this property, but did not receive the property change notification. Does this property not support KVO? If so, is there any way to confirm whether a certain attribute supports KVO? Here is my code:
- (instancetype)initWithWidgetSpaceTracker:(WidgetSpaceTracker*)owner {
if ((self = [super init])) {
DCHECK(owner);
_owner = owner;
NSWindow* window = _owner->GetNSWindow();
if (window) {
[window addObserver:self
forKeyPath:@"onActiveSpace"
options:NSKeyValueObservingOptionNew
context:nil];
}
}
return self;
}
- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id>*)change
context:(void*)context {
DCHECK(_owner);
if ([keyPath isEqual:@"onActiveSpace"]) {
_owner->OnSpaceActiveChanged([change[NSKeyValueChangeNewKey] boolValue]);
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
Upvotes: 0
Views: 56
Reputation: 621
onActiveSpace
can change under the following circumstances:
When the user changes the stage they are currently viewing. For example, assume that in Mission Control there are Stage 1 and Stage 2, and the window is on Stage 1 while the user is viewing Stage 1. In this case, onActiveSpace is YES; however, if the user switches to Stage 2, onActiveSpace will change to NO. This stage change can be detected via the NSWorkspaceActiveSpaceDidChangeNotification
.
When the window moves to another stage. This cannot be achieved using public APIs. Instead, you can use the SkyLight API as shown below:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
APPKIT_EXTERN NSNotificationName const MA_NSWindowActiveSpaceDidChangeNotification;
APPKIT_EXTERN void startWindowActiveSpaceObservation(void);
NS_ASSUME_NONNULL_END
/* ——————————— */
#import <objc/message.h>
#import <objc/runtime.h>
#include <dlfcn.h>
NSNotificationName const MA_NSWindowActiveSpaceDidChangeNotification = @"MA_NSWindowActiveSpaceDidChangeNotification";
void _ma_NSWindow_Category_didReceiveEvent(unsigned int type, const void *data, size_t length, const void *context, unsigned int cid) {
long windowNumber = *(long *)data;
NSWindow *window = reinterpret_cast<id (*)(id, SEL, long)>(objc_msgSend)(NSApp, sel_registerName("windowWithWindowNumber:"), windowNumber);
if (window == nil) return;
[NSNotificationCenter.defaultCenter postNotificationName:MA_NSWindowActiveSpaceDidChangeNotification object:window];
}
void startWindowActiveSpaceObservation(void) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen("/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight", RTLD_NOW);
void *symbol = dlsym(handle, "SLSRegisterConnectionNotifyProc");
assert(symbol != NULL);
__kindof NSNotificationCenter *notificationCenter = NSWorkspace.sharedWorkspace.notificationCenter;
unsigned int connectionID = reinterpret_cast<unsigned int (*)(id, SEL)>(objc_msgSend)(notificationCenter, sel_registerName("connectionID"));
// ____NSDoOneTimeWindowNotificationRegistration_block_invoke
reinterpret_cast<SInt16 (*)(unsigned int, void *, unsigned int, void *)>(symbol)(connectionID, (void *)_ma_NSWindow_Category_didReceiveEvent, 0x33b, NULL);
});
}
Then, you can use it like this:
// invoke from `-applicationDidFinishLaunching:`
startWindowActiveSpaceObservation();
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_didChangeWindowActiveSpace:) name:MA_NSWindowActiveSpaceDidChangeNotification object:nil];
Thus, when notifications for NSWorkspaceActiveSpaceDidChangeNotification
and MA_NSWindowActiveSpaceDidChangeNotification
are received, the onActiveSpace value will change.
Upvotes: 0