package xgraphics import ( "fmt" "image" "github.com/jezek/xgb/xproto" "github.com/jezek/xgbutil" "github.com/jezek/xgbutil/ewmh" "github.com/jezek/xgbutil/icccm" "github.com/jezek/xgbutil/keybind" "github.com/jezek/xgbutil/mousebind" "github.com/jezek/xgbutil/xevent" "github.com/jezek/xgbutil/xwindow" ) /* xgraphics/xsurface.go contains methods for the Image type that perform X related requests. Namely, methods that send image data start with 'X'. */ // XSurfaceSet will set the given window's background to this image's pixmap. // Note that an image can have multiple surfaces, which is why the window // id still needs to be passed to XPaint. A call to XSurfaceSet simply tells // X that the window specified should use the pixmap in Image as its // background image. // Note that XSurfaceSet cannot be called on a sub-image. (An error will be // returned if you do.) // XSurfaceSet will also allocate an X pixmap if one hasn't been created for // this image yet. // (Generating a pixmap id can cause an error, so this call could return // an error.) func (im *Image) XSurfaceSet(wid xproto.Window) error { if im.Subimg { return fmt.Errorf("XSurfaceSet cannot be called on sub-images." + "Please set the surface using the original parent image.") } if im.Pixmap == 0 { if err := im.CreatePixmap(); err != nil { return err } } // Tell the surface (window) to use this pixmap. xproto.ChangeWindowAttributes(im.X.Conn(), wid, xproto.CwBackPixmap, []uint32{uint32(im.Pixmap)}) return nil } // CreatePixmap allocates an X resource identifier for a pixmap. (It does not // do any drawing.) You only need to call this if you're using XDraw/XExpPaint. // If you're using XSurfaceSet/XDraw/XPaint, then CreatePixmap is called for // you automatically. func (im *Image) CreatePixmap() error { // Generate the pixmap id. pid, err := xproto.NewPixmapId(im.X.Conn()) if err != nil { return err } // Now actually create the pixmap. err = xproto.CreatePixmapChecked(im.X.Conn(), im.X.Screen().RootDepth, pid, xproto.Drawable(im.X.RootWin()), uint16(im.Bounds().Dx()), uint16(im.Bounds().Dy())).Check() if err != nil { return err } // Now give it to the image. im.Pixmap = pid return nil } // XPaint will write the contents of the pixmap to a window. // Note that painting will do nothing if XDraw hasn't been called. // XPaint is what switches the buffer (drawn to using XDraw) into the window // to be visible. That is, multiple calls to XDraw can be made, and the screen // will only be updated once with a call to XPaint. func (im *Image) XPaint(wid xproto.Window) { // We clear the whole window here because sometimes we rely on the tiling // of a background pixmap. If anyone knows if this is a significant // performance problem, please let me know. (It seems like the whole area // of the window is cleared when it is resized anyway.) xproto.ClearArea(im.X.Conn(), false, wid, 0, 0, 0, 0) } // XExpPaint achieves a similar result as XPaint and XSurfaceSet, but // uses CopyArea instead of setting a background pixmap and using ClearArea. // CreatePixmap must be called before using XExpPaint. // XExpPaint can be called on sub-images. // x and y correspond to the destination x and y to copy the image to. // // This should not be used on the same image with XSurfaceSet and XPaint. func (im *Image) XExpPaint(wid xproto.Window, x, y int) { if im.Pixmap == 0 { return } xproto.CopyArea(im.X.Conn(), xproto.Drawable(im.Pixmap), xproto.Drawable(wid), im.X.GC(), int16(im.Rect.Min.X), int16(im.Rect.Min.Y), int16(x), int16(y), uint16(im.Rect.Dx()), uint16(im.Rect.Dy())) } // XPaintRects is a convenience function for issuing XDraw requests on // each sub-image generated by the rects in the slice provided, and then // painting the updated pixmap all at once to the window provided. // This is efficient because no pixels are copied when taking a SubImage, // and each XDraw call on a sub-image updates the pixels represented by that // sub-image and only that sub-image. func (im *Image) XPaintRects(wid xproto.Window, rects ...image.Rectangle) { for _, rect := range rects { if si := im.SubImage(rect).(*Image); si != nil { si.XDraw() } } im.XPaint(wid) } // XDraw will write the contents of Image to a pixmap. // Note that this is more like a buffer. Drawing does not put the contents // on the screen. // After drawing, it is necessary to call XPaint to put the contents somewhere. // Draw may return an X error if something has gone horribly wrong. // // XSurfaceSet should be called before XDraw. (If not, X will yell at you.) // More specifically, CreatePixmap needs to be called before XDraw, but it is // done automatically in XSurfaceSet. // // If you're using sub-images to update a particular region of the image, XDraw // is where you'll see the performance benefit (not XPaint). func (im *Image) XDraw() { im.xdraw(false) } // XDrawChecked is the same as XDraw, but issues PutImageChecked requests // instead. This should *only* be used for debugging purposes, as each // PutImageChecked request blocks for a round trip to the X server. func (im *Image) XDrawChecked() error { return im.xdraw(true) } func (im *Image) xdraw(checked bool) error { width, height := im.Rect.Dx(), im.Rect.Dy() // Put the raw image data into its own slice. // If this isn't a sub-image, then skip because it isn't necessary. var data []uint8 if !im.Subimg { data = im.Pix } else { data = make([]uint8, width*height*4) for y := im.Rect.Min.Y; y < im.Rect.Max.Y; y++ { i := (y - im.Rect.Min.Y) * width * 4 copy(data[i:i+4*width], im.Pix[im.PixOffset(im.Rect.Min.X, y):]) } } // X's max request size (by default) is (2^16) * 4 = 262144 bytes, which // corresponds precisely to a 256x256 sized image with 32 bits per pixel. // Thus, we check the size of the image data and calculate the number of // rows of the image we'll send in each request. If a single row of an // image exceeds the max request length, we're in trouble. N.B. The // constant 28 comes from the fixed size part of a PutImage request. rowsPer := (xgbutil.MaxReqSize - 28) / (width * 4) bytesPer := rowsPer * width * 4 // The start x position of what we're sending. Doesn't change. xpos := im.Rect.Min.X // The start y position of what we're sending. Increases based on the // number of rows of the image we send in each request. ypos := im.Rect.Min.Y // The height of each PutImage request. It's always rowsPer, unless its // the last request and we're not sending the maximum number of bytes. heightPer := 0 // The start and end positions of the raw bytes being sent. start, end := 0, 0 // The sliced data we're sending, for convenience. var toSend []byte for end < len(data) { end = start + bytesPer if end > len(data) { // make sure end doesn't extend beyond data end = len(data) } toSend = data[start:end] heightPer = len(toSend) / 4 / width if checked { err := xproto.PutImageChecked( im.X.Conn(), xproto.ImageFormatZPixmap, xproto.Drawable(im.Pixmap), im.X.GC(), uint16(width), uint16(heightPer), int16(xpos), int16(ypos), 0, 24, toSend).Check() if err != nil { return err } } else { xproto.PutImage(im.X.Conn(), xproto.ImageFormatZPixmap, xproto.Drawable(im.Pixmap), im.X.GC(), uint16(width), uint16(heightPer), int16(xpos), int16(ypos), 0, 24, toSend) } start = end ypos += rowsPer } return nil } // XShow creates a new window and paints the image to the window. // This is useful for debugging, or if you're creating an image viewer. // XShow also returns the xwindow.Window value, in case you want to do // further processing. (Like attach event handlers.) func (im *Image) XShow() *xwindow.Window { return im.XShowExtra("", false) } // XShowName is just like XShow, except it sets the name of the window to the // name provided, and will quit the current event loop if 'quit' is true when // the window is closed. // If name is empty and quit is false, then the behavior is precisely the same // as XShow. func (im *Image) XShowExtra(name string, quit bool) *xwindow.Window { if len(name) == 0 { name = "xgbutil Image Window" } w, h := im.Rect.Dx(), im.Rect.Dy() win, err := xwindow.Generate(im.X) if err != nil { xgbutil.Logger.Printf("Could not generate new window id: %s", err) return nil } // Create a very simple window with dimensions equal to the image. win.Create(im.X.RootWin(), 0, 0, w, h, 0) // Make this window close gracefully. win.WMGracefulClose(func(w *xwindow.Window) { xevent.Detach(w.X, w.Id) keybind.Detach(w.X, w.Id) mousebind.Detach(w.X, w.Id) w.Destroy() if quit { xevent.Quit(w.X) } }) // Set WM_STATE so it is interpreted as a top-level window. err = icccm.WmStateSet(im.X, win.Id, &icccm.WmState{ State: icccm.StateNormal, }) if err != nil { // not a fatal error xgbutil.Logger.Printf("Could not set WM_STATE: %s", err) } // Set WM_NORMAL_HINTS so the window can't be resized. err = icccm.WmNormalHintsSet(im.X, win.Id, &icccm.NormalHints{ Flags: icccm.SizeHintPMinSize | icccm.SizeHintPMaxSize, MinWidth: uint(w), MinHeight: uint(h), MaxWidth: uint(w), MaxHeight: uint(h), }) if err != nil { // not a fatal error xgbutil.Logger.Printf("Could not set WM_NORMAL_HINTS: %s", err) } // Set _NET_WM_NAME so it looks nice. err = ewmh.WmNameSet(im.X, win.Id, name) if err != nil { // not a fatal error xgbutil.Logger.Printf("Could not set _NET_WM_NAME: %s", err) } // Paint our image before mapping. im.XSurfaceSet(win.Id) im.XDraw() im.XPaint(win.Id) // Now we can map, since we've set all our properties. // (The initial map is when the window manager starts managing.) win.Map() return win }