Exemple #1
0
// drag around windows with the mouse.
func MakeDraggable(X *xgbutil.XUtil, win xproto.Window) {
	// utility window for movement
	xwin := xwindow.New(X, win)

	// state
	var offsetX, offsetY int
	var lastX, lastY int

	// saves initial click location
	startDrag := func(X *xgbutil.XUtil, rootX, rootY, eventX, eventY int) (bool, xproto.Cursor) {
		offsetX = eventX
		offsetY = eventY
		lastX = rootX
		lastY = rootY

		// apparently the cursor is just ID 0
		return true, 0
	}
	// moves the window
	stepDrag := func(X *xgbutil.XUtil, rootX, rootY, eventX, eventY int) {
		// maintain mouse position within window
		toX := rootX - offsetX
		toY := rootY - offsetY

		// move window
		xwin.Move(toX, toY)
	}
	stopDrag := func(X *xgbutil.XUtil, rx, ry, ex, ey int) {}

	// actually bind handler to window
	mousebind.Drag(X, win, win, "1", true, startDrag, stepDrag, stopDrag)
	log.Printf("MakeDraggable: activated window %v\n", xwin)
}
Exemple #2
0
// setupEventHandlers attaches the canvas' channels to the window and
// sets the appropriate callbacks to some events:
// ConfigureNotify events will cause the window to update its state of geometry.
// Expose events will cause the window to repaint the current image.
// Button events to allow panning.
// Key events to perform various tasks when certain keys are pressed.
func (w *Window) setupEventHandlers(chans chans) {
	w.Listen(xproto.EventMaskStructureNotify | xproto.EventMaskExposure |
		xproto.EventMaskButtonPress | xproto.EventMaskButtonRelease | xproto.EventMaskKeyPress)

	// Get the current geometry in case we don't get a ConfigureNotify event
	// (or have already missed it).
	if _, err := w.Geometry(); err != nil {
		errLg.Fatal(err)
	}

	// Keep a state of window geometry.
	xevent.ConfigureNotifyFun(
		func(X *xgbutil.XUtil, ev xevent.ConfigureNotifyEvent) {
			w.Geom.WidthSet(int(ev.Width))
			w.Geom.HeightSet(int(ev.Height))
		}).Connect(w.X, w.Id)

	// Repaint the window on expose events.
	xevent.ExposeFun(
		func(X *xgbutil.XUtil, ev xevent.ExposeEvent) {
			chans.ctl <- []string{"pan", "origin"}
		}).Connect(w.X, w.Id)

	// Setup a drag handler to allow panning.
	mousebind.Drag(w.X, w.Id, w.Id, "1", false,
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) (bool, xproto.Cursor) {
			chans.panStartChan <- image.Point{ex, ey}
			return true, 0
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			chans.panStepChan <- image.Point{ex, ey}
		},
		// We do nothing on mouse release
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) { return })

	for _, kb := range keybinds {
		k := kb // Needed because the callback closure will capture kb
		err := keybind.KeyPressFun(
			func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) {
				chans.ctl <- k.command
			}).Connect(w.X, w.Id, k.key, false)
		if err != nil {
			errLg.Println(err)
		}
	}
}
Exemple #3
0
// setupMoveDrag does the boiler plate for registering this client's
// "move" drag.
func setupMoveDrag(c Client, dragWin xproto.Window,
	buttonStr string, grab bool) {

	dStart := xgbutil.MouseDragBeginFun(
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) (bool, xproto.Cursor) {
			return c.DragMoveBegin(rx, ry, ex, ey), cursors.Fleur
		})
	dStep := xgbutil.MouseDragFun(
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			c.DragMoveStep(rx, ry, ex, ey)
		})
	dEnd := xgbutil.MouseDragFun(
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			c.DragMoveEnd(rx, ry, ex, ey)
		})
	mousebind.Drag(X, X.Dummy(), dragWin, buttonStr, grab, dStart, dStep, dEnd)
}
Exemple #4
0
// setupResizeDrag does the boiler plate for registering this client's
// "resize" drag.
func setupResizeDrag(c Client, dragWin xproto.Window,
	buttonStr string, grab bool, direction uint32) {

	dStart := xgbutil.MouseDragBeginFun(
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) (bool, xproto.Cursor) {
			return c.DragResizeBegin(direction, rx, ry, ex, ey)
		})
	dStep := xgbutil.MouseDragFun(
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			c.DragResizeStep(rx, ry, ex, ey)
		})
	dEnd := xgbutil.MouseDragFun(
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			c.DragResizeEnd(rx, ry, ex, ey)
		})
	mousebind.Drag(X, X.Dummy(), dragWin, buttonStr, grab, dStart, dStep, dEnd)
}
Exemple #5
0
// setupEventHandlers attaches the canvas' channels to the window and
// sets the appropriate callbacks to some events:
// ConfigureNotify events will cause the window to update its state of geometry.
// Expose events will cause the window to repaint the current image.
// Button events to allow panning.
// Key events to perform various tasks when certain keys are pressed. Should
// these be configurable? Meh.
func (w *window) setupEventHandlers(chans chans) {
	w.chans = chans
	w.Listen(xproto.EventMaskStructureNotify | xproto.EventMaskExposure |
		xproto.EventMaskButtonPress | xproto.EventMaskButtonRelease |
		xproto.EventMaskKeyPress)

	// Get the current geometry in case we don't get a ConfigureNotify event
	// (or have already missed it).
	_, err := w.Geometry()
	if err != nil {
		errLg.Fatal(err)
	}

	// And ask the canvas to draw the first image when it gets around to it.
	go func() {
		w.chans.drawChan <- func(origin image.Point) image.Point {
			return image.Point{}
		}
	}()

	// Keep a state of window geometry.
	xevent.ConfigureNotifyFun(
		func(X *xgbutil.XUtil, ev xevent.ConfigureNotifyEvent) {
			w.Geom.WidthSet(int(ev.Width))
			w.Geom.HeightSet(int(ev.Height))
		}).Connect(w.X, w.Id)

	// Repaint the window on expose events.
	xevent.ExposeFun(
		func(X *xgbutil.XUtil, ev xevent.ExposeEvent) {
			w.chans.drawChan <- func(origin image.Point) image.Point {
				return origin
			}
		}).Connect(w.X, w.Id)

	// Setup a drag handler to allow panning.
	mousebind.Drag(w.X, w.Id, w.Id, "1", false,
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) (bool, xproto.Cursor) {
			w.chans.panStartChan <- image.Point{ex, ey}
			return true, 0
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			w.chans.panStepChan <- image.Point{ex, ey}
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			w.chans.panEndChan <- image.Point{ex, ey}
		})

	// Set up a map of keybindings to avoid a lot of boiler plate.
	// for keystring, fun := range kbs {
	for _, keyb := range keybinds {
		keyb := keyb
		err := keybind.KeyPressFun(
			func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) {
				keyb.action(w)
			}).Connect(w.X, w.Id, keyb.key, false)
		if err != nil {
			errLg.Println(err)
		}
	}
}
Exemple #6
0
func main() {
	X, err := xgbutil.NewConn()
	fatal(err)

	// Whenever the mousebind package is used, you must call Initialize.
	// Similarly for the keybind package.
	keybind.Initialize(X)
	mousebind.Initialize(X)

	// Easter egg! Use a right click to draw a gopher.
	gopherPng, _, err := image.Decode(bytes.NewBuffer(gopher.GopherPng()))
	fatal(err)

	// Now scale it to a reasonable size.
	gopher := xgraphics.Scale(gopherPng, gopherWidth, gopherHeight)

	// Create a new xgraphics.Image. It automatically creates an X pixmap for
	// you, and handles drawing to windows in the XDraw, XPaint and
	// XSurfaceSet functions.
	// N.B. An error is possible since X pixmap allocation can fail.
	canvas := xgraphics.New(X, image.Rect(0, 0, width, height))

	// Color in the background color.
	canvas.For(func(x, y int) xgraphics.BGRA {
		return bg
	})

	// Use the convenience function XShowExtra to create and map the
	// canvas window.
	// XShowExtra will also set the surface window of canvas for us.
	// We also use XShowExtra to set the name of the window and to quit the
	// main event loop when the window is closed.
	win := canvas.XShowExtra("Pointer painting", true)

	// Listen for pointer motion events and key press events.
	win.Listen(xproto.EventMaskButtonPress | xproto.EventMaskButtonRelease |
		xproto.EventMaskKeyPress)

	// The mousebind drag function runs three callbacks: one when the drag is
	// first started, another at each "step" in the drag, and a final one when
	// the drag is done.
	// The button sequence (in this case '1') is pressed, the first callback
	// is executed. If the first return value is true, the drag continues
	// and a pointer grab is initiated with the cursor id specified in the
	// second return value (use 0 to keep the cursor unchanged).
	// If it's false, the drag stops.
	// Note that Drag will automatically compress MotionNotify events.
	mousebind.Drag(X, win.Id, win.Id, "1", false,
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) (bool, xproto.Cursor) {
			drawPencil(canvas, win, ex, ey)
			return true, 0
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			drawPencil(canvas, win, ex, ey)
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {})

	mousebind.Drag(X, win.Id, win.Id, "3", false,
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) (bool, xproto.Cursor) {
			drawGopher(canvas, gopher, win, ex, ey)
			return true, 0
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
			drawGopher(canvas, gopher, win, ex, ey)
		},
		func(X *xgbutil.XUtil, rx, ry, ex, ey int) {})

	// Bind to the clear key specified, and just redraw the bg color.
	keybind.KeyPressFun(
		func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) {
			clearCanvas(canvas, win)
		}).Connect(X, win.Id, clearKey, false)

	// Bind a callback to each key specified in the 'pencils' color map.
	// The response is to simply switch the pencil color.
	for key, clr := range pencils {
		c := clr
		keybind.KeyPressFun(
			func(X *xgbutil.XUtil, ev xevent.KeyPressEvent) {
				log.Printf("Changing pencil color to: %#v", c)
				pencil = c
			}).Connect(X, win.Id, key, false)
	}

	// Output some basic directions.
	fmt.Println("Use the left or right buttons on your mouse to paint " +
		"squares and gophers.")
	fmt.Println("Pressing numbers 1, 2, 3, 4, 5 or 6 will switch your pencil " +
		"color.")
	fmt.Println("Pressing 'c' will clear the canvas.")

	xevent.Main(X)
}
Exemple #7
0
func main() {

	// I don't want to retype all of these things
	// TODO: find/replace fatal with util.Fatal
	fatal := util.Fatal

	// establish X connection
	X, err := xgbutil.NewConn()
	fatal(err)

	// initiate extension tools
	shape.Init(X.Conn())
	mousebind.Initialize(X)

	// Detail our current window manager. Insures a minimum of EWMH compliance
	wm_name, err := ewmh.GetEwmhWM(X)
	fatal(err)
	log.Printf("Window manager: %s\n", wm_name)

	// create the cross UI
	cross_ui := makeCross(X)
	cross := cross_ui.Window

	// map the icons on the cross the the actions they should perform
	// when objects are dropped over them
	win_to_action := make(map[xproto.Window]wm.WindowInteraction)
	for name, icon := range cross_ui.Icons {
		if action, ok := wm.Actions[name]; ok {
			win_to_action[icon.Window.Id] = action
		} else {
			// otherwise,
			// shade the icon because it has no action attatched to it
			icon.SetState(ui.StateDisabled)
		}
	}

	// define handlers for the three parts of any drag-drop operation
	dm := util.DragManager{}
	handleDragStart := func(X *xgbutil.XUtil, rx, ry, ex, ey int) (cont bool, cursor xproto.Cursor) {
		// find the window we are trying to drag
		win, err := wm.FindManagedWindowUnderMouse(X)
		if err != nil {
			// don't continue the drag
			log.Printf("DragStart: could not get incoming window: %v\n", err)
			return false, 0
		}

		// cool awesome!
		dm.StartDrag(win)
		// continue the drag
		return true, 0
	}

	handleDragStep := func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
		// see if we have a window that ISN'T the incoming window
		win, err := wm.FindManagedWindowUnderMouse(X)
		if err != nil {
			// whatever
			log.Printf("DragStep: no window found or something: %v\n", err)
			return
		}

		// oh we have a window? and it isn't the start window!? And not the current target!?
		if win != dm.Incoming && win != dm.Target {
			// reposition the cross over it
			// TODO: actually do this, center operates on rects, and all I have is this xproto.Window
			dm.SetTarget(win)

			// get the target width/height
			target_geom, err := xwindow.New(X, win).Geometry()
			if err != nil {
				log.Printf("DragStep: issues getting target geometry: %v\n", err)
				return
			}

			// set the target goemetry X, Y to the actual x, y relative to the root window
			tx, ty, err := wm.TranslateCoordinatesSync(X, win, X.RootWin(), 0, 0)
			if err != nil {
				log.Printf("DragStep: issue translating target coordinates to root coordinates: %v\n", err)
				return
			}
			target_geom.XSet(tx)
			target_geom.YSet(ty)
			x, y := util.CenterOver(cross.Geom, target_geom)
			cross.Move(x, y)
			cross.Map()
		}
	}

	handleDragEnd := func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
		exit_early := false
		// get icon we are dropping over
		icon_win, _, err := wm.FindNextUnderMouse(X, cross.Id)
		if err != nil {
			log.Printf("DragEnd: icon not found: %v\n", err)
			exit_early = true
		}

		incoming, target, err := dm.EndDrag()
		// drag manager produces errors if we don't have both an Incoming and a Target yet
		if err != nil {
			log.Printf("DragEnd: drag manager state error: %v\n", err)
			exit_early = true
		}

		// we tried: hide UI
		cross.Unmap()

		// we had some sort of error, escape!
		if exit_early {
			return
		}

		// retrieve the action that this icon indicates
		if action, ok := win_to_action[icon_win]; ok {
			// create util-window objects from our window IDs
			if incoming_id, inc_ok := incoming.(xproto.Window); inc_ok {
				inc_win := xwindow.New(X, incoming_id)
				if target_id, t_ok := target.(xproto.Window); t_ok {
					t_win := xwindow.New(X, target_id)

					// perform the action!
					action(t_win, inc_win)

				} else {
					log.Println("DragEnd: target type error (was %v)\n", target)
				}
			} else {
				log.Println("DragEnd: incoming type error (was %v)\n", incoming)
			}
		} else {
			log.Printf("DragEnd: couldn't map window %v to an action", icon_win)
		}
	}

	mousebind.Drag(X, X.RootWin(), X.RootWin(), KeyComboMove, true,
		handleDragStart,
		handleDragStep,
		handleDragEnd)

	///////////////////////////////////////////////////////////////////////////
	// Window resizing behavior spike
	ManageResizingWindows(X)

	// start event loop, even though we have no events
	// to keep app from just closing
	xevent.Main(X)
}
Exemple #8
0
func ManageResizingWindows(X *xgbutil.XUtil) {

	var DRAG_DATA *ResizeDrag

	handleDragStart := func(X *xgbutil.XUtil, rx, ry, ex, ey int) (cont bool, cursor xproto.Cursor) {
		// get the clicked window
		win, err := wm.FindManagedWindowUnderMouse(X)
		if err != nil {
			log.Printf("ResizeStart: couldn't find window under mouse: %v\n", err)
			return false, 0
		}
		// get coordinates inside the clicked window
		_, reply, err := wm.FindNextUnderMouse(X, win)
		if err != nil {
			log.Printf("ResizeStart: couldn't get coordinates of click inside win %v: %v\n", win, err)
			return false, 0
		}

		// create an xwindow.Window so we can get a rectangle to find our bearings from
		xwin := xwindow.New(X, win)
		geom, err := xwin.DecorGeometry()
		if err != nil {
			log.Printf("ResizeStart: geometry error: %v\n", err)
			return false, 0
		}

		// get what side of the rect our mouseclick was on
		x, y := int(reply.WinX), int(reply.WinY)
		dir := SideOfRectangle(geom, x, y)

		// get coordinate part for the edge. this is either X or Y.
		target_edge := EdgePos(geom, dir)

		log.Printf("ResizeStart: on window %v - %v. Direction/edge: %v/%v\n", win, geom, dir, target_edge)

		// find adjacent windows
		adjacent := list.New()

		// note that this is an intellegent request: the WM only gives us a list of visible, normal windows
		// we don't have to worry about moving hidden windows or something
		managed_windows, err := ewmh.ClientListGet(X)
		if err != nil {
			// we can safley ignore this error, because then we just fall back to resizing only this window
			log.Printf("ResizeStart: error getting EWMH client list: %v\n", err)
		} else {
			// select managed windows
			// always enough space
			// TODO: don't grossly overallocate
			for _, candidate_id := range managed_windows {
				// no need to run calculations for ourself!
				if candidate_id == win {
					continue
				}

				cand_window := xwindow.New(X, candidate_id)
				cand_geom, err := cand_window.DecorGeometry()
				if err != nil {
					log.Printf("ResizeStart: couldn't get geometry for ajacency candidate %v: %v\n", candidate_id, err)
					continue
				}

				cand_edge := EdgePos(cand_geom, dir.Opposite())
				if abs(cand_edge-target_edge) <= AdjacencyEpsilon {
					// cool, edges are touching.
					// make sure this window isn't totally above or below the candidate
					// we do so by constructing a rect using the clicked window's edge
					// and the candidate's orthagonal dimension
					// if the rect overlaps, then this window is truly adjacent
					//
					// TODO: consider adding a mimumum overlap
					if dir == wm.Top || dir == wm.Bottom {
						// measuring X coords
						if EdgePos(cand_geom, wm.Right) < EdgePos(geom, wm.Left) {
							continue
						}
						if EdgePos(cand_geom, wm.Left) > EdgePos(geom, wm.Right) {
							continue
						}
					} else {
						if EdgePos(cand_geom, wm.Bottom) < EdgePos(geom, wm.Top) {
							continue
						}
						if EdgePos(cand_geom, wm.Top) > EdgePos(geom, wm.Bottom) {
							continue
						}
					}
					// if a window has made it to here, it is adgacent!
					// add it to the list
					log.Printf("ResizeStart: will resize adjacent window: %v - %v\n", candidate_id, cand_geom)
					adjacent.PushBack(cand_window)
				}
			}
		}

		// construct the drag data
		data := ResizeDrag{xwin, dir, adjacent, rx, ry}

		DRAG_DATA = &data

		// TODO: finish this
		// create an edge
		// find the adjacent windows
		// start the drag
		return true, 0
	}

	handleResize := func(rx, ry int) {
		delta := rx - DRAG_DATA.LastX
		if DRAG_DATA.Direction == wm.Top || DRAG_DATA.Direction == wm.Bottom {
			delta = ry - DRAG_DATA.LastY
		}

		if DRAG_DATA.Direction == wm.Left || DRAG_DATA.Direction == wm.Top {
			delta = delta * -1
		}

		target_geom, err := DRAG_DATA.Window.DecorGeometry()
		if err != nil {
			log.Printf("Geom retrieve err: %v\n", err)
			return
		}
		target_edge := EdgePos(target_geom, DRAG_DATA.Direction)

		// resize the target by the delta
		err = ResizeDirection(X, DRAG_DATA.Window, DRAG_DATA.Direction, delta)
		if err != nil {
			log.Printf("ResizeStep: can't resize target: %v\n", err)
			return
		}

		// calculate actual delta that occured, for resizing the adjacent windows
		// handles issues with window sizing hints on windows like terminals
		// making big differences for us
		target_geom_a, err := DRAG_DATA.Window.DecorGeometry()
		if err != nil {
			log.Printf("ResizeStep: Geom retrieve err: %v\n", err)
			return
		}
		target_edge_a := EdgePos(target_geom_a, DRAG_DATA.Direction)
		delta = target_edge_a - target_edge
		if DRAG_DATA.Direction == wm.Left || DRAG_DATA.Direction == wm.Top {
			delta = delta * -1
		}

		// resize each adjacent window by the opposite
		for e := DRAG_DATA.Adjacent.Front(); e != nil; e = e.Next() {
			// extract window from the linked list
			adj_win := e.Value.(*xwindow.Window)
			adj_geom, err := adj_win.DecorGeometry()
			if err != nil {
				log.Printf("ResizeStep: can't query adjacent window %v geometry: %v", adj_win, err)
			}

			log.Printf("ResizeStep: resizing adjacent window %v - %v: edge/delta %v/%v\n", adj_win.Id, adj_geom, DRAG_DATA.Direction.Opposite(), -delta)
			// resize in the opposite direction, with the opposite delta
			// except the delta should be some actual delta calculated from our source window,
			// because issues with terminal windows happen
			err = ResizeDirection(X, adj_win, DRAG_DATA.Direction.Opposite(), -delta)
			// then to garuntee the edges touch...
			AdjoinEdge(DRAG_DATA.Window, adj_win, DRAG_DATA.Direction)

			if err != nil {
				log.Printf("ResizeStep: can't resize adjacent window %v: %v\n", adj_win, err)
				continue
			}
		}

		// save new coordinates
		DRAG_DATA.LastX = rx
		DRAG_DATA.LastY = ry
	}

	handleDragStep := func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
		if DynamicDragResize {
			handleResize(rx, ry)
		}
	}

	handleDragEnd := func(X *xgbutil.XUtil, rx, ry, ex, ey int) {
		// only run on high enough deltas. Prevents windows from resizing when the user has gone "nah."
		// use the adjacency epsilon here too
		delta := abs(rx - DRAG_DATA.LastX)
		if DRAG_DATA.Direction == wm.Top || DRAG_DATA.Direction == wm.Bottom {
			delta = abs(ry - DRAG_DATA.LastY)
		}

		if delta > AdjacencyEpsilon {
			handleResize(rx, ry)
		} else {
			log.Printf("ResizeEnd: delta %v less than epsilon %v, skipping resize\n", delta, AdjacencyEpsilon)
		}

		DRAG_DATA = nil
	}

	// resizes the window by 1px vertically, then observes the actual change
	resizeBugHunt := func(X *xgbutil.XUtil, ev xevent.ButtonPressEvent) {
		// get xwindow from click
		clicked, err := wm.FindManagedWindowUnderMouse(X)
		if err != nil {
			log.Println(err)
			return
		}
		win := xwindow.New(X, clicked)

		names := []string{"PreDecor", "PostDecorPreMove", "PostDecor", "Pre", "Post"}
		geometries := make(map[string]xrect.Rect, 4)

		// take measurements
		pre_decor, err := win.DecorGeometry()
		if err != nil {
			log.Printf("Error fetching pre DecorGeom: %v\n", err)
		}
		geometries["PreDecor"] = pre_decor

		geo, err := win.Geometry()
		if err != nil {
			log.Printf("Error fetching pre Geom: %v\n", err)
		}
		geometries["Pre"] = geo

		// resize vertically by 1px
		log.Println("Resizing using window.Geometry() + 50, not DecorGeometry() + 1")
		//err = win.WMResize(geo.Width() + 50, geo.Height())
		if err != nil {
			log.Println(err)
		}
		// wait to finish
		err = wm.PollFor(win, wm.GeometryDiffers(geo), wm.DecorDiffers(pre_decor))
		if err != nil {
			log.Printf("Oops wjile waiting for resizing and things: %v\n", err)
		}
		post_decor_pre_move, _, err := wm.Geometries(win)
		if err != nil {
			log.Println(err)
			post_decor_pre_move = pre_decor
		}
		geometries["PostDecorPreMove"] = post_decor_pre_move
		// move zero pixels, then wait
		err = wm.Move(win, post_decor_pre_move.X(), post_decor_pre_move.Y())
		if err != nil {
			log.Printf("error in wm.Move zero px: %v\n", err)
		}

		geo, err = win.DecorGeometry()
		if err != nil {
			log.Printf("Error fetching post DecorGeom: %v\n", err)
		}
		geometries["PostDecor"] = geo

		geo, err = win.Geometry()
		if err != nil {
			log.Printf("Error fetching post Geom: %v\n", err)
		}
		geometries["Post"] = geo

		for _, k := range names {
			log.Printf("%s: %v\n", k, geometries[k])
		}

		// release X events
		// needed if the event binding is synchronous
		// see http://godoc.burntsushi.net/pkg/github.com/BurntSushi/xgbutil/mousebind/#hdr-When_to_use_a_synchronous_binding
		// xproto.AllowEvents(X.Conn(), xproto.AllowReplayPointer, 0)
	}

	// bind handler
	mousebind.Drag(X, X.RootWin(), X.RootWin(), KeyComboResize, true,
		handleDragStart,
		handleDragStep,
		handleDragEnd)

	mousebind.ButtonPressFun(resizeBugHunt).Connect(X, X.RootWin(), ui.KeyOption+"-Shift-Control-1", false, true)

}