0

I'm trying to create an OS X application that allows an external application (i.e. textEdit) to float on top of the running application without keeping focus, but never disappearing. Basically what I'm trying to do is mimic the ForceQuit Application (press option-command-esc to see it), where it's always on top but doesn't always have focus.

What I'm doing now with limited success is this:

Creating a NSTimer to keep putting it on top:

_timer = [NSTimer scheduleTimerWithTimeInterval:0.1 target:self selector:@selector(keepTextEditOnTop) userInfo:nil repeats:YES];

...

Bring textEdit to the front:

- (void)keepTextEditOnTop {
    AXUIElementRef appRef = AXUIElementCreateApplication(_textEditProcessID);
    AXUIElementSetAttributeValue(appRef, kAXFrontmostAttribute, kCFBooleanTrue);
}

The textEdit window will stay on top if you click off it (make it lose focus). But there are two problems. 1.) It will flash briefly putting it back on top 2.) It will essentially always keep focus so you can't interact with the underlying app.

From the appRef, you can get the window:

CFArrayRef windowList;
AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute, (CFTypeRef *)&windowList);

AXUIElementRef windowRef = (AXUIElementRef) CFArrayGetValueAtIndex(windowList, 0);

But I don't think it will be possible to convert it into a NSWindow. If I could do that, I imagine I could make it a child window of the main window or maybe just a floating window.

Any ideas on how this could be done?

Edit- Adding suggestions from @TheDarkKnight

I'm trying this:

- (void)testing{

    CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
    for (NSMutableDictionary* entry in (__bridge NSArray*)windowList)
    {
        NSString* ownerName = [entry objectForKey:(__bridge id)kCGWindowOwnerName];
        NSString *windowNum = [entry objectForKey:(__bridge id)kCGWindowNumber];
    if([ownerName isEqualToString:@"Sublime Text"]){
       [self holdToTheFront:[windowNum integerValue]];
    }
}
CFRelease(windowList);

}

Getting a EXC_BAD_ACCESS error as noted in the comment below:

- (void)holdToTheFront:(NSInteger)winNum {
     objc_object*  nsviewObject = reinterpret_cast<objc_object *>(winNum);

    //Getting a "Thread 1:EXC_BAD_ACCESS (code=1, address=0x18)" at the line below:
    NSWindow* nsWindowObject = ((id (*)(id, SEL))objc_msgSend)((__bridge NSView *)nsviewObject, sel_registerName("window"));

    int NSWindowCollectionBehaviorCanJoinAllSpaces = 1 << 0;
    ((id (*)(id, SEL, NSUInteger))objc_msgSend)(nsWindowObject, sel_registerName("setCollectionBehavior:"), NSWindowCollectionBehaviorCanJoinAllSpaces);
}
user3564870
  • 385
  • 2
  • 13

1 Answers1

0

In order for an application window to be visible on all spaces, you can do something like this: -

objc_object* nsviewObject = reinterpret_cast<objc_object *>(windowObject);
objc_object* nsWindowObject = objc_msgSend(nsviewObject, sel_registerName("window"));
int NSWindowCollectionBehaviorCanJoinAllSpaces = 1 << 0;
objc_msgSend(nsWindowObject, sel_registerName("setCollectionBehavior:"), NSWindowCollectionBehaviorCanJoinAllSpaces);

Where windowObject is the windowNumber of the application window you want to remain on top.

Community
  • 1
  • 1
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • Thanks! I'll give it a shot in the morning. – user3564870 Nov 29 '16 at 05:49
  • I don't think it's possible to get a window number for windows that the app doesn't own. Then there would be the assumption that the external app is Cocoa. – user3564870 Nov 29 '16 at 18:14
  • Does [this](http://stackoverflow.com/questions/2107657/mac-cocoa-getting-a-list-of-windows-using-accessibility-api) help? – TheDarkKnight Nov 30 '16 at 10:01
  • yes. I added the code I'm working on based on your input to my question. I'm getting a EXC_BAD_ACCESS error as noted by the comment. – user3564870 Nov 30 '16 at 16:20
  • Check the address of `nsviewObject`. I suspect the issue may be that the number returned is in the VM space of the process that you're retrieving from, not your own process. If this is the case, then you won't be able to do it this way. – TheDarkKnight Dec 01 '16 at 09:15