// DrawText is a convenience function that will create a new image, render // the provided text to it, paint the image to the provided window, and resize // the window to fit the text snugly. // // An error can occur when rendering the text to an image. func DrawText(win *xwindow.Window, font *truetype.Font, size float64, fontClr, bgClr render.Color, text string) error { // BUG(burntsushi): If `text` is zero-length, very bad things happen. if len(text) == 0 { text = " " } // Over estimate the extents. ew, eh := xgraphics.Extents(font, size, text) // Create an image using the over estimated extents. img := xgraphics.New(win.X, image.Rect(0, 0, ew, eh)) xgraphics.BlendBgColor(img, bgClr.ImageColor()) // Now draw the text, grab the (x, y) position advanced by the text, and // check for an error in rendering. _, _, err := img.Text(0, 0, fontClr.ImageColor(), size, font, text) if err != nil { return err } // Resize the window to the geometry determined by (x, y). win.Resize(ew, eh) // Now draw the image to the window and destroy it. img.XSurfaceSet(win.Id) // subimg := img.SubImage(image.Rect(0, 0, ew, eh)) img.XDraw() img.XPaint(win.Id) img.Destroy() return nil }
// DrawText is a convenience function that will create a new image, render // the provided text to it, paint the image to the provided window, and resize // the window to fit the text snugly. // // An error can occur when rendering the text to an image. func DrawText(win *xwindow.Window, font *truetype.Font, size float64, fontClr, bgClr color.RGBA, text string) error { // Over estimate the extents. ew, eh := xgraphics.TextMaxExtents(font, size, text) eh += misc.TextBreathe // <-- this is the bug // Create an image using the over estimated extents. img := xgraphics.New(win.X, image.Rect(0, 0, ew, eh)) xgraphics.BlendBgColor(img, bgClr) // Now draw the text, grab the (x, y) position advanced by the text, and // check for an error in rendering. x, y, err := img.Text(0, 0, fontClr, size, font, text) if err != nil { return err } // Resize the window to the geometry determined by (x, y). w, h := x, y+misc.TextBreathe // <-- also part of the bug win.Resize(w, h) // Now draw the image to the window and destroy it. img.XSurfaceSet(win.Id) subimg := img.SubImage(image.Rect(0, 0, w, h)) subimg.XDraw() subimg.XPaint(win.Id) img.Destroy() return nil }
// Swap the position and size of the target and incoming windows func Swap(target, incoming *xwindow.Window) error { // get bounds for both windows target_bounds, err := target.DecorGeometry() if err != nil { log.Printf("Swap: error getting bounds of target: %v\n", err) return err } incoming_bounds, err := incoming.DecorGeometry() if err != nil { log.Printf("Swap: error getting bounds of incoming: %v\n", err) return err } // configure windows, easy as pie! err = MoveResize(target, incoming_bounds.X(), incoming_bounds.Y(), incoming_bounds.Width(), incoming_bounds.Height()) if err != nil { log.Printf("Swap: error configuring target: %v\n", err) return err } err = MoveResize(incoming, target_bounds.X(), target_bounds.Y(), target_bounds.Width(), target_bounds.Height()) if err != nil { log.Printf("Swap: error configuring incoming: %v\n", err) return err } // cool return nil }
// cutting windows in half on the Y axis func splitHorizontal(target, incoming *xwindow.Window, incomingOnLeft bool) error { bounds, err := target.DecorGeometry() if err != nil { log.Printf("splitHorizontal: error getting bounds of target: %v\n", err) return err } left_width := bounds.Width() / 2 right_width := bounds.Width() - left_width var left, right *xwindow.Window if incomingOnLeft { left = incoming right = target } else { left = target right = incoming } err = MoveResize(right, bounds.X()+left_width, bounds.Y(), right_width, bounds.Height()) if err != nil { log.Printf("splitHorizontal: error configuring right: %v\n", err) return err } err = MoveResize(left, bounds.X(), bounds.Y(), left_width, bounds.Height()) if err != nil { log.Printf("splitHorizontal: error configuring left: %v\n", err) return err } // cool return nil }
func newWindow(controlCh *controlCh, X *xgbutil.XUtil, width, height int) *xwindow.Window { var ( err error win *xwindow.Window ) win, err = xwindow.Generate(X) if err != nil { panic(err) } win.Create(X.RootWin(), 0, 0, width, height, xproto.CwBackPixel|xproto.CwEventMask, 0, xproto.EventMaskButtonRelease) // Xorg application exits when the window is closed. win.WMGracefulClose( func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() xevent.Quit(X) controlCh.exit <- true }) // In order to get ConfigureNotify events, we must listen to the window // using the 'StructureNotify' mask. win.Listen(xproto.EventMaskButtonPress | xproto.EventMaskButtonRelease | xproto.EventMaskKeyPress | xproto.EventMaskKeyRelease | xproto.EventMaskStructureNotify) win.Map() return win }
func Geometries(win *xwindow.Window) (xrect.Rect, xrect.Rect, error) { decor, err := win.DecorGeometry() if err != nil { return nil, nil, err } base, err := win.Geometry() if err != nil { return nil, nil, err } return decor, base, nil }
// same as above, but moveresize instead of just move at the first step, // then resize to the provided w/h instead of a snapshotted one // this implementation differs from Move in that it makes no effort to be end-synchronous // This function waits only on the window's inner geometry resizing, not on actual movement occuring func MoveResize(win *xwindow.Window, x, y, width, height int) error { // snapshot window dimensions base, err := win.Geometry() if err != nil { return err } // move window then wait... err = win.WMMoveResize(x, y, width, height) if err != nil { return err } err = PollFor(win, GeometryDiffers(base)) if err != nil { return err } // check that the new geometry is what we requested // this may be inadvisable: what about window hints? geom, err := win.Geometry() if err != nil { return err } if geom.Width() != width || geom.Height() != height { // something derped! resize to make it right! // if window hints constrained us, this won't upset them log.Println("MoveResize: resizing again after incorrect new dimensions") err = win.WMResize(width, height) if err != nil { return err } } return nil }
func newWindow(X *xgbutil.XUtil, width, height int) *xwindow.Window { var ( err error win *xwindow.Window ) win, err = xwindow.Generate(X) if err != nil { log.Fatal(err) } win.Create(X.RootWin(), 0, 0, width, height, xproto.CwBackPixel|xproto.CwEventMask, 0, xproto.EventMaskButtonRelease) win.WMGracefulClose( func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() xevent.Quit(X) }) win.Map() if err != nil { log.Fatal(err) } return win }
// Put the incoming window on the `dir` side of the target, // and transform the orthagonal dimension (eg, if `dir` is Up, then dim is `Width` // to be the same as the target's dimension // TODO: clip windows to display boundry func Shove(target, incoming *xwindow.Window, dir Direction) error { // get geometries i, err := incoming.DecorGeometry() if err != nil { return err } t, err := target.DecorGeometry() if err != nil { return err } // move in the correct direction if dir == Top { err := MoveResize(incoming, t.X(), t.Y()-i.Height(), t.Width(), i.Height()) if err != nil { return err } } if dir == Bottom { err := MoveResize(incoming, t.X(), t.Y()+t.Height(), t.Width(), i.Height()) if err != nil { return err } } if dir == Left { err := MoveResize(incoming, t.X()-i.Width(), t.Y(), i.Width(), t.Height()) if err != nil { return err } } if dir == Right { err := MoveResize(incoming, t.X()+t.Width(), t.Y(), i.Width(), t.Height()) if err != nil { return err } } return nil }
// move the incoming window so that it is directly adjacent to the target's edge func AdjoinEdge(target, incoming *xwindow.Window, dir wm.Direction) error { t, err := target.DecorGeometry() if err != nil { return err } i, err := incoming.DecorGeometry() if err != nil { return err } delta := EdgePos(t, dir) - EdgePos(i, dir.Opposite()) if dir == wm.Left || dir == wm.Right { return wm.Move(incoming, delta+i.X(), i.Y()) } else { return wm.Move(incoming, i.X(), delta+i.Y()) } return nil }
func newWindow(X *xgbutil.XUtil, width, height int) *xwindow.Window { var ( err error win *xwindow.Window ) win, err = xwindow.Generate(X) if err != nil { log.Fatal(err) } win.Create(X.RootWin(), 0, 0, width, height, xproto.CwBackPixel|xproto.CwEventMask, 0, xproto.EventMaskButtonRelease) win.WMGracefulClose( func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) // w.Destroy() xevent.Quit(X) application.Exit() }) // In order to get ConfigureNotify events, we must listen to the window // using the 'StructureNotify' mask. win.Listen(xproto.EventMaskStructureNotify) win.Map() xevent.ConfigureNotifyFun( func(X *xgbutil.XUtil, ev xevent.ConfigureNotifyEvent) { reshape(int(ev.Width), int(ev.Height)) }).Connect(X, win.Id) // err = mousebind.ButtonReleaseFun( // func(X *xgbutil.XUtil, ev xevent.ButtonReleaseEvent) { // newWindow(X) // }).Connect(X, win.Id, "1", false, false) if err != nil { log.Fatal(err) } return win }
func splitVertical(target, incoming *xwindow.Window, incomingOnTop bool) error { bounds, err := target.DecorGeometry() if err != nil { log.Printf("splitVertical: error getting bounds of target: %v\n", err) return err } bottom_height := bounds.Height() / 2 top_height := bounds.Height() - bottom_height var top, bottom *xwindow.Window if incomingOnTop { top = incoming bottom = target } else { top = target bottom = incoming } // target goes on bottom... err = MoveResize(bottom, bounds.X(), bounds.Y()+top_height, bounds.Width(), bottom_height) if err != nil { log.Printf("splitVertical: error configuring bottom: %v\n", err) return err } // and incoming on top err = MoveResize(top, bounds.X(), bounds.Y(), bounds.Width(), top_height) if err != nil { log.Printf("splitVertical: error configuring top: %v\n", err) return err } // cool return nil }
// Sometimes window managers are really slow about // re-implemented here because under Fluxbox, win.WMMove() results in the window // growing vertically by the height of the titlebar! // So we snapshot the size of the window before we move it, // move it, compare the sizes, then resize it vertically to be in line with our intentions // // this is synchronous: it waits for the window to finish moving before it releases control // because it would be impossible to selectivley poll for just the move. func Move(win *xwindow.Window, x, y int) error { // snapshot both sorts of window geometries decor_geom, geom, err := Geometries(win) if err != nil { return err } log.Printf("Move: detected geometry to be %v\n", geom) // move the window, then wait for it to finish moving err = win.WMMove(x, y) if err != nil { return err } // this waits 30MS under non-Fluxbox window manager // WHAT DO err = PollFor(win, GeometryDiffers(geom)) if err != nil { // if we had a timeout, that means that the geometry didn't derp during // moving, and everything is A-OK! // skip the rest of the function if _, wasTimeout := err.(*TimeoutError); wasTimeout { return nil } return err } // compare window widths before/after move _, post_move_base, err := Geometries(win) if err != nil { return err } delta_w := post_move_base.Width() - geom.Width() delta_h := post_move_base.Height() - geom.Height() if delta_h != 0 || delta_w != 0 { // fluxbox has done it again. We issued a move, and we got a taller window, too! log.Printf("Move: resetting dimensions to %v due to w/h delta: %v/%v\n", geom, delta_w, delta_h) err = win.WMResize(geom.Width(), geom.Height()) if err != nil { return err } // wait for that to succeed err = PollFor(win, GeometryDiffers(post_move_base)) if err != nil { return err } } // make sure window did actually move err = PollFor(win, DecorDiffers(decor_geom)) if err != nil { // if we had a timeout, that means that the window didn't move // we want to send an error mentioning that fact specifically // instead of a generic "lol timeout happan in polling :DDD" if te, wasTimeout := err.(*TimeoutError); wasTimeout { return &TimeoutError{"Move: window didn't move", te.Timeout} } // return whatever other error stymied the polling return err } return nil }
// resize a window by a certain number of pixels in a given direction. // This function tries to prevent the window from moving func ResizeDirection(X *xgbutil.XUtil, win *xwindow.Window, dir wm.Direction, px int) error { // we resize around the decor_geometry of the window if px == 0 { // no need to resize return nil } geom, err := win.Geometry() if err != nil { return fmt.Errorf("Resize: coudn't get normal geometry: %v", err) } w, h := geom.Width(), geom.Height() log.Printf("ResizeDirection: pre_geom == %v\n", geom) if dir == wm.Left || dir == wm.Right { // horizontal resize w += px } else { h += px } // two-step resize -> move process, to compensate for WM peculiarities and window sizing hints // first save the initial position info pre_decor, err := win.DecorGeometry() if err != nil { return fmt.Errorf("Resize: coudn't get decorated geometry: %v", err) } // resize the window err = win.WMResize(w, h) if err != nil { return err } // wait for the geometry to change // we use a goroutine to query X a bunch while waiting for the window // to finish resizing err = wm.PollFor(win, wm.DecorDiffers(pre_decor), wm.GeometryDiffers(geom)) if err != nil { return fmt.Errorf("ResizeDirection: error waiting for window geometries to change: %v", err) } post_decor, post_geom, err := wm.Geometries(win) if err != nil { return err } log.Printf("ResizeDirection: post_decor == %v\n", post_decor) log.Printf("ResizeDirection: post_geom == %v\n", post_geom) // the opposite edge should stay in the same place op := dir.Opposite() pre_edge := EdgePos(pre_decor, op) post_edge := EdgePos(post_decor, op) delta := post_edge - pre_edge x, y := post_decor.X(), post_decor.Y() // move the window upwards by our height resize so that the bottom edge stays in the same place if dir == wm.Top || dir == wm.Bottom { y -= delta } // move the window right by our resize so that the right stays in the same place if dir == wm.Left || dir == wm.Right { x -= delta } // move to lock opposite edge return wm.Move(win, x, y) }