| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package x11driver |
| |
| // TODO: implement a back buffer. |
| |
| import ( |
| "image" |
| "image/color" |
| "image/draw" |
| "sync" |
| |
| "github.com/BurntSushi/xgb" |
| "github.com/BurntSushi/xgb/render" |
| "github.com/BurntSushi/xgb/xproto" |
| |
| "golang.org/x/exp/shiny/driver/internal/drawer" |
| "golang.org/x/exp/shiny/driver/internal/event" |
| "golang.org/x/exp/shiny/driver/internal/lifecycler" |
| "golang.org/x/exp/shiny/driver/internal/x11key" |
| "golang.org/x/exp/shiny/screen" |
| "golang.org/x/image/math/f64" |
| "golang.org/x/mobile/event/key" |
| "golang.org/x/mobile/event/mouse" |
| "golang.org/x/mobile/event/paint" |
| "golang.org/x/mobile/event/size" |
| "golang.org/x/mobile/geom" |
| ) |
| |
| type windowImpl struct { |
| s *screenImpl |
| |
| xw xproto.Window |
| xg xproto.Gcontext |
| xp render.Picture |
| |
| event.Deque |
| xevents chan xgb.Event |
| |
| // This next group of variables are mutable, but are only modified in the |
| // screenImpl.run goroutine. |
| width, height int |
| |
| lifecycler lifecycler.State |
| |
| mu sync.Mutex |
| released bool |
| } |
| |
| func (w *windowImpl) Release() { |
| w.mu.Lock() |
| released := w.released |
| w.released = true |
| w.mu.Unlock() |
| |
| // TODO: call w.lifecycler.SetDead and w.lifecycler.SendEvent, a la |
| // handling atomWMDeleteWindow? |
| |
| if released { |
| return |
| } |
| render.FreePicture(w.s.xc, w.xp) |
| xproto.FreeGC(w.s.xc, w.xg) |
| xproto.DestroyWindow(w.s.xc, w.xw) |
| } |
| |
| func (w *windowImpl) Upload(dp image.Point, src screen.Buffer, sr image.Rectangle) { |
| src.(bufferUploader).upload(xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr) |
| } |
| |
| func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { |
| fill(w.s.xc, w.xp, dr, src, op) |
| } |
| |
| func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { |
| w.s.drawUniform(w.xp, &src2dst, src, sr, op, opts) |
| } |
| |
| func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { |
| src.(*textureImpl).draw(w.xp, &src2dst, sr, op, opts) |
| } |
| |
| func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { |
| drawer.Copy(w, dp, src, sr, op, opts) |
| } |
| |
| func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op, opts *screen.DrawOptions) { |
| drawer.Scale(w, dr, src, sr, op, opts) |
| } |
| |
| func (w *windowImpl) Publish() screen.PublishResult { |
| // TODO: implement a back buffer, and copy or flip that here to the front |
| // buffer. |
| |
| // This sync isn't needed to flush the outgoing X11 requests. Instead, it |
| // acts as a form of flow control. Outgoing requests can be quite small on |
| // the wire, e.g. draw this texture ID (an integer) to this rectangle (four |
| // more integers), but much more expensive on the server (blending a |
| // million source and destination pixels). Without this sync, the Go X11 |
| // client could easily end up sending work at a faster rate than the X11 |
| // server can serve. |
| w.s.xc.Sync() |
| |
| return screen.PublishResult{} |
| } |
| |
| func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) { |
| // TODO: does the order of these lifecycle and size events matter? Should |
| // they really be a single, atomic event? |
| w.lifecycler.SetVisible((int(ev.X)+int(ev.Width)) > 0 && (int(ev.Y)+int(ev.Height)) > 0) |
| w.lifecycler.SendEvent(w, nil) |
| |
| newWidth, newHeight := int(ev.Width), int(ev.Height) |
| if w.width == newWidth && w.height == newHeight { |
| return |
| } |
| w.width, w.height = newWidth, newHeight |
| w.Send(size.Event{ |
| WidthPx: newWidth, |
| HeightPx: newHeight, |
| WidthPt: geom.Pt(newWidth), |
| HeightPt: geom.Pt(newHeight), |
| PixelsPerPt: w.s.pixelsPerPt, |
| }) |
| } |
| |
| func (w *windowImpl) handleExpose() { |
| w.Send(paint.Event{}) |
| } |
| |
| func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) { |
| r, c := w.s.keysyms.Lookup(uint8(detail), state) |
| w.Send(key.Event{ |
| Rune: r, |
| Code: c, |
| Modifiers: x11key.KeyModifiers(state), |
| Direction: dir, |
| }) |
| } |
| |
| func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) { |
| // TODO: should a mouse.Event have a separate MouseModifiers field, for |
| // which buttons are pressed during a mouse move? |
| btn := mouse.Button(b) |
| switch btn { |
| case 4: |
| btn = mouse.ButtonWheelUp |
| case 5: |
| btn = mouse.ButtonWheelDown |
| case 6: |
| btn = mouse.ButtonWheelLeft |
| case 7: |
| btn = mouse.ButtonWheelRight |
| } |
| if btn.IsWheel() { |
| if dir != mouse.DirPress { |
| return |
| } |
| dir = mouse.DirStep |
| } |
| w.Send(mouse.Event{ |
| X: float32(x), |
| Y: float32(y), |
| Button: btn, |
| Modifiers: x11key.KeyModifiers(state), |
| Direction: dir, |
| }) |
| } |