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