Protect Your AIR Applications From Phantom Monitors

If you use multiple monitors on a regular basis, you have probably been in a situation where a window gets stranded on a nonexistent virtual monitor. Sometimes the fix is to restart the application; sometimes the only way to fix it is to reconnect the monitor, retrieve the window, and drag it on to the main monitor before unplugging it again. Lame.

Don’t write AIR applications that aren’t phantom monitor-proof. If you allow secondary windows to be opened, make sure you think about what will happen when these windows end up on monitors that no longer exist.

AIR and the host operating system usually just do the right thing which is to move all the windows over to the main monitor when non-primary monitors go away. This is always true of normal windows (NativeWindowType.NORMAL). Additionally, if you don’t specify window coordinates at all when opening a new window, the operating system is smart enough to make it appear on an actual monitor rather than one that isn’t there anymore. But if you’re working with lightweight or utility windows, and if you’re controlling where they open, this is something you need to watch out for.

I ran into this issue when working on a new version of MailBrew (which I will probably release this week). I got several requests to make the summary window (a little window that shows you how many unread messages you have for each account) reopen when the application starts, and to position it wherever it was last placed. The problem is that the summary window could very easily be placed on a monitor that no longer exists.

In order to prevent falling victim to a phantom monitor, I wrote this very simple function:

private function verifyPosition():void
{
    var screens:Array = Screen.getScreensForRectangle(this.nativeWindow.bounds);
    if (screens.length == 0)
    {
        var mainScreen:Screen = Screen.mainScreen;
        var newPoint:Point = new Point(mainScreen.visibleBounds.x + 2,
                                       mainScreen.visibleBounds.y + 2);
        this.setLocation(newPoint);
    }
}

All I have to do is call verifyPosition from any code that might cause the summary window to be repositioned. If the summary window is off-screen, I simply reposition it in the top left-hand corner and let the user move it him/herself.

A more interesting solution might be to write some heuristics which tried to place the summary window in a similar location to where it was placed on the nonexistent window, but in order to get the new version of MailBrew out the door this week, I went with a simpler approach. Maybe I’ll have a go at this problem in the next version (and share the code).

Note that this does not address every circumstance when the summary window could end up on a phantom monitor. Specifically, it does not address the situation where the user places the summary window on a different monitor, then changes the monitor configuration without restarting the application or reopening the summary window from the main application widow. The reason I don’t have a more encompassing solution is that AIR currently does not have APIs to detect a change in the user’s monitor configuration, so the only bulletproof way to do this would be set a timer to constantly check to see if the summary window is on a phantom monitor. That seemed like overkill to me, so I wrote my code to do the following instead:

  • When the application is started, if the summary window will not be visible, it defaults to the top left-hand corner of the main monitor.
  • When the summary window is opened from the main application window, if its last location no longer exists (because it’s on a phantom monitor), it defaults to the top left-hand corner of the main monitor.
  • Whenever new email messages are found, if the summary window is open on a phantom monitor, it is repositioned in the top left-hand corner.

In other words, the summary window will never be irrevocably lost, and it will always fix itself eventually. In no cases will ever require the user to reconnect a monitor to retrieve it.

If your applications might be susceptible to phantom monitors, I highly encourage you to use a technique like this. In this age of powerful multi-headed graphics cards, inexpensive LCDs, and extensive multi-tasking (requiring multiple monitors for many of us), it’s only a matter of time before a user runs into this and thinks significantly less of your application.