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