#!/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 <Destroy> [list catch [list destroy $wf]]
    bind $FW <Property> [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 <ButtonPress-1> \
			[list MarkAndRaise Window $wf $XId %X %Y]
    bind $wf.tbwframe.title <Button1-Motion> \
			[list Move Window $wf $XId %X %Y]

    foreach outlineWin [list $wf.rb $wf.br $wf.lrc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId 1 %X 0 1 %Y 0]
    }

    foreach outlineWin [list $wf.rt $wf.tr $wf.urc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId 1 %X 0 -1 %Y 1]
    }

    foreach outlineWin [list $wf.lt $wf.tl $wf.ulc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId -1 %X 1 -1 %Y 1]
    }

    foreach outlineWin [list $wf.lb $wf.bl $wf.llc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId -1 %X 1 1 %Y 0]
    }

    bind $wf.l <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.l <Button1-Motion> \
			[list ResizeWin $wf $XId -1 %X 1 0 %Y 0]

    bind $wf.r <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.r <Button1-Motion> \
			[list ResizeWin $wf $XId 1 %X 0 0 %Y 0]

    bind $wf.t <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.t <Button1-Motion> \
			[list ResizeWin $wf $XId 0 %X 0 -1 %Y 1]

    bind $wf.b <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.b <Button1-Motion> \
			[list ResizeWin $wf $XId 0 %X 0 1 %Y 0]

    # Make sure that we clean up when the window's deleted.

    bind $wf <Destroy> [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 <Destroy> [list catch [list destroy $wf]]
    bind $FW <Property> [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 <ButtonPress-1> \
			[list MarkAndRaise Window $wf $XId %X %Y]
    bind $wf.tbwframe.title <Button1-Motion> \
			[list Move Window $wf $XId %X %Y]

    foreach outlineWin [list $wf.rb $wf.br $wf.lrc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId 1 %X 0 1 %Y 0]
    }

    foreach outlineWin [list $wf.rt $wf.tr $wf.urc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId 1 %X 0 -1 %Y 1]
    }

    foreach outlineWin [list $wf.lt $wf.tl $wf.ulc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId -1 %X 1 -1 %Y 1]
    }

    foreach outlineWin [list $wf.lb $wf.bl $wf.llc] {
	bind $outlineWin <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
	bind $outlineWin <Button1-Motion> \
			[list ResizeWin $wf $XId -1 %X 1 1 %Y 0]
    }

    bind $wf.l <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.l <Button1-Motion> \
			[list ResizeWin $wf $XId -1 %X 1 0 %Y 0]

    bind $wf.r <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.r <Button1-Motion> \
			[list ResizeWin $wf $XId 1 %X 0 0 %Y 0]

    bind $wf.t <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.t <Button1-Motion> \
			[list ResizeWin $wf $XId 0 %X 0 -1 %Y 1]

    bind $wf.b <ButtonPress-1> \
			[list MarkSpot $wf $XId %X %Y]
    bind $wf.b <Button1-Motion> \
			[list ResizeWin $wf $XId 0 %X 0 1 %Y 0]

    # Make sure that we clean up when the window's deleted.

}
    bind $wf <Destroy> [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 <ButtonPress-1> \
			[list MarkAndRaise Icon $iconWin $XId %X %Y]
    bind $iconWin.label <Button1-Motion> \
			[list Move Icon $iconWin $XId %X %Y]

    # These bindings take care of de-iconifying the window.

    bind $iconWin.icon <ButtonPress-1> [list DeiconifyWindow $XId]

    # These are for cleanup upon window deletion.

    bind $iconWin <Destroy> \
		[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 <MapRequest> "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 . <ConfigureRequest> {HandleConfigureRequest %i %w %h}
bind .rw <ConfigureRequest> {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 . <ResizeRequest> {HandleResizeRequest %i %w %h}
bind .rw <ResizeRequest> {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 . <ButtonPress-1> {PopupCmdMenu %i %x %y}
bind .rw <ButtonPress-1> {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 . <ButtonPress-3> {PopupWmMenu %i %x %y}
bind .rw <ButtonPress-3> {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" &

