twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

x11.go (7053B)


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