package screen // #cgo pkg-config: x11 xext // #include // #include // #include // #include // #define XUTIL_DEFINE_FUNCTIONS // #include // #include // // void copyBGR24(void *dst, char *src, size_t l) { // 64bit aligned copy // uint64_t *d = (uint64_t*)dst; // uint64_t *s = (uint64_t*)src; // l /= 8; // for (size_t i = 0; i < l; i ++) { // uint64_t v = *s; // // Reorder BGR to RGB // *d = 0xFF000000FF000000 | // ((v >> 16) & 0xFF00000000) | (v & 0xFF0000000000) | ((v & 0xFF00000000) << 16) | // ((v >> 16) & 0xFF) | (v & 0xFF00) | ((v & 0xFF) << 16); // d++; // s++; // } // } // // void copyBGR16(void *dst, char *src, size_t l) { // 64bit aligned copy // uint64_t *d = (uint64_t*)dst; // uint32_t *s = (uint32_t*)src; // l /= 8; // for (size_t i = 0; i < l; i ++) { // uint64_t v = *s; // // Reorder BGR to RGB // *d = 0xFF000000FF000000 | // ((v & 0xF8000000) << 8) | ((v & 0x7E00000) << 21) | ((v & 0x1F0000) << 35) | // ((v & 0xF800) >> 8) | ((v & 0x7E0) << 5) | ((v & 0x1F) << 19); // d++; // s++; // } // } // // char *align64(char *ptr) { // return 64bit aligned pointer // if (((size_t)ptr & 0x07) == 0) { // return ptr; // } // // Clear lower 3bits to align the address to 8bytes. // return (char*)(((size_t)ptr & (~(size_t)0x07)) + 0x08); // } // size_t align64ForTest(size_t ptr) { // return (size_t)align64((char*)ptr); // } import "C" import ( "errors" "fmt" "image" "image/color" "unsafe" ) const shmaddrInvalid = ^uintptr(0) type display C.Display type pixelFormat int const ( pixFmtBGR24 pixelFormat = iota pixFmtRGB24 pixFmtBGR16 pixFmtRGB16 ) func openDisplay() (*display, error) { dp := C.XOpenDisplay(nil) if dp == nil { return nil, errors.New("failed to open display") } return (*display)(dp), nil } func (d *display) c() *C.Display { return (*C.Display)(d) } func (d *display) Close() { C.XCloseDisplay(d.c()) } func (d *display) NumScreen() int { return int(C.XScreenCount(d.c())) } type shmImage struct { dp *C.Display img *C.XImage shm C.XShmSegmentInfo b []byte pixFmt pixelFormat } func (s *shmImage) Free() { if s.img != nil { C.XShmDetach(s.dp, &s.shm) C.XDestroyImage(s.img) } if uintptr(unsafe.Pointer(s.shm.shmaddr)) != shmaddrInvalid { C.shmdt(unsafe.Pointer(s.shm.shmaddr)) } } func (s *shmImage) ColorModel() color.Model { return color.RGBAModel } func (s *shmImage) Bounds() image.Rectangle { return image.Rect(0, 0, int(s.img.width), int(s.img.height)) } type colorFunc func() (r, g, b, a uint32) func (c colorFunc) RGBA() (r, g, b, a uint32) { return c() } func (s *shmImage) At(x, y int) color.Color { switch s.pixFmt { case pixFmtRGB24: addr := (x + y*int(s.img.width)) * 4 r := uint32(s.b[addr]) * 0x100 g := uint32(s.b[addr+1]) * 0x100 b := uint32(s.b[addr+2]) * 0x100 return colorFunc(func() (_, _, _, _ uint32) { return r, g, b, 0xFFFF }) case pixFmtBGR24: addr := (x + y*int(s.img.width)) * 4 b := uint32(s.b[addr]) * 0x100 g := uint32(s.b[addr+1]) * 0x100 r := uint32(s.b[addr+2]) * 0x100 return colorFunc(func() (_, _, _, _ uint32) { return r, g, b, 0xFFFF }) case pixFmtRGB16: addr := (x + y*int(s.img.width)) * 2 b1, b2 := s.b[addr], s.b[addr+1] r := uint32(b1>>3) * 0x100 g := uint32((b1&0x7)<<3|(b2&0xE0)>>5) * 0x100 b := uint32(b2&0x1F) * 0x100 return colorFunc(func() (_, _, _, _ uint32) { return r, g, b, 0xFFFF }) case pixFmtBGR16: addr := (x + y*int(s.img.width)) * 2 b1, b2 := s.b[addr], s.b[addr+1] b := uint32(b1>>3) * 0x100 g := uint32((b1&0x7)<<3|(b2&0xE0)>>5) * 0x100 r := uint32(b2&0x1F) * 0x100 return colorFunc(func() (_, _, _, _ uint32) { return r, g, b, 0xFFFF }) default: panic("unsupported pixel format") } } func (s *shmImage) RGBAAt(x, y int) color.RGBA { switch s.pixFmt { case pixFmtRGB24: addr := (x + y*int(s.img.width)) * 4 r := s.b[addr] g := s.b[addr+1] b := s.b[addr+2] return color.RGBA{R: r, G: g, B: b, A: 0xFF} case pixFmtBGR24: addr := (x + y*int(s.img.width)) * 4 b := s.b[addr] g := s.b[addr+1] r := s.b[addr+2] return color.RGBA{R: r, G: g, B: b, A: 0xFF} case pixFmtRGB16: addr := (x + y*int(s.img.width)) * 2 b1, b2 := s.b[addr], s.b[addr+1] r := b1 >> 3 g := (b1&0x7)<<3 | (b2&0xE0)>>5 b := b2 & 0x1F return color.RGBA{R: r, G: g, B: b, A: 0xFF} case pixFmtBGR16: addr := (x + y*int(s.img.width)) * 2 b1, b2 := s.b[addr], s.b[addr+1] b := b1 >> 3 g := (b1&0x7)<<3 | (b2&0xE0)>>5 r := b2 & 0x1F return color.RGBA{R: r, G: g, B: b, A: 0xFF} default: panic("unsupported pixel format") } } func (s *shmImage) ToRGBA(dst *image.RGBA) *image.RGBA { dst.Rect = s.Bounds() dst.Stride = int(s.img.width) * 4 l := int(4 * s.img.width * s.img.height) if len(dst.Pix) < l { if cap(dst.Pix) < l { dst.Pix = make([]uint8, l) } dst.Pix = dst.Pix[:l] } switch s.pixFmt { case pixFmtRGB24: C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix))) return dst case pixFmtBGR24: C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix))) return dst case pixFmtRGB16: C.memcpy(unsafe.Pointer(&dst.Pix[0]), unsafe.Pointer(s.img.data), C.size_t(len(dst.Pix))) return dst case pixFmtBGR16: C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.size_t(len(dst.Pix))) return dst default: panic("unsupported pixel format") } } func newShmImage(dp *C.Display, screen int) (*shmImage, error) { cScreen := C.int(screen) w := int(C.XDisplayWidth(dp, cScreen)) h := int(C.XDisplayHeight(dp, cScreen)) v := C.XDefaultVisual(dp, cScreen) depth := int(C.XDefaultDepth(dp, cScreen)) s := &shmImage{dp: dp} switch { case v.red_mask == 0xFF && v.green_mask == 0xFF00 && v.blue_mask == 0xFF0000: s.pixFmt = pixFmtRGB24 case v.red_mask == 0xFF0000 && v.green_mask == 0xFF00 && v.blue_mask == 0xFF: s.pixFmt = pixFmtBGR24 case v.red_mask == 0x1F && v.green_mask == 0x7E0 && v.blue_mask == 0xF800: s.pixFmt = pixFmtRGB16 case v.red_mask == 0xF800 && v.green_mask == 0x7E0 && v.blue_mask == 0x1F: s.pixFmt = pixFmtBGR16 default: fmt.Printf("x11capture: unsupported pixel format (R: %0x, G: %0x, B: %0x)\n", v.red_mask, v.green_mask, v.blue_mask) return nil, errors.New("unsupported pixel format") } s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.size_t(w*h*4+8), C.IPC_CREAT|0600) if s.shm.shmid == -1 { return nil, errors.New("failed to get shared memory") } s.shm.shmaddr = (*C.char)(C.shmat(s.shm.shmid, unsafe.Pointer(nil), 0)) if uintptr(unsafe.Pointer(s.shm.shmaddr)) == shmaddrInvalid { s.shm.shmaddr = nil return nil, errors.New("failed to get shared memory address") } s.shm.readOnly = 0 C.shmctl(s.shm.shmid, C.IPC_RMID, nil) s.img = C.XShmCreateImage( dp, v, C.uint(depth), C.ZPixmap, C.align64(s.shm.shmaddr), &s.shm, C.uint(w), C.uint(h)) if s.img == nil { s.Free() return nil, errors.New("failed to create XShm image") } C.XShmAttach(dp, &s.shm) C.XSync(dp, 0) return s, nil } type reader struct { dp *C.Display img *shmImage } func newReader(screen int) (*reader, error) { dp := C.XOpenDisplay(nil) if dp == nil { return nil, errors.New("failed to open display") } if C.XShmQueryExtension(dp) == 0 { return nil, errors.New("no XShm support") } img, err := newShmImage(dp, screen) if err != nil { C.XCloseDisplay(dp) return nil, err } return &reader{ dp: dp, img: img, }, nil } func (r *reader) Size() (int, int) { return int(r.img.img.width), int(r.img.img.height) } func (r *reader) Read() *shmImage { C.XShmGetImage(r.dp, C.XDefaultRootWindow(r.dp), r.img.img, 0, 0, C.AllPlanes) r.img.b = C.GoBytes( unsafe.Pointer(r.img.img.data), C.int(r.img.img.width*r.img.img.height*4), ) return r.img } func (r *reader) Close() { r.img.Free() C.XCloseDisplay(r.dp) } // cAlign64 is fot testing func cAlign64(ptr uintptr) uintptr { return uintptr(C.align64ForTest(C.size_t(uintptr(ptr)))) }