#!/usr/local/bin/wish8.4 #! /home/neil/src/tcl/tkwm8.0/unix/wish -root # # Tkwm Demo Window Manager # set tkwmDir /home/neil/src/tcl/tkwm load [file join $tkwmDir rootwin0.3/libRootwin0.3.so] load [file join $tkwmDir xop0.4/libXop0.4.so] load [file join $tkwmDir foreignwin0.1/libForeignwin0.1.so] #wm geom . 1024x768 #wm geom . 1280x1024 wm withdraw . update rootwin .rw # This is a script that implements a (just barely usable) window # manager based on Tk with the window manager patches. To use this # window manager, I put the following lines in my .xsession file: # # LD_LIBRARY_PATH=/usr/local/lib # export LD_LIBRARY_PATH # TKWM=/home/neil/src/tcl/tkwm8.0/unix/wish # TKWM_SCRIPT=/home/neil/src/tcl/tkwm8.0/tkwmdemos/wmdemo.tk # exec $TKWM $TKWM_SCRIPT -root # # On your system, you will of course have to change the following: # # 1) Get the LD_LIBRARY_PATH correct; this will have to point # to the directory where the Tcl and Tk libraries reside. # # 2) Substitute the path to the tkwm-hacked wish in the "TKWM" line # # 3) Substitute the path to this script in the TKWM_SCRIPT line # # NOTE: wish (at least as of version tk8.0p2) does not handle the "-root" # option correctly. (It doesn't handle the "-sync" option correctly # either.) The "-root" option has to be LAST, i.e. AFTER the script # name, or wish will not execute the script. # # NOTE ALSO: I did this on a Red Hat Linux system (version 4.1). # Your milage may vary; I don't know what magic will be required # on other systems. Also, this window manager is rather crude, # and probably does a lot of things incorrectly. I have cobbled # this together without looking at the ICCCM, so there are lots # of things that I just guessed about, or ignored completely. # LogMessage logs an informational or error message. # (This is here mainly for debugging purposes.) proc LogMessage {txt} { global env logfp # This logs the message to a file; you could, if you # wished, pop up a window, or just ignore errors, or # make the action a configuration option. if {![info exists logfp]} { set fn [file join $env(HOME) .tkwmlog] set logfp [open $fn "a"] } puts $logfp $txt flush $logfp # close $logfp } # "ShouldManage" returns 1 if XId is the X window ID of a # window that should be managed, 0 if it should not be # managed. proc ShouldManage {XId} { # Don't try to manage the window manager's windows! set rc [catch [list xop window $XId inthisapp] inthisapp] if {$rc != 0} { #LogMessage $inthisapp return 0 } if {$inthisapp} { return 0 } # Don't manage windows that have the overrideredirct flag set; # these are usually things like popup menus that should not # have window manager decorations. set atts [xop window $XId attributes] set attsArray(overrideRedirect) 0 array set attsArray $atts if {$attsArray(overrideRedirect)} { return 0 } # OK, do it! return 1 } # "ShouldMapWindow" returns 1 if we should map window "XId", # and returns 0 if we shouldn't. proc ShouldMapWindow {XId} { array set hints [xop wmprops $XId hints] if {![info exists hints(initState)]} { return 0 } if {[string compare $hints(initState) "Normal"] != 0} { return 1 } return 0 } proc ShouldMapIcon {XId} { catch {unset attr} array set attr [xop window $XId attributes] if {![info exists attr(mapState)]} { return 0 } if {[string compare $attr(mapState) "iconic"] != 0} { return 1 } return 0 } # The array WinInfo contains all the bookkeeping info # that we need for each managed window. The array indices # are of the form "XId,wi,fieldName", where "XId" is the # X window identifier of the application's main window, # "wi" is either "Window" or "Icon", and "fieldName" is a # name that describes the information stored. Valid values # for "fieldName" are: # # Window,Path Tk path name of the application's # window manager decorations # # Icon,Path Tk path name of the application's # icon window # # Icon,Image Tk photo image used for icon # (may not be defined) # # Icon,XPos X coordinate of the application's # icon window # # Icon,YPos Y coordinate of the application's # icon window # # Window,XPos X coordinate of the application's # window decorations # # Window,YPos Y coordinate of the application's # window decorations # # Window,NWidth Window width in "normal" state # # Window,NHeight Window height in "normal" state # # Window,Max 1 if window is maximized, 0 if normal size # set WinInfo(0) "" unset WinInfo(0) # The image "iconifyImage" is an image used for the "iconify" # button in the window's decorative frame. Likewise, "maxImage" # is used for the "maximize" button. image create bitmap iconifyImage -data { #define iconify_width 16 #define iconify_height 16 static unsigned char iconify_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0xf0, 0x0f, 0x30, 0x08, 0x30, 0x08, 0x30, 0x08, 0x30, 0x08, 0x30, 0x08, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; } image create bitmap maxImage -data { #define max_width 16 #define max_height 16 static unsigned char max_bits[] = { 0x00, 0x00, 0xfe, 0x7f, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0xfe, 0x7f, 0x00, 0x00}; } # SendDelWindow sends a ClientMessage event to a window, # instructing it to delete itself. proc SendDelWindow {XId} { puts "SendDelWindow: $XId" xop event send $XId ClientMessage \ -messagetype [winfo atom "WM_PROTOCOLS"] \ -format 32 \ -data [list [winfo atom "WM_DELETE_WINDOW"] 0] } # MakeFrameAround builds a frame around window "XId". proc MakeFrameAround {XId} { global WinInfo # Make sure we don't do this twice. if {[info exists WinInfo($XId,Window,Path)]} { return } # Get information about the window. Gotta be careful about # race conditions; window may have been destroyed before we # get to this point. set NHcmd [list xop wmprops $XId normalhints] set rc [catch $NHcmd NHres] if {$rc != 0} { # Should make something up here. #puts stderr "Couldn't get WM normal hints for $XId" return } array set normalhints $NHres set Tcmd [list xop wmprops $XId name] set rc [catch $Tcmd Title] if {$rc != 0} { # Should make something up here #LogMessage "Couldn't get title for $XId" return } # Make a frame in the root window to hold the managed window. set wf ".rw.frame$XId" if {[winfo exists $wf]} { return } frame $wf set WinInfo($XId,Window,Path) $wf # Make the outer window decorations. set frameWidth 5 set frameBorder 1 set cornerSize 16 set outlineColor gray frame $wf.ulc -width $frameWidth -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_left_corner frame $wf.tl -width $cornerSize -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_left_corner frame $wf.t -width 20 -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_side frame $wf.tr -width $cornerSize -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_right_corner frame $wf.urc -width $frameWidth -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_right_corner frame $wf.lt -width $frameWidth -height $cornerSize \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_left_corner frame $wf.l -width $frameWidth -height 20 \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor left_side frame $wf.lb -width $frameWidth -height $cornerSize \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_left_corner frame $wf.rt -width $frameWidth -height $cornerSize \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor top_right_corner frame $wf.r -width $frameWidth -height 20 \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor right_side frame $wf.rb -width $frameWidth -height $cornerSize \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_right_corner frame $wf.llc -width $frameWidth -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_left_corner frame $wf.bl -width $cornerSize -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_left_corner frame $wf.b -width 20 -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_side frame $wf.br -width $cornerSize -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_right_corner frame $wf.lrc -width $frameWidth -height $frameWidth \ -background $outlineColor -relief raised \ -borderwidth $frameBorder -cursor bottom_right_corner # Make the inner frame that holds the title bar and # window controls, and the managed window. frame $wf.tbwframe # Grid everything together. grid $wf.ulc -row 0 -column 0 -sticky nsew grid $wf.tl -row 0 -column 1 -sticky ew grid $wf.t -row 0 -column 2 -sticky ew grid $wf.tr -row 0 -column 3 -sticky ew grid $wf.urc -row 0 -column 4 -sticky nsew grid $wf.lt -row 1 -column 0 -sticky ns grid $wf.l -row 2 -column 0 -sticky ns grid $wf.lb -row 3 -column 0 -sticky ns grid $wf.tbwframe -row 1 -column 1 -rowspan 3 -columnspan 3 -sticky nsew grid $wf.rt -row 1 -column 4 -sticky ns grid $wf.r -row 2 -column 4 -sticky ns grid $wf.rb -row 3 -column 4 -sticky ns grid $wf.llc -row 4 -column 0 -sticky nsew grid $wf.bl -row 4 -column 1 -sticky ew grid $wf.b -row 4 -column 2 -sticky ew grid $wf.br -row 4 -column 3 -sticky ew grid $wf.lrc -row 4 -column 4 -sticky nsew grid rowconfigure $wf 2 -weight 1 grid columnconfigure $wf 2 -weight 1 # Make the title/menu bar at the top. menubutton $wf.tbwframe.leftmenu -indicatoron yes \ -menu $wf.tbwframe.leftmenu.menu \ -relief raised grid $wf.tbwframe.leftmenu -row 0 -column 0 -sticky nsw menu $wf.tbwframe.leftmenu.menu -tearoff no $wf.tbwframe.leftmenu.menu add command \ -label "Lower" -command [list lower $wf] $wf.tbwframe.leftmenu.menu add command \ -label "Raise" -command [list raise $wf] $wf.tbwframe.leftmenu.menu add separator $wf.tbwframe.leftmenu.menu add command \ -label "Close" -command [list SendDelWindow $XId] $wf.tbwframe.leftmenu.menu add command \ -label "Kill" -command [list xop drawable $XId killclient] label $wf.tbwframe.title -text "$Title" -relief raised button $wf.tbwframe.iconbutton -image iconifyImage \ -command [list IconifyWindow $XId] button $wf.tbwframe.maxbutton -image maxImage \ -command [list MaxMinWindow $XId] grid $wf.tbwframe.title -row 0 -column 1 -sticky nsew grid $wf.tbwframe.iconbutton -row 0 -column 2 -sticky nse grid $wf.tbwframe.maxbutton -row 0 -column 3 -sticky nse grid columnconfigure $wf.tbwframe 1 -weight 1 # Get window width/height. if {[info exists normalhints(userSize)]} { set winWidth [lindex $normalhints(userSize) 0] set winHeight [lindex $normalhints(userSize) 1] } elseif {[info exists normalhints(progSize)]} { set winWidth [lindex $normalhints(progSize) 0] set winHeight [lindex $normalhints(progSize) 1] } else { set winWidth 320 set winHeight 240 if {[info exists requestedSize($XId,width)]} { set winWidth $requestedSize($XId,width) } if {[info exists requestedSize($XId,height)]} { set winHeight $requestedSize($XId,height) } } set WinInfo($XId,Window,NWidth) $winWidth set WinInfo($XId,Window,NHeight) $winHeight set fwcmd [list foreignwin $wf.tbwframe.fw $XId \ -width $winWidth -height $winHeight] set rc [catch $fwcmd FW] if {$rc != 0} { catch [list destroy $wf] return } grid $wf.tbwframe.fw -row 1 -column 0 -columnspan 4 -sticky nsew bind $FW [list catch [list destroy $wf]] bind $FW [list HandleProperty $wf %P %i] # Shouldn't need this... # update $wf.tbwframe.fw configure -width $winWidth -height $winHeight # Bind stuff so that we can move/resize/etc. bind $wf.tbwframe.title \ [list MarkAndRaise Window $wf $XId %X %Y] bind $wf.tbwframe.title \ [list Move Window $wf $XId %X %Y] foreach outlineWin [list $wf.rb $wf.br $wf.lrc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId 1 %X 0 1 %Y 0] } foreach outlineWin [list $wf.rt $wf.tr $wf.urc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId 1 %X 0 -1 %Y 1] } foreach outlineWin [list $wf.lt $wf.tl $wf.ulc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId -1 %X 1 -1 %Y 1] } foreach outlineWin [list $wf.lb $wf.bl $wf.llc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId -1 %X 1 1 %Y 0] } bind $wf.l \ [list MarkSpot $wf $XId %X %Y] bind $wf.l \ [list ResizeWin $wf $XId -1 %X 1 0 %Y 0] bind $wf.r \ [list MarkSpot $wf $XId %X %Y] bind $wf.r \ [list ResizeWin $wf $XId 1 %X 0 0 %Y 0] bind $wf.t \ [list MarkSpot $wf $XId %X %Y] bind $wf.t \ [list ResizeWin $wf $XId 0 %X 0 -1 %Y 1] bind $wf.b \ [list MarkSpot $wf $XId %X %Y] bind $wf.b \ [list ResizeWin $wf $XId 0 %X 0 1 %Y 0] # Make sure that we clean up when the window's deleted. bind $wf [list catch [list CleanupFor $XId]] } proc NewMakeFrameAround {XId} { global WinInfo # Make sure we don't do this twice. if {[info exists WinInfo($XId,Window,Path)]} { return } # Get information about the window. Gotta be careful about # race conditions; window may have been destroyed before we # get to this point. set NHcmd [list xop wmprops $XId normalhints] set rc [catch $NHcmd NHres] if {$rc != 0} { # Should make something up here. #puts stderr "Couldn't get WM normal hints for $XId" return } array set normalhints $NHres set Tcmd [list xop wmprops $XId name] set rc [catch $Tcmd Title] if {$rc != 0} { # Should make something up here #LogMessage "Couldn't get title for $XId" return } # Make a frame in the root window to hold the managed window. set wf ".rw.frame$XId" if {[winfo exists $wf]} { return } pack [frame $wf -bd 2 -bg blue] -fill both -expand y pack [frame $wf.tbwframe] -fill both -expand y set WinInfo($XId,Window,Path) $wf # Get window width/height. if {[info exists normalhints(userSize)]} { set winWidth [lindex $normalhints(userSize) 0] set winHeight [lindex $normalhints(userSize) 1] } elseif {[info exists normalhints(progSize)]} { set winWidth [lindex $normalhints(progSize) 0] set winHeight [lindex $normalhints(progSize) 1] } else { set winWidth 320 set winHeight 240 if {[info exists requestedSize($XId,width)]} { set winWidth $requestedSize($XId,width) } if {[info exists requestedSize($XId,height)]} { set winHeight $requestedSize($XId,height) } } set WinInfo($XId,Window,NWidth) $winWidth set WinInfo($XId,Window,NHeight) $winHeight set fwcmd [list foreignwin $wf.tbwframe.fw $XId \ -width $winWidth -height $winHeight] set rc [catch $fwcmd FW] if {$rc != 0} { catch [list destroy $wf] return } # grid $wf.tbwframe.fw -row 1 -column 0 -columnspan 4 -sticky nsew bind $FW [list catch [list destroy $wf]] bind $FW [list HandleProperty $wf %P %i] # Shouldn't need this... # update $wf.tbwframe.fw configure -width $winWidth -height $winHeight pack $wf.tbwframe.fw -fill both -expand y # Bind stuff so that we can move/resize/etc. if 0 { bind $wf.tbwframe.title \ [list MarkAndRaise Window $wf $XId %X %Y] bind $wf.tbwframe.title \ [list Move Window $wf $XId %X %Y] foreach outlineWin [list $wf.rb $wf.br $wf.lrc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId 1 %X 0 1 %Y 0] } foreach outlineWin [list $wf.rt $wf.tr $wf.urc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId 1 %X 0 -1 %Y 1] } foreach outlineWin [list $wf.lt $wf.tl $wf.ulc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId -1 %X 1 -1 %Y 1] } foreach outlineWin [list $wf.lb $wf.bl $wf.llc] { bind $outlineWin \ [list MarkSpot $wf $XId %X %Y] bind $outlineWin \ [list ResizeWin $wf $XId -1 %X 1 1 %Y 0] } bind $wf.l \ [list MarkSpot $wf $XId %X %Y] bind $wf.l \ [list ResizeWin $wf $XId -1 %X 1 0 %Y 0] bind $wf.r \ [list MarkSpot $wf $XId %X %Y] bind $wf.r \ [list ResizeWin $wf $XId 1 %X 0 0 %Y 0] bind $wf.t \ [list MarkSpot $wf $XId %X %Y] bind $wf.t \ [list ResizeWin $wf $XId 0 %X 0 -1 %Y 1] bind $wf.b \ [list MarkSpot $wf $XId %X %Y] bind $wf.b \ [list ResizeWin $wf $XId 0 %X 0 1 %Y 0] # Make sure that we clean up when the window's deleted. } bind $wf [list catch [list CleanupFor $XId]] } # HandleProperty handles "Property" events for windows. # We need this to do things like change the window name # printed in the title bar. proc HandleProperty {wf propName winId} { switch -exact -- $propName { {WM_NAME} { set winTitle [xop wmprops $winId name] $wf.tbwframe.title configure -text $winTitle } } } # MarkSpot marks a spot (x,y) for window w. It also records # the window's current size and position for resizing purposes. proc MarkSpot {w XId x y} { global WindowMarkPos global WindowMarkSize global WindowMarkXY set WindowMarkPos($w,X) $x set WindowMarkPos($w,Y) $y set WindowMarkSize($w,Width) [winfo width $w.tbwframe.fw] set WindowMarkSize($w,Height) [winfo height $w.tbwframe.fw] set WindowMarkXY($w,X) [winfo rootx $w] set WindowMarkXY($w,Y) [winfo rooty $w] } # ResizeWin resizes window w (corresponding to X window ID XId). # It does so by finding the change between the position (x,y) # and the previously marked position; these changes are then # multiplied by xSizeMult and ySizeMult, and added to the recorded # window size, giving the new window size. The window's position # is then offset by an amount corresponding to the changes # multiplied by xOffsetMult and yOffsetMult. proc ResizeWin {w XId xSizeMult x xOffsetMult ySizeMult y yOffsetMult} { LogMessage "Calling ResizeWin for $w, $XId" global WinInfo global WindowMarkPos global WindowMarkSize global WindowMarkXY set deltaX [expr $x - $WindowMarkPos($w,X)] set deltaY [expr $y - $WindowMarkPos($w,Y)] set NewWindowWidth [expr $WindowMarkSize($w,Width) + $xSizeMult*$deltaX] set NewWindowHeight [expr $WindowMarkSize($w,Height) + $ySizeMult*$deltaY] $w.tbwframe.fw configure -width $NewWindowWidth -height $NewWindowHeight if {!$WinInfo($XId,Window,Max)} { set WinInfo($XId,Window,NWidth) $NewWindowWidth set WinInfo($XId,Window,NHeight) $NewWindowHeight } set NewWindowX [expr $WindowMarkXY($w,X) + $xOffsetMult*$deltaX] set NewWindowY [expr $WindowMarkXY($w,Y) + $yOffsetMult*$deltaY] place $w -x $NewWindowX -y $NewWindowY } # CleanupFor cleans up all the records associated with # window "XId". proc CleanupFor {XId} { global WinInfo if {[info exists WinInfo($XId,Window,Path)]} { catch [list destroy $WinInfo($XId,Window,Path)] } if {[info exists WinInfo($XId,Icon,Path)]} { catch [list destroy $WinInfo($XId,Icon,Path)] } if {[info exists WinInfo($XId,Icon,Image)]} { catch [list rename $WinInfo($XId,Icon,Image) ""] } foreach idx [array names WinInfo "$XId,*"] { unset WinInfo($idx) } } proc MarkAndRaise {windowOrIcon w XId x y} { global WindowMarkPos raise $w set WindowMarkPos($w,x) $x set WindowMarkPos($w,y) $y } proc Move {windowOrIcon w XId x y} { global WindowMarkPos global WinInfo set offsetX [expr $x - $WindowMarkPos($w,x) \ + $WinInfo($XId,$windowOrIcon,XPos)] set offsetY [expr $y - $WindowMarkPos($w,y) \ + $WinInfo($XId,$windowOrIcon,YPos)] place $w -x $offsetX -y $offsetY set WinInfo($XId,$windowOrIcon,XPos) $offsetX set WinInfo($XId,$windowOrIcon,YPos) $offsetY set WindowMarkPos($w,x) $x set WindowMarkPos($w,y) $y } # MakeIconFor makes an icon window for X window "XId". proc MakeIconFor {XId} { global WinInfo global requestedSize # Make the icon frame. set iconWin .rw.icon$XId set WinInfo($XId,Icon,Path) $iconWin frame $iconWin # Make the image for the icon window. array set hints [xop wmprops $XId hints] if {[info exists hints(iconWindowId)]} { set iconXId $hints(iconWindowId) foreignwin $iconWin.icon $iconXId if {[info exists requestedSize($iconXId,width)]} { $iconWin.icon configure \ -width $requestedSize($iconXId,width) \ -height $requestedSize($iconXId,height) } } elseif {[info exists hints(iconPixmapId)]} { set WinInfo($XId,Icon,Image) [image create photo] xop drawable $hints(iconPixmapId) getimage $WinInfo($XId,Icon,Image) label $iconWin.icon -image $WinInfo($XId,Icon,Image) } else { # No icon specified; gotta roll our own. # For now, we'll just do something stupid. canvas $iconWin.icon -width 100 -height 100 $iconWin.icon create rectangle 10 10 90 90 -fill red } # Make the icon label. set iconName [xop wmprops $XId iconname] if {[string compare $iconName ""] == 0} { set iconName [xop wmprops $XId name] } label $iconWin.label -text $iconName # Put 'em all together. grid $iconWin.icon -row 0 -column 0 grid $iconWin.label -row 1 -column 0 -sticky ew # These bindings make the icon draggable. bind $iconWin.label \ [list MarkAndRaise Icon $iconWin $XId %X %Y] bind $iconWin.label \ [list Move Icon $iconWin $XId %X %Y] # These bindings take care of de-iconifying the window. bind $iconWin.icon [list DeiconifyWindow $XId] # These are for cleanup upon window deletion. bind $iconWin \ [list catch [list CleanupFor $XId]] } # IconifyWindow iconifies window "XId". proc IconifyWindow {XId} { global WinInfo # Unmap the window, and place the icon where it's supposed to go. if {[info exists WinInfo($XId,Window,Path)]} { catch [list place forget $WinInfo($XId,Window,Path)] } place $WinInfo($XId,Icon,Path) \ -x $WinInfo($XId,Icon,XPos) -y $WinInfo($XId,Icon,YPos) } # DeiconifyWindow deiconifies window "XId". proc DeiconifyWindow {XId} { global WinInfo # Unmap the icon, and place the window where it's supposed to go. catch [list place forget $WinInfo($XId,Icon,Path)] if {$WinInfo($XId,Window,Max)} { place $WinInfo($XId,Window,Path) -x 0 -y 0 } else { place $WinInfo($XId,Window,Path) \ -x $WinInfo($XId,Window,XPos) -y $WinInfo($XId,Window,YPos) } } # Toggle the "maximized" state of the window. proc MaxMinWindow {XId} { global WinInfo set w $WinInfo($XId,Window,Path) if {$WinInfo($XId,Window,Max)} { # Restore original window size. $w.tbwframe.fw configure \ -width $WinInfo($XId,Window,NWidth) \ -height $WinInfo($XId,Window,NHeight) place $w -x $WinInfo($XId,Window,XPos) -y $WinInfo($XId,Window,YPos) set WinInfo($XId,Window,Max) 0 } else { # Maximize window. First, figure out how big it should be. set widthDiff [expr [winfo width $w] - [winfo width $w.tbwframe.fw]] set heightDiff [expr [winfo height $w] - [winfo height $w.tbwframe.fw]] set maxWidth [expr [winfo width .] - $widthDiff] set maxHeight [expr [winfo height .] - $heightDiff] # Now resize the window, and put it in the upper left corner. $w.tbwframe.fw configure \ -width $maxWidth -height $maxHeight place $w -x 0 -y 0 set WinInfo($XId,Window,Max) 1 } } # HandleNewWindow does whatever needs to be done for # a newly-created window. proc HandleNewWindow {XId} { puts stderr "Handling new window $XId" global WinInfo # Don't do anything for windows we're not supposed to manage. if {![ShouldManage $XId]} { return } # OK, we're supposed to manage this window; start by making # a window-manager frame around it and making an icon for it. set WinInfo($XId,Window,Max) 0 MakeFrameAround $XId MakeIconFor $XId # Get all the "hints" we need. array set normalhints [xop wmprops $XId normalhints] array set hints [xop wmprops $XId hints] # Figure out what the window's position and its icon's # position should be. if {[info exists normalhints(userPosition)]} { set windowPosition $normalhints(userPosition) } elseif {[info exists normalhints(progPosition)]} { set windowPosition $normalhints(progPosition) } else { set windowPosition {100 100} } set WinInfo($XId,Window,XPos) [lindex $windowPosition 0] set WinInfo($XId,Window,YPos) [lindex $windowPosition 1] if {[info exists hints(iconPosition)]} { set iconPosition $hints(iconPosition) } else { set iconPosition {100 100} } set WinInfo($XId,Icon,XPos) [lindex $iconPosition 0] set WinInfo($XId,Icon,YPos) [lindex $iconPosition 1] # Determine what to map: the window or the icon, or neither. # NOTE: for now, we will consider the "Zoomed" state to be # the same as "Normal". if {![info exists hints(initState)]} { set hints(initState) Normal } switch -exact -- $hints(initState) { {Zoomed} - {DontCare} - {Normal} { DeiconifyWindow $XId } {Iconic} { IconifyWindow $XId } {Inactive} - {default} { # Do nothing. } } } ######################################################### # # # Here's the guts of the window manager. First, we make # # a frame around each existing window. Then we handle # # MapRequest events, so that we can put a frame around # # each newly-created window. # # # ######################################################### foreach winID [xop window [winfo id .rw] children] { HandleNewWindow $winID } #@@@ Why doesn't this always work right without the "after"? bind .rw "after 100 [list HandleNewWindow %i]" proc DoXopResize {XId w h} { #xop window $XId resize $w $h LogMessage " Called \" xop window $XId resize $w $h\"" } # If we get a ConfigureRequest event, just record the window # size for later use. proc HandleConfigureRequest {XId w h} { global requestedSize set requestedSize($XId,width) $w set requestedSize($XId,height) $h LogMessage "HandleConfigureRequest $XId $w $h" after 500 [list DoXopResize $XId $w $h] } #bind . {HandleConfigureRequest %i %w %h} bind .rw {HandleConfigureRequest %i %w %h} proc HandleResizeRequest {XId w h} { global requestedSize set requestedSize($XId,width) $w set requestedSize($XId,height) $h LogMessage "HandleResizeRequest $XId $w $h" return set errCode [catch [list xop window $XId resize $w $h] msg] if {$errCode != 0} { LogMessage "Resize error: $msg" } } #bind . {HandleResizeRequest %i %w %h} bind .rw {HandleResizeRequest %i %w %h} # This pops up the command menu. Notice that we only do this if # the window "winID" is the root window. proc PopupCmdMenu {winID x y} { if {$winID != [winfo id .rw]} { return } tk_popup .rw.cmdmenu $x $y } # This pops up the window manager control menu. proc PopupWmMenu {winID x y} { if {$winID != [winfo id .rw]} { return } tk_popup .rw.wmmenu $x $y } # This is a proc that executes a Unix command. proc ExecUnixCmd {str} { eval [concat exec $str &] } # This is a proc that sets the root window's background color. proc SetRootBackground {} { set initColor [. cget -background] if {[string compare $initColor ""] == 0} { set initColor gray } set color [tk_chooseColor -initialcolor $initColor] if {[string compare $color ""] != 0} { . configure -background $color } } # Make the menu hierarchy. menu .rw.cmdmenu -tearoff no .rw.cmdmenu add command -label "xterm" -command {ExecUnixCmd "xterm"} .rw.cmdmenu add command -label "xeyes" -command {ExecUnixCmd "xeyes"} .rw.cmdmenu add command -label "backgammon" -command {ExecUnixCmd "xgammon"} .rw.cmdmenu add command -label "OSView" -command {ExecUnixCmd "xosview"} .rw.cmdmenu add command -label "Clock" -command {ExecUnixCmd "xclock"} .rw.cmdmenu add command -label "XFig" -command {ExecUnixCmd "xfig"} .rw.cmdmenu add command -label "Netscape" -command {ExecUnixCmd "netscape"} #bind . {PopupCmdMenu %i %x %y} bind .rw {PopupCmdMenu %i %x %y} menu .rw.wmmenu -tearoff no .rw.wmmenu add command -label "Background color" -command SetRootBackground .rw.wmmenu add separator .rw.wmmenu add command -label "Exit" -command "exit" #bind . {PopupWmMenu %i %x %y} bind .rw {PopupWmMenu %i %x %y} # Set the cursor and background to something I like... .rw configure -cursor arrow .rw configure -background "#00b2b2" # Start up the user's initial applications. #exec xterm -geometry "+1+1" & exec xterm & #exec xosview -geometry "-250+20" & #exec xclock -geometry "180x180-50+20" &