github.com/as/shiny@v0.8.2/driver/x11driver/screen.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package x11driver
     6  
     7  import (
     8  	"fmt"
     9  	"image"
    10  	"image/color"
    11  	"image/draw"
    12  	"log"
    13  	"sync"
    14  
    15  	"github.com/BurntSushi/xgb"
    16  	"github.com/BurntSushi/xgb/render"
    17  	"github.com/BurntSushi/xgb/shm"
    18  	"github.com/BurntSushi/xgb/xproto"
    19  
    20  	"github.com/as/shiny/driver/internal/x11key"
    21  	"github.com/as/shiny/event/key"
    22  	"github.com/as/shiny/event/lifecycle"
    23  	"github.com/as/shiny/event/mouse"
    24  	"github.com/as/shiny/math/f64"
    25  	"github.com/as/shiny/screen"
    26  	//	"github.com/as/shiny/event/paint"
    27  )
    28  
    29  // TODO: check that xgb is safe to use concurrently from multiple goroutines.
    30  // For example, its Conn.WaitForEvent concept is a method, not a channel, so
    31  // it's not obvious how to interrupt it to service a NewWindow request.
    32  
    33  type screenImpl struct {
    34  	xc      *xgb.Conn
    35  	xsi     *xproto.ScreenInfo
    36  	keysyms x11key.KeysymTable
    37  
    38  	atomNETWMName      xproto.Atom
    39  	atomUTF8String     xproto.Atom
    40  	atomWMDeleteWindow xproto.Atom
    41  	atomWMProtocols    xproto.Atom
    42  	atomWMTakeFocus    xproto.Atom
    43  
    44  	pixelsPerPt  float32
    45  	pictformat24 render.Pictformat
    46  	pictformat32 render.Pictformat
    47  
    48  	// window32 and its related X11 resources is an unmapped window so that we
    49  	// have a depth-32 window to create depth-32 pixmaps from, i.e. pixmaps
    50  	// with an alpha channel. The root window isn't guaranteed to be depth-32.
    51  	gcontext32 xproto.Gcontext
    52  	window32   xproto.Window
    53  
    54  	// opaqueP is a fully opaque, solid fill picture.
    55  	opaqueP render.Picture
    56  
    57  	uniformMu sync.Mutex
    58  	uniformC  render.Color
    59  	uniformP  render.Picture
    60  
    61  	mu              sync.Mutex
    62  	buffers         map[shm.Seg]*bufferImpl
    63  	uploads         map[uint16]chan struct{}
    64  	windowX         xproto.Window
    65  	windowImp       *windowImpl
    66  	nPendingUploads int
    67  	completionKeys  []uint16
    68  }
    69  
    70  func newScreenImpl(xc *xgb.Conn) (*screenImpl, error) {
    71  	s := &screenImpl{
    72  		xc:      xc,
    73  		xsi:     xproto.Setup(xc).DefaultScreen(xc),
    74  		buffers: map[shm.Seg]*bufferImpl{},
    75  		uploads: map[uint16]chan struct{}{},
    76  		//		windows: map[xproto.Window]*windowImpl{},
    77  	}
    78  	if err := s.initAtoms(); err != nil {
    79  		return nil, err
    80  	}
    81  	if err := s.initKeyboardMapping(); err != nil {
    82  		return nil, err
    83  	}
    84  	const (
    85  		mmPerInch = 25.4
    86  		ptPerInch = 72
    87  	)
    88  	pixelsPerMM := float32(s.xsi.WidthInPixels) / float32(s.xsi.WidthInMillimeters)
    89  	s.pixelsPerPt = pixelsPerMM * mmPerInch / ptPerInch
    90  	if err := s.initPictformats(); err != nil {
    91  		return nil, err
    92  	}
    93  	if err := s.initWindow32(); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	var err error
    98  	s.opaqueP, err = render.NewPictureId(xc)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
   101  	}
   102  	s.uniformP, err = render.NewPictureId(xc)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
   105  	}
   106  	render.CreateSolidFill(s.xc, s.opaqueP, render.Color{
   107  		Red:   0xffff,
   108  		Green: 0xffff,
   109  		Blue:  0xffff,
   110  		Alpha: 0xffff,
   111  	})
   112  	render.CreateSolidFill(s.xc, s.uniformP, render.Color{})
   113  
   114  	go s.run()
   115  	return s, nil
   116  }
   117  
   118  func (s *screenImpl) run() {
   119  	for {
   120  		ev, err := s.xc.WaitForEvent()
   121  		if err != nil {
   122  			log.Printf("x11driver: xproto.WaitForEvent: %v", err)
   123  			continue
   124  		}
   125  		w := s.windowImp
   126  		switch ev := ev.(type) {
   127  		case xproto.DestroyNotifyEvent:
   128  
   129  		case shm.CompletionEvent:
   130  			s.mu.Lock()
   131  			s.completionKeys = append(s.completionKeys, ev.Sequence)
   132  			s.handleCompletions()
   133  			s.mu.Unlock()
   134  
   135  		case xproto.ClientMessageEvent:
   136  			if ev.Type != s.atomWMProtocols || ev.Format != 32 {
   137  				break
   138  			}
   139  			switch xproto.Atom(ev.Data.Data32[0]) {
   140  			case s.atomWMDeleteWindow:
   141  				screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageDead})
   142  			case s.atomWMTakeFocus:
   143  				xproto.SetInputFocus(s.xc, xproto.InputFocusParent, ev.Window, xproto.Timestamp(ev.Data.Data32[1]))
   144  			}
   145  		case xproto.ConfigureNotifyEvent:
   146  			w.handleConfigureNotify(ev)
   147  		case xproto.ExposeEvent:
   148  			if ev.Count == 0 { // TODO(as)
   149  				w.handleExpose()
   150  			}
   151  		case xproto.FocusInEvent:
   152  			screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageFocused}) // TODO(as)
   153  		case xproto.FocusOutEvent:
   154  			screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageVisible}) // TODO(as)
   155  		case xproto.KeyPressEvent:
   156  			w.handleKey(ev.Detail, ev.State, key.DirPress)
   157  		case xproto.KeyReleaseEvent:
   158  			w.handleKey(ev.Detail, ev.State, key.DirRelease)
   159  		case xproto.ButtonPressEvent:
   160  			w.handleMouse(ev.EventX, ev.EventY, ev.Detail, ev.State, mouse.DirPress)
   161  		case xproto.ButtonReleaseEvent:
   162  			w.handleMouse(ev.EventX, ev.EventY, ev.Detail, ev.State, mouse.DirRelease)
   163  		case xproto.MotionNotifyEvent:
   164  			w.handleMouse(ev.EventX, ev.EventY, 0, ev.State, mouse.DirNone)
   165  		}
   166  	}
   167  }
   168  
   169  func (s *screenImpl) findWindow(key xproto.Window) *windowImpl {
   170  	return s.windowImp
   171  }
   172  
   173  // handleCompletions must only be called while holding s.mu.
   174  func (s *screenImpl) handleCompletions() {
   175  	return
   176  	if s.nPendingUploads != 0 {
   177  		return
   178  	}
   179  	for _, ck := range s.completionKeys {
   180  		completion, ok := s.uploads[ck]
   181  		if !ok {
   182  			log.Printf("x11driver: no matching upload for a SHM completion event")
   183  			continue
   184  		}
   185  		delete(s.uploads, ck)
   186  		close(completion)
   187  	}
   188  	s.completionKeys = s.completionKeys[:0]
   189  }
   190  
   191  const (
   192  	maxShmSide = 0x00007fff // 32,767 pixels.
   193  	maxShmSize = 0x10000000 // 268,435,456 bytes.
   194  )
   195  
   196  func (s *screenImpl) NewBuffer(size image.Point) (retBuf screen.Buffer, retErr error) {
   197  	// TODO: detect if the X11 server or connection cannot support SHM pixmaps,
   198  	// and fall back to regular pixmaps.
   199  
   200  	w, h := int64(size.X), int64(size.Y)
   201  	if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
   202  		return nil, fmt.Errorf("x11driver: invalid buffer size %v", size)
   203  	}
   204  
   205  	b := &bufferImpl{
   206  		s: s,
   207  		rgba: image.RGBA{
   208  			Stride: 4 * size.X,
   209  			Rect:   image.Rectangle{Max: size},
   210  		},
   211  		size: size,
   212  	}
   213  
   214  	if size.X == 0 || size.Y == 0 {
   215  		// No-op, but we can't take the else path because the minimum shmget
   216  		// size is 1.
   217  	} else {
   218  		xs, err := shm.NewSegId(s.xc)
   219  		if err != nil {
   220  			return nil, fmt.Errorf("x11driver: shm.NewSegId: %v", err)
   221  		}
   222  
   223  		bufLen := 4 * size.X * size.Y
   224  		shmid, addr, err := shmOpen(bufLen)
   225  		if err != nil {
   226  			return nil, fmt.Errorf("x11driver: shmOpen: %v", err)
   227  		}
   228  		defer func() {
   229  			if retErr != nil {
   230  				shmClose(addr)
   231  			}
   232  		}()
   233  		a := (*[maxShmSize]byte)(addr)
   234  		b.buf = (*a)[:bufLen:bufLen]
   235  		b.rgba.Pix = b.buf
   236  		b.addr = addr
   237  
   238  		// readOnly is whether the shared memory is read-only from the X11 server's
   239  		// point of view. We need false to use SHM pixmaps.
   240  		const readOnly = false
   241  		shm.Attach(s.xc, xs, uint32(shmid), readOnly)
   242  		b.xs = xs
   243  	}
   244  
   245  	s.mu.Lock()
   246  	s.buffers[b.xs] = b
   247  	s.mu.Unlock()
   248  
   249  	return b, nil
   250  }
   251  
   252  func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) {
   253  	w, h := int64(size.X), int64(size.Y)
   254  	if w < 0 || maxShmSide < w || h < 0 || maxShmSide < h || maxShmSize < 4*w*h {
   255  		return nil, fmt.Errorf("x11driver: invalid texture size %v", size)
   256  	}
   257  	if w == 0 || h == 0 {
   258  		return &textureImpl{
   259  			s:    s,
   260  			size: size,
   261  		}, nil
   262  	}
   263  
   264  	xm, err := xproto.NewPixmapId(s.xc)
   265  	if err != nil {
   266  		return nil, fmt.Errorf("x11driver: xproto.NewPixmapId failed: %v", err)
   267  	}
   268  	xp, err := render.NewPictureId(s.xc)
   269  	if err != nil {
   270  		return nil, fmt.Errorf("x11driver: xproto.NewPictureId failed: %v", err)
   271  	}
   272  	xproto.CreatePixmap(s.xc, textureDepth, xm, xproto.Drawable(s.window32), uint16(w), uint16(h))
   273  	render.CreatePicture(s.xc, xp, xproto.Drawable(xm), s.pictformat32, render.CpRepeat, []uint32{render.RepeatPad})
   274  	render.SetPictureFilter(s.xc, xp, uint16(len("bilinear")), "bilinear", nil)
   275  	// The X11 server doesn't zero-initialize the pixmap. We do it ourselves.
   276  	render.FillRectangles(s.xc, render.PictOpSrc, xp, render.Color{}, []xproto.Rectangle{{
   277  		Width:  uint16(w),
   278  		Height: uint16(h),
   279  	}})
   280  
   281  	return &textureImpl{
   282  		s:    s,
   283  		size: size,
   284  		xm:   xm,
   285  		xp:   xp,
   286  	}, nil
   287  }
   288  
   289  func (s *screenImpl) NewWindow(opts *screen.NewWindowOptions) (screen.Window, error) {
   290  	width, height := 1024, 768
   291  	if opts != nil {
   292  		if opts.Width > 0 {
   293  			width = opts.Width
   294  		}
   295  		if opts.Height > 0 {
   296  			height = opts.Height
   297  		}
   298  	}
   299  
   300  	xw, err := xproto.NewWindowId(s.xc)
   301  	if err != nil {
   302  		return nil, fmt.Errorf("x11driver: xproto.NewWindowId failed: %v", err)
   303  	}
   304  	xg, err := xproto.NewGcontextId(s.xc)
   305  	if err != nil {
   306  		return nil, fmt.Errorf("x11driver: xproto.NewGcontextId failed: %v", err)
   307  	}
   308  	xp, err := render.NewPictureId(s.xc)
   309  	if err != nil {
   310  		return nil, fmt.Errorf("x11driver: render.NewPictureId failed: %v", err)
   311  	}
   312  	pictformat := render.Pictformat(0)
   313  	switch s.xsi.RootDepth {
   314  	default:
   315  		return nil, fmt.Errorf("x11driver: unsupported root depth %d", s.xsi.RootDepth)
   316  	case 24:
   317  		pictformat = s.pictformat24
   318  	case 32:
   319  		pictformat = s.pictformat32
   320  	}
   321  
   322  	w := &windowImpl{
   323  		s:       s,
   324  		xw:      xw,
   325  		xg:      xg,
   326  		xp:      xp,
   327  		xevents: make(chan xgb.Event),
   328  	}
   329  	if s.windowImp != nil {
   330  		panic("double it")
   331  	}
   332  	s.windowImp = w
   333  
   334  	screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageAlive}) // TODO(as)
   335  
   336  	xproto.CreateWindow(s.xc, s.xsi.RootDepth, xw, s.xsi.Root,
   337  		0, 0, uint16(width), uint16(height), 0,
   338  		xproto.WindowClassInputOutput, s.xsi.RootVisual,
   339  		xproto.CwEventMask,
   340  		[]uint32{0 |
   341  			xproto.EventMaskKeyPress |
   342  			xproto.EventMaskKeyRelease |
   343  			xproto.EventMaskButtonPress |
   344  			xproto.EventMaskButtonRelease |
   345  			xproto.EventMaskPointerMotion |
   346  			xproto.EventMaskExposure |
   347  			xproto.EventMaskStructureNotify |
   348  			xproto.EventMaskFocusChange,
   349  		},
   350  	)
   351  	s.setProperty(xw, s.atomWMProtocols, s.atomWMDeleteWindow, s.atomWMTakeFocus)
   352  
   353  	title := []byte(opts.GetTitle())
   354  	xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, s.atomNETWMName, s.atomUTF8String, 8, uint32(len(title)), title)
   355  
   356  	xproto.CreateGC(s.xc, xg, xproto.Drawable(xw), 0, nil)
   357  	render.CreatePicture(s.xc, xp, xproto.Drawable(xw), pictformat, 0, nil)
   358  	xproto.MapWindow(s.xc, xw)
   359  
   360  	return w, nil
   361  }
   362  
   363  func (s *screenImpl) initAtoms() (err error) {
   364  	s.atomNETWMName, err = s.internAtom("_NET_WM_NAME")
   365  	if err != nil {
   366  		return err
   367  	}
   368  	s.atomUTF8String, err = s.internAtom("UTF8_STRING")
   369  	if err != nil {
   370  		return err
   371  	}
   372  	s.atomWMDeleteWindow, err = s.internAtom("WM_DELETE_WINDOW")
   373  	if err != nil {
   374  		return err
   375  	}
   376  	s.atomWMProtocols, err = s.internAtom("WM_PROTOCOLS")
   377  	if err != nil {
   378  		return err
   379  	}
   380  	s.atomWMTakeFocus, err = s.internAtom("WM_TAKE_FOCUS")
   381  	if err != nil {
   382  		return err
   383  	}
   384  	return nil
   385  }
   386  
   387  func (s *screenImpl) internAtom(name string) (xproto.Atom, error) {
   388  	r, err := xproto.InternAtom(s.xc, false, uint16(len(name)), name).Reply()
   389  	if err != nil {
   390  		return 0, fmt.Errorf("x11driver: xproto.InternAtom failed: %v", err)
   391  	}
   392  	if r == nil {
   393  		return 0, fmt.Errorf("x11driver: xproto.InternAtom failed")
   394  	}
   395  	return r.Atom, nil
   396  }
   397  
   398  func (s *screenImpl) initKeyboardMapping() error {
   399  	const keyLo, keyHi = 8, 255
   400  	km, err := xproto.GetKeyboardMapping(s.xc, keyLo, keyHi-keyLo+1).Reply()
   401  	if err != nil {
   402  		return err
   403  	}
   404  	n := int(km.KeysymsPerKeycode)
   405  	if n < 2 {
   406  		return fmt.Errorf("x11driver: too few keysyms per keycode: %d", n)
   407  	}
   408  	for i := keyLo; i <= keyHi; i++ {
   409  		s.keysyms[i][0] = uint32(km.Keysyms[(i-keyLo)*n+0])
   410  		s.keysyms[i][1] = uint32(km.Keysyms[(i-keyLo)*n+1])
   411  	}
   412  	return nil
   413  }
   414  
   415  func (s *screenImpl) initPictformats() error {
   416  	pformats, err := render.QueryPictFormats(s.xc).Reply()
   417  	if err != nil {
   418  		return fmt.Errorf("x11driver: render.QueryPictFormats failed: %v", err)
   419  	}
   420  	s.pictformat24, err = findPictformat(pformats.Formats, 24)
   421  	if err != nil {
   422  		return err
   423  	}
   424  	s.pictformat32, err = findPictformat(pformats.Formats, 32)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	return nil
   429  }
   430  
   431  func findPictformat(fs []render.Pictforminfo, depth byte) (render.Pictformat, error) {
   432  	// This presumes little-endian BGRA.
   433  	want := render.Directformat{
   434  		RedShift:   16,
   435  		RedMask:    0xff,
   436  		GreenShift: 8,
   437  		GreenMask:  0xff,
   438  		BlueShift:  0,
   439  		BlueMask:   0xff,
   440  		AlphaShift: 24,
   441  		AlphaMask:  0xff,
   442  	}
   443  	if depth == 24 {
   444  		want.AlphaShift = 0
   445  		want.AlphaMask = 0x00
   446  	}
   447  	for _, f := range fs {
   448  		if f.Type == render.PictTypeDirect && f.Depth == depth && f.Direct == want {
   449  			return f.Id, nil
   450  		}
   451  	}
   452  	return 0, fmt.Errorf("x11driver: no matching Pictformat for depth %d", depth)
   453  }
   454  
   455  func (s *screenImpl) initWindow32() error {
   456  	visualid, err := findVisual(s.xsi, 32)
   457  	if err != nil {
   458  		return err
   459  	}
   460  	colormap, err := xproto.NewColormapId(s.xc)
   461  	if err != nil {
   462  		return fmt.Errorf("x11driver: xproto.NewColormapId failed: %v", err)
   463  	}
   464  	if err := xproto.CreateColormapChecked(
   465  		s.xc, xproto.ColormapAllocNone, colormap, s.xsi.Root, visualid).Check(); err != nil {
   466  		return fmt.Errorf("x11driver: xproto.CreateColormap failed: %v", err)
   467  	}
   468  	s.window32, err = xproto.NewWindowId(s.xc)
   469  	if err != nil {
   470  		return fmt.Errorf("x11driver: xproto.NewWindowId failed: %v", err)
   471  	}
   472  	s.gcontext32, err = xproto.NewGcontextId(s.xc)
   473  	if err != nil {
   474  		return fmt.Errorf("x11driver: xproto.NewGcontextId failed: %v", err)
   475  	}
   476  	const depth = 32
   477  	xproto.CreateWindow(s.xc, depth, s.window32, s.xsi.Root,
   478  		0, 0, 1, 1, 0,
   479  		xproto.WindowClassInputOutput, visualid,
   480  		// The CwBorderPixel attribute seems necessary for depth == 32. See
   481  		// http://stackoverflow.com/questions/3645632/how-to-create-a-window-with-a-bit-depth-of-32
   482  		xproto.CwBorderPixel|xproto.CwColormap,
   483  		[]uint32{0, uint32(colormap)},
   484  	)
   485  	xproto.CreateGC(s.xc, s.gcontext32, xproto.Drawable(s.window32), 0, nil)
   486  	return nil
   487  }
   488  
   489  func findVisual(xsi *xproto.ScreenInfo, depth byte) (xproto.Visualid, error) {
   490  	for _, d := range xsi.AllowedDepths {
   491  		if d.Depth != depth {
   492  			continue
   493  		}
   494  		for _, v := range d.Visuals {
   495  			if v.RedMask == 0xff0000 && v.GreenMask == 0xff00 && v.BlueMask == 0xff {
   496  				return v.VisualId, nil
   497  			}
   498  		}
   499  	}
   500  	return 0, fmt.Errorf("x11driver: no matching Visualid")
   501  }
   502  
   503  func (s *screenImpl) setProperty(xw xproto.Window, prop xproto.Atom, values ...xproto.Atom) {
   504  	b := make([]byte, len(values)*4)
   505  	for i, v := range values {
   506  		b[4*i+0] = uint8(v >> 0)
   507  		b[4*i+1] = uint8(v >> 8)
   508  		b[4*i+2] = uint8(v >> 16)
   509  		b[4*i+3] = uint8(v >> 24)
   510  	}
   511  	xproto.ChangeProperty(s.xc, xproto.PropModeReplace, xw, prop, xproto.AtomAtom, 32, uint32(len(values)), b)
   512  }
   513  
   514  func (s *screenImpl) drawUniform(xp render.Picture, src2dst *f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) {
   515  	if sr.Empty() {
   516  		return
   517  	}
   518  
   519  	if opts == nil && *src2dst == (f64.Aff3{1, 0, 0, 0, 1, 0}) {
   520  		fill(s.xc, xp, sr, src, op)
   521  		return
   522  	}
   523  
   524  	r, g, b, a := src.RGBA()
   525  	c := render.Color{
   526  		Red:   uint16(r),
   527  		Green: uint16(g),
   528  		Blue:  uint16(b),
   529  		Alpha: uint16(a),
   530  	}
   531  	points := trifanPoints(src2dst, sr)
   532  
   533  	s.uniformMu.Lock()
   534  	defer s.uniformMu.Unlock()
   535  
   536  	if s.uniformC != c {
   537  		s.uniformC = c
   538  		render.FreePicture(s.xc, s.uniformP)
   539  		render.CreateSolidFill(s.xc, s.uniformP, c)
   540  	}
   541  
   542  	if op == draw.Src {
   543  		// We implement draw.Src as render.PictOpOutReverse followed by
   544  		// render.PictOpOver, for the same reason as in textureImpl.draw.
   545  		render.TriFan(s.xc, render.PictOpOutReverse, s.opaqueP, xp, 0, 0, 0, points[:])
   546  	}
   547  	render.TriFan(s.xc, render.PictOpOver, s.uniformP, xp, 0, 0, 0, points[:])
   548  }