// 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) }
// 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) } } }
// 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) }
// 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) }
// 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) } } }
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) }
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) }
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) }