Thursday, October 3, 2019

Generating "Always On Top" NSWindow in macOS across all detected displays

Also: Using UIKit & Cocoa Frameworks using Objective-C

In macOS or OS X, written in either Objective-C or Swift Langues, you may want an application to run both in full screen and at the top most level in the window layers, covering all apps behind it. This is known in some circles as the Z-Order or z dimension in a x-y-z Cartesian Plane. It's aptly named after Frenchman Rene Descartes, a highly revered & gifted Mathematician, and also Philosopher who made invaluable contributions to his respective fields. The x, y is the horizontal axis and vertical axis, while z-axis is the depth when you visualise the screen in three dimensions (3-D).

This snippet below I had written in Obj-C, and can be ported to Swift easily. The function is quite rudimentary in syntax and structuring. In a nutshell it enumerates all of the displays, and places a semi-transparent window, running full screen with a floating point opacity alpha value. If you want the background apps to still be visible then it's imperative that you set a low alpha float value (<= 0.5f).

Also, there are plenty of customisations possible to configure with classes NSScreen, NSWindow, and more. You can add a fullscreen subview from an NSImage or NSImageView and affix it to the primary screen, or all screens, whichever you fancy. There are also passed arguments specifying various colour values using class NSColor (can be specified in either hexadecimal #fffff or RGB float values eg: {1f,1f,1f}). You can also customise the behaviour of the full screen window, whether it can be resized or borderless, it's completely up to you! There are a plethora of possible configurations and styles defined in the class specifications or header.

This first function is a helper function that returns a UIImageView object based on input of image and frame, which are arguments passed into method. It sets the boundaries and affixes the NSImage as an NSImageView, as well as the x,y,h,w as a CGRect dimension frame.



+ (NSImageView *) getImage:(NSString *) image
                     frame:(CGRect) frame
{
    NSImageView *imageView = [[NSImageView alloc] init];
    [imageView setFrame:frame];
    [imageView setImage:[NSImage imageNamed:image]];
    [imageView setBoundsOrigin: NSZeroPoint];
    return imageView;
}


Below this function modifies the enumerated Window whilst passing in the frame (x,y, w, h dimensions), and the Screen which are passed in. It also specifies the window behaviour and background colour, which in this case it's an NSColor defined as a constant:



+ (NSWindow *) modifyWindow:(NSWindow *) window
                      frame:(NSRect) frame
                     screen:(NSScreen *) screen
{

    [window setBackgroundColor:t_colour];
    [window setCollectionBehavior:  
  NSWindowCollectionBehaviorStationary |
                                    NSWindowCollectionBehaviorCanJoinAllSpaces |
                                    NSWindowCollectionBehaviorFullScreenAuxiliary |
                                    NSWindowCollectionBehaviorTransient ];
    [window makeKeyAndOrderFront:NSApp];
    [window setAlphaValue:kConstAlphaDefault];
    [window setLevel:kCGMainMenuWindowLevel-1];
    [window toggleFullScreen:nil];
    return window;
}


When the NSScreens are enumerated, each member is sent as an argument screen, also idx number (which is redundant since we can simply get the screen index internally from the members of the class). Anyhow, we create an NSRect for the dimensions (x,y, height and width). Since we are interested in covering the entire space of said screen we use the size height and width, and start at 0, 0 (x,y). We finally affix an NSImageView with kConstImageBlock with is just a char constant defined in some header. This subview function was the first function we described in the first code snippet. Also there is a flag to detect the "primary" display. The NSImageView is only affixed as a subview on the "primary" display. The other displays just get a coloured in, however if you desire you can add the Image by removing the if then conditional, see below:



+ (void) displayBlockScreen:(NSScreen *) screen
                      index:(int) idx
{
    NSMutableArray* windows = [[NSMutableArray alloc] init];
    NSRect frame = NSMakeRect(0,
                              0,
                              [screen frame].size.width,
                              [screen frame].size.height);
    NSWindow *window  = [[NSWindow alloc] initWithContentRect
frame styleMask: NSWindowStyleMaskBorderless backing: NSBackingStoreBuffered defer: NO screen: screen];
    
    if (screen == [NSScreen mainScreen])
    {
        NSImageView *imageView = [self getImage:kConstImageBlock frame:frame];
        [window.contentView addSubview:imageView];
    }
    [windows addObject:[self modifyWindow:window
                                    frame:frame
                                   screen:screen]];
}


Last but certainly not least is the very first, entry point function which does exactly what it's name indicates, displayEnum. It sequentially enumerates thru all of the detected NSScreen screens value array, starting at index 0, and accounting for each of the Monitor Screen Displays that are found. The "do while" loop can be a "for next" loop or a bunch of other iterator types, but "do while" was on my mind, and was feeling adventurous. The loop stops incrementing the index as the array count is finally reached and the condition is met, see below:


+ (voiddisplayEnum
{
    int idx = 0;
    NSArray *screens = [NSScreen screens];
    NSUInteger count = [screens count];
    
    NSLog(@"\n\tTotal Screens: [%lu]\n\n", count);
    
    do {
        NSScreen *screen = [screens objectAtIndex:idx];
        [self displayBlockScreen:screen
                           index:idx];
        idx++;
    while (idx < count);
}


Obliged you chose to read thru this, and visiting my ghetto coding blog.I sincerely hope that this code can be of some service to you, a software engineer trying to dynamically generate full covering, always on top screen(s), with additional need for image subviews affixing to the respective detected window(s). Hopefully the code above can easily facilitate the need to detect multiple display configurations, as well as a designation of a "primary" display. Surely out there somewhere in the deepest recesses of this fine universe exists a use case scenario, imperatively requesting such specialised screen and window behaviour described above? I'm quite confident that the practical application exists somewhere in the vast macOS user world obusiness, corporate, healtheducation, maybe even cyber-security. 

Cheers & until next time,

-Apollo

No comments:

Post a Comment

Generating "Always On Top" NSWindow in macOS across all detected displays

Also: Using UIKit & Cocoa Frameworks using Objective-C In m acOS or OS X , written in either Objective-C or Swift  Langues, you m...