github.com/as/shiny@v0.8.2/driver/gldriver/x11.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  // +build linux,!android
     6  
     7  package gldriver
     8  
     9  /*
    10  #cgo LDFLAGS: -lEGL -lGLESv2 -lX11
    11  
    12  #include <stdbool.h>
    13  #include <stdint.h>
    14  #include <stdlib.h>
    15  
    16  char *eglGetErrorStr();
    17  void startDriver();
    18  void processEvents();
    19  void makeCurrent(uintptr_t ctx);
    20  void swapBuffers(uintptr_t ctx);
    21  void doCloseWindow(uintptr_t id);
    22  uintptr_t doNewWindow(int width, int height, char* title, int title_len);
    23  uintptr_t doShowWindow(uintptr_t id);
    24  uintptr_t surfaceCreate();
    25  */
    26  import "C"
    27  import (
    28  	"errors"
    29  	"runtime"
    30  	"time"
    31  	"unsafe"
    32  
    33  	"github.com/as/shiny/driver/internal/x11key"
    34  	"github.com/as/shiny/event/key"
    35  	"github.com/as/shiny/event/mouse"
    36  	"github.com/as/shiny/event/paint"
    37  	"github.com/as/shiny/event/size"
    38  	"github.com/as/shiny/geom"
    39  	"github.com/as/shiny/gl"
    40  	"github.com/as/shiny/screen"
    41  )
    42  
    43  const useLifecycler = true
    44  
    45  const handleSizeEventsAtChannelReceive = true
    46  
    47  var theKeysyms x11key.KeysymTable
    48  
    49  func init() {
    50  	// It might not be necessary, but it probably doesn't hurt to try to make
    51  	// 'the main thread' be 'the X11 / OpenGL thread'.
    52  	runtime.LockOSThread()
    53  }
    54  
    55  func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
    56  	width, height := optsSize(opts)
    57  
    58  	title := opts.GetTitle()
    59  	ctitle := C.CString(title)
    60  	defer C.free(unsafe.Pointer(ctitle))
    61  
    62  	retc := make(chan uintptr)
    63  	uic <- uiClosure{
    64  		f: func() uintptr {
    65  			return uintptr(C.doNewWindow(C.int(width), C.int(height), ctitle, C.int(len(title))))
    66  		},
    67  		retc: retc,
    68  	}
    69  	return <-retc, nil
    70  }
    71  
    72  func initWindow(w *windowImpl) {
    73  	w.glctx, w.worker = glctx, worker
    74  }
    75  
    76  func showWindow(w *windowImpl) {
    77  	retc := make(chan uintptr)
    78  	uic <- uiClosure{
    79  		f: func() uintptr {
    80  			return uintptr(C.doShowWindow(C.uintptr_t(w.id)))
    81  		},
    82  		retc: retc,
    83  	}
    84  	w.ctx = <-retc
    85  	go drawLoop(w)
    86  }
    87  
    88  func closeWindow(id uintptr) {
    89  	uic <- uiClosure{
    90  		f: func() uintptr {
    91  			C.doCloseWindow(C.uintptr_t(id))
    92  			return 0
    93  		},
    94  	}
    95  }
    96  
    97  func drawLoop(w *windowImpl) {
    98  	glcontextc <- w.ctx.(uintptr)
    99  	go func() {
   100  		for range w.publish {
   101  			publishc <- w
   102  		}
   103  	}()
   104  }
   105  
   106  var (
   107  	glcontextc = make(chan uintptr)
   108  	publishc   = make(chan *windowImpl)
   109  	uic        = make(chan uiClosure)
   110  
   111  	// TODO: don't assume that there is only one window, and hence only
   112  	// one (global) GL context.
   113  	//
   114  	// TODO: should we be able to make a shiny.Texture before having a
   115  	// shiny.Window's GL context? Should something like gl.IsProgram be a
   116  	// method instead of a function, and have each shiny.Window have its own
   117  	// gl.Context?
   118  	glctx  gl.Context
   119  	worker gl.Worker
   120  )
   121  
   122  // uiClosure is a closure to be run on C's UI thread.
   123  type uiClosure struct {
   124  	f    func() uintptr
   125  	retc chan uintptr
   126  }
   127  
   128  func main(f func(screen.Screen)) error {
   129  	if gl.Version() == "GL_ES_2_0" {
   130  		return errors.New("gldriver: ES 3 required on X11")
   131  	}
   132  	C.startDriver()
   133  	glctx, worker = gl.NewContext()
   134  
   135  	closec := make(chan struct{})
   136  	go func() {
   137  		f(theScreen)
   138  		close(closec)
   139  	}()
   140  
   141  	// heartbeat is a channel that, at regular intervals, directs the select
   142  	// below to also consider X11 events, not just Go events (channel
   143  	// communications).
   144  	//
   145  	// TODO: select instead of poll. Note that knowing whether to call
   146  	// C.processEvents needs to select on a file descriptor, and the other
   147  	// cases below select on Go channels.
   148  	heartbeat := time.NewTicker(time.Second / 60)
   149  	workAvailable := worker.WorkAvailable()
   150  
   151  	for {
   152  		select {
   153  		case <-closec:
   154  			return nil
   155  		case ctx := <-glcontextc:
   156  			// TODO: do we need to synchronize with seeing a size event for
   157  			// this window's context before or after calling makeCurrent?
   158  			// Otherwise, are we racing with the gl.Viewport call? I've
   159  			// occasionally seen a stale viewport, if the window manager sets
   160  			// the window width and height to something other than that
   161  			// requested by XCreateWindow, but it's not easily reproducible.
   162  			C.makeCurrent(C.uintptr_t(ctx))
   163  		case w := <-publishc:
   164  			C.swapBuffers(C.uintptr_t(w.ctx.(uintptr)))
   165  			w.publishDone <- screen.PublishResult{}
   166  		case req := <-uic:
   167  			ret := req.f()
   168  			if req.retc != nil {
   169  				req.retc <- ret
   170  			}
   171  		case <-heartbeat.C:
   172  			C.processEvents()
   173  		case <-workAvailable:
   174  			worker.DoWork()
   175  		}
   176  	}
   177  }
   178  
   179  //export onExpose
   180  func onExpose(id uintptr) {
   181  	theScreen.mu.Lock()
   182  	w := theScreen.windows[id]
   183  	theScreen.mu.Unlock()
   184  
   185  	if w == nil {
   186  		return
   187  	}
   188  
   189  	w.Send(paint.Event{External: true})
   190  }
   191  
   192  //export onKeysym
   193  func onKeysym(k, unshifted, shifted uint32) {
   194  	theKeysyms[k][0] = unshifted
   195  	theKeysyms[k][1] = shifted
   196  }
   197  
   198  //export onKey
   199  func onKey(id uintptr, state uint16, detail, dir uint8) {
   200  	theScreen.mu.Lock()
   201  	w := theScreen.windows[id]
   202  	theScreen.mu.Unlock()
   203  
   204  	if w == nil {
   205  		return
   206  	}
   207  
   208  	r, c := theKeysyms.Lookup(detail, state)
   209  	w.Send(key.Event{
   210  		Rune:      r,
   211  		Code:      c,
   212  		Modifiers: x11key.KeyModifiers(state),
   213  		Direction: key.Direction(dir),
   214  	})
   215  }
   216  
   217  //export onMouse
   218  func onMouse(id uintptr, x, y int32, state uint16, button, dir uint8) {
   219  	theScreen.mu.Lock()
   220  	w := theScreen.windows[id]
   221  	theScreen.mu.Unlock()
   222  
   223  	if w == nil {
   224  		return
   225  	}
   226  
   227  	// TODO: should a mouse.Event have a separate MouseModifiers field, for
   228  	// which buttons are pressed during a mouse move?
   229  	btn := mouse.Button(button)
   230  	switch btn {
   231  	case 4:
   232  		btn = mouse.ButtonWheelUp
   233  	case 5:
   234  		btn = mouse.ButtonWheelDown
   235  	case 6:
   236  		btn = mouse.ButtonWheelLeft
   237  	case 7:
   238  		btn = mouse.ButtonWheelRight
   239  	}
   240  	if btn.IsWheel() {
   241  		if dir != uint8(mouse.DirPress) {
   242  			return
   243  		}
   244  		dir = uint8(mouse.DirStep)
   245  	}
   246  	w.Send(mouse.Event{
   247  		X:         float32(x),
   248  		Y:         float32(y),
   249  		Button:    btn,
   250  		Modifiers: x11key.KeyModifiers(state),
   251  		Direction: mouse.Direction(dir),
   252  	})
   253  }
   254  
   255  //export onFocus
   256  func onFocus(id uintptr, focused bool) {
   257  	theScreen.mu.Lock()
   258  	w := theScreen.windows[id]
   259  	theScreen.mu.Unlock()
   260  
   261  	if w == nil {
   262  		return
   263  	}
   264  
   265  	w.lifecycler.SetFocused(focused)
   266  	w.lifecycler.SendEvent(w, w.glctx)
   267  }
   268  
   269  //export onConfigure
   270  func onConfigure(id uintptr, x, y, width, height, displayWidth, displayWidthMM int32) {
   271  	theScreen.mu.Lock()
   272  	w := theScreen.windows[id]
   273  	theScreen.mu.Unlock()
   274  
   275  	if w == nil {
   276  		return
   277  	}
   278  
   279  	w.lifecycler.SetVisible(x+width > 0 && y+height > 0)
   280  	w.lifecycler.SendEvent(w, w.glctx)
   281  
   282  	const (
   283  		mmPerInch = 25.4
   284  		ptPerInch = 72
   285  	)
   286  	pixelsPerMM := float32(displayWidth) / float32(displayWidthMM)
   287  	w.Send(size.Event{
   288  		WidthPx:     int(width),
   289  		HeightPx:    int(height),
   290  		WidthPt:     geom.Pt(width),
   291  		HeightPt:    geom.Pt(height),
   292  		PixelsPerPt: pixelsPerMM * mmPerInch / ptPerInch,
   293  	})
   294  }
   295  
   296  //export onDeleteWindow
   297  func onDeleteWindow(id uintptr) {
   298  	theScreen.mu.Lock()
   299  	w := theScreen.windows[id]
   300  	theScreen.mu.Unlock()
   301  
   302  	if w == nil {
   303  		return
   304  	}
   305  
   306  	w.lifecycler.SetDead(true)
   307  	w.lifecycler.SendEvent(w, w.glctx)
   308  }
   309  
   310  func surfaceCreate() error {
   311  	if C.surfaceCreate() == 0 {
   312  		return errors.New("gldriver: surface creation failed")
   313  	}
   314  	return nil
   315  }