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 }