Updated 2011-07-31 15:44:57 by RLE

The MacOS X Aqua environment allows you to specify dual displays (possibly more) in an arbitrary arrangement - for example, side-by-side with the origin on one monitor offset from that of the other.

This presents a potential problem if a Tk program saves and restores window geometry - the user may disconnect a monitor or the saved geometry might apply to a different computer than the current one.

The following code uses the MacOS X /usr/sbin/system_profiler command to detect the number of monitors, and whether they are mirrored or not, and reads the system preferences (i.e. the com.apple.windowserver domain) to find the arrangement for each attached monitor.

The check_geom_aqua procedure is passed a window geometry (as returned by the "wm geometry" command, and checks that returns at least part of the top of the window is visible on one of the displays (thus allowing a window to be moved using the window manager controls). If none of the window is visible, the geometry is adjusted so that the window is centered on the primary display.

Tested on OSX 10.4.8 - YMMV

I'd appreciate knowing if anyone has a simpler way of achieving this functionality stevel - Jan 07
 proc check_geom {win geom {yoffset 0}} {
     # yoffset can be used to allow for a menubar (as in MacOS X Aqua)
     # or space at the bottom for window manager controls
     scan $geom "%ix%i+%i+%i" w h x y
     set sw [winfo screenwidth $win]
     set sh [winfo screenheight $win]
     if {$w + $x > $sw} {
         set x [expr {($sw - $w)/2}]
         set geom ""
     }
     if {$h + $y > $sh} {
         set y [expr {($sh - $y)/2}]
         set geom ""
     }        
     if {$y < $yoffset} {
         set y $yoffset
         set geom ""
     }
     if {$h + $yoffset*2 > $sh} {
         set h [expr {$sh - $yoffset*2}]
         set geom ""
     }
     if {$geom eq ""} {
         set geom =${w}x${h}+${x}+$y
     }
     return $geom
 }

 proc check_geom_aqua {win geom} {
     scan $geom "%ix%i+%i+%i" w h x y
     set cmd /usr/sbin/system_profiler
     if {[auto_execok $cmd] ne ""} {
         set fd [open "| $cmd SPDisplaysDataType"]
         set displaynum 0
         foreach line [split [read $fd] \n] {
             set fields [split [string trim $line] :]
             set arg [string trim [lindex $fields 1]]
             switch -- [lindex $fields 0] {
                 Resolution { incr displaynum }
                 Mirror     { set mirror [expr {$arg ne "Off"}] }
             }   
         }   
         close $fd
         if {$displaynum == 1 || $mirror} {
             # if a single display or mirrored we can trust Tk to
             # tell us the screen size
             return [check_geom $win $geom 20]
         }
         set prefs /Library/Preferences/com.apple.windowserver
         if {![catch {
             set fd [open "| defaults read $prefs DisplaySets"]
         }]} {
             set lines [read $fd]
             set num -1
             foreach line [split [lindex [split $lines "()"] 2] \n] {
                 set fields [split [string trim $line " ;"] =]
                 set var [string trim [lindex $fields 0]]
                 set val [string trim [lindex $fields 1]]
                 switch -- $var {
                     Active { incr num }
                     OriginX -
                     OriginY -
                     Height { set $var $val }
                     Width   {
                         if {$num == 0} {
                             # allow for menubar
                             incr OriginY 20
                         }
                         # note the window must have at least 20 pixels
                         # overlapping with the display (enough to move
                         # via the window manager)
                         lappend displays $num $OriginX $OriginY \
                                         [expr {$OriginX + $val - 20}] \
                                         [expr {$OriginY + $Height - 1}]
                     }
                 }
             }
             close $fd
             # look for top left on a display
             set found ""
             foreach {num x1 y1 x2 y2} $displays {
                 if {$x >= $x1 && $x < $x2 && $y >= $y1 && $y < $y2} {
                     set found $num
                     break
                 }
             }
             if {$found eq ""} {
                 # look for top right on a display
                 set ex [expr {$x + $w - 1}]
                 foreach {num x1 y1 x2 y2} $displays {
                 if {$ex >= $x1 && $ex < $x2 && $y >= $y1 && $y < $y2} {
                         set found $num
                         break
                     }
                 }
             }
             if {$found eq ""} {
                 # not on any display, so we center on primary display
                 set geom [check_geom $win $geom 20]
             }
         }
     } 
     return $geom
 }

 if 0 {

     proc check {} {
         set geom [check_aqua_geom . [winfo geom .]]
         puts "geom = $geom"
         wm geom . $geom
     }

     package require Tk

     if 1 {
         # play with geom interactively
         bind . <Configure> [list check]
     } else {
         # specify geom well outside most people's displays
         wm geom . =200x2000+4000+4000
         puts "waiting"
         after 3000 check
     }
 }