Two fullscreen native windows in two monitors with Adobe AIR

How to use two fullscreen windows in two monitors with Adobe AIR

In my physical computing classes, the need often arises to display content on more than one video output (monitors, projectors, etc.) from a single AIR application. While this is certainly possible, my students often have a hard time figuring it out. This is why I put together this little tutorial.

Please note that this tutorial will show you how to create two fullscreen windows and display each one of them in a different monitor. It will not show you how to extend a single window across multiple monitors. If this is what you need, check out my other tutorial

In this tutorial, we will mostly look at the two following ActionScript objects: NativeWindow and Screen. Please note that these objects are not available in the Flash Player. This tutorial applies to the AIR runtime only.

Here we go. Setting the default window to be fullscreen is easy. Here is how to do it:

stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
stage.color = 0xC02A2A; // red

This basically means: anchor to top left, do not scale and go fullscreen. I’m also making the stage’s color red to make things super obvious

To fill the second screen with a fullscreen window, we first have to create a window. Since you need a NativeWindowInitOptions object to create a window, we’ll first create one:

var nwio:NativeWindowInitOptions = new NativeWindowInitOptions();
nwio.systemChrome = NativeWindowSystemChrome.NONE;
var secondWindow:NativeWindow = new NativeWindow(nwio);

NativeWindowSystemChrome.NONE simply means to not display the window’s menu bar. Then, we need to set the window’s size and position. The easiest way to do that, is to simply grab those informations from AIR’s static Screen.screens array. Since your first monitor is at index 0, you simply copy the bounds of the monitor at position 1 in the array to the bounds property of the new window:

secondWindow.bounds = (Screen.screens[1] as Screen).bounds;

Note that the Screen.screens array is an array of virtual desktops. There is not always a one-to-one match between screens and virtual desktops. For example, if your monitors are in mirroring mode, there will be only one entry in the array.

To display the window, you then have to call: activate().

secondWindow.activate();

The only thing left is to change the properties of the new window’s stage to be the same as the ones of the primary window’s stage. Said simply: make it fullscreen too:

secondWindow.stage.align = StageAlign.TOP_LEFT;
secondWindow.stage.scaleMode = StageScaleMode.NO_SCALE;
secondWindow.stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
secondWindow.stage.color = 0x387D19; // green

To add DisplayObjects to the main screen you simply should simply use stage.addChild() as usual. To add DisplayObjects to the secondary screen, use: secondWindow.stage.addChild().

That’s it. If you are on Windows, you are done. You can get the full code in one block at the end of this article. If however, you are on Mac OS, you should read on… .

The Mac situation

On Mac OS X Mavericks (which is the version I used to test this), the second window does not really go fullscreen with the above code.

A partial workaround is to wait a little and re-apply the bounds:

if (Capabilities.os.indexOf("Mac OS") > -1) {
    setInterval(uglyMacHack, 1000);
}
 
public function uglyMacHack(e:Event = null):void {
    secondWindow.bounds = (Screen.screens[1] as Screen).bounds;
}

Another partial workaround, is to tick off the “Displays have separate Spaces” checkbox in the OS’ Mission Control preference pane.

However, even when using those workarounds an issue remains: when you click on the second window, the menu bar on one or both screens reappear (arghh!). This is a long standing bug (orphaned link) dating back to 2011 that Adobe never fixed.

In any case, here is the full (cross-platform…ish) code. If you run it, your primary monitor should turn red and your secondary monitor should turn green:

package
{
 
    import flash.display.NativeWindow;
    import flash.display.NativeWindowInitOptions;
    import flash.display.NativeWindowSystemChrome;
    import flash.display.Screen;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageDisplayState;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.system.Capabilities;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.text.TextFormatAlign;
    import flash.utils.setInterval;
 
    public class TwoFullscreenNativeWindowsInTwoMonitors extends Sprite {
 
        public var secondWindow:NativeWindow;
 
        public function TwoFullscreenNativeWindowsInTwoMonitors() {
 
            // Ouput screen sizes and positions (for debugging)
            for each (var s:Screen in Screen.screens) trace(s.bounds);                
 
            // Make primary (default) window's stage go fullscreen
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
            stage.color = 0xC02A2A; // red
 
            // Create fullscreen window on second monitor (check if available first)
            if (Screen.screens[1]) {
 
                // Second window
                var nwio:NativeWindowInitOptions = new NativeWindowInitOptions();
                nwio.systemChrome = NativeWindowSystemChrome.NONE;
                secondWindow = new NativeWindow(nwio);
                secondWindow.bounds = (Screen.screens[1] as Screen).bounds;
                secondWindow.activate();
 
                // Second window's stage
                secondWindow.stage.align = StageAlign.TOP_LEFT;
                secondWindow.stage.scaleMode = StageScaleMode.NO_SCALE;
                secondWindow.stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
                secondWindow.stage.color = 0x387D19; // green
 
            }
 
            // Fix a bug on Mac OS X
            if (Capabilities.os.indexOf("Mac OS") > -1) setInterval(uglyMacHack, 1000);
 
        }
 
        public function uglyMacHack(e:Event = null):void {
            secondWindow.bounds = (Screen.screens[1] as Screen).bounds;
            secondWindow.dispatchEvent(new Event(Event.RESIZE));
        }
 
    }
 
}

Hope this helps!

Comments

  1. I’ve been searching for this solution for hours and hours. Thank you!

    What IDE did you use for this? Also, suppose I wanted to use your great script for the purpose of synchronously playing a video on both screens. How would I do as much? To give you a better idea…

    MONITOR 1
    MONITOR 2

    SCENE A
    SCENE B
    SCENE C

    VIEW A1, A2, A3
    VIEW B1, B2, B3
    VIEW C1, C2, C3

    The scenes are video footage. The views are the same as the scenes, but each one has different overlay. The scenes would play on MONITOR 1, while the respective VIEW would play on MONITOR 2 synchronously. When the user presses a predetermined key, the VIEW displaying on MONITOR 2 would cycle to the next view (VIEW A1…A2…A3). When the user presses a separate predetermined key, the next SCENE would load on MONITOR 1 and it’s respective VIEW on MONITOR 2. So basically, there are various scenes that all have overlays (VIEWS), which need to be able to load and run on MONITOR 2 synchronously like a side-by-side comparison, but with the dynamic ability that allows the user to interact with the process and either change the VIEW on a particular SCENE or the SCENE itself. And I need this to run as a standalone. Suggestions?

    1. My IDE is FlashBuilder as it comes bundled with my Creative Cloud membership. As far as running it standalone, its easy to publish an application with Adobe AIR (Mac or PC). As for the two videos, I have never tried it but you should be able to sync them by simply starting playback at the same time. You could also merge them in a single video of which one half is playing on one screen and the other half is playing on another screen using the techiques described here: /blog/how-to-display-a-single-fullscreen-window-across-multiple-screens-in-adobe-air

      1. So I have this array I am using for the videos:

        //Create array of scenes and their overlays
        var videos:Array = [
        {
        primary:’scene1.f4v’,
        secondary:[
        ‘scene1A.f4v’,
        ‘scene1B.f4v’
        ]
        },
        {
        primary:’scene2.f4v’,
        secondary:[
        ‘scene2A.f4v’,
        ‘scene2B.f4v’
        ]
        }
        ]

        I am not 100% how to use this with your script. You mention stage.addChild( ) and secondWindow.stage.addChild( ). Will my array work with these?

      2. I have a great script running even without crutches on OS X 10.9.5 (Mavericks). Testing without this part of the code
        if (Capabilities.os. indexOf (“Mac OS”) > -1) {
        setInterval(uglyMacHack, 1000);
        }
        Everything works! AIR SDK 25.0.

  2. Hi, sorry for my bad english.
    thanks for this demo. It works.

    is it a problem whis 3 Monitores ?

  3. I have a great script running even without crutches on OS X 10.9.5 (Mavericks). Testing without this part of the code
    if (Capabilities.os. indexOf (“Mac OS”) > -1) {
    setInterval(uglyMacHack, 1000);
    }
    Everything works! AIR SDK 25.0.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.