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 }