~eliasnaur/gio

ui/app: (wayland) return a specific error value when connection fails v1 PROPOSED

Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio/%3C20190907173426.25682-1-denis.smirnov.91%40gmail.com%3E/mbox | git am -3
Learn more about email & git

[PATCH 1/6] ui/app: (wayland) return a specific error value when connection fails Export this patch

Denys Smirnov
Returning specific error will help the high-level code fallback from
Wayland to X11 code path.

Signed-off-by: Denys Smirnov <denis.smirnov.91@gmail.com>
---
 ui/app/os_wayland.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go
index 571e8f2..5f8d654 100644
--- a/ui/app/os_wayland.go
+++ b/ui/app/os_wayland.go
@@ -54,6 +54,8 @@ import (
 */
 import "C"
 
+var errWLDisplayConnectFailed = errors.New("wayland: wl_display_connect failed")
+
 type wlConn struct {
 	disp         *C.struct_wl_display
 	compositor   *C.struct_wl_compositor
@@ -1110,7 +1112,7 @@ func waylandConnect() error {
 	c.disp = C.wl_display_connect(nil)
 	if c.disp == nil {
 		c.destroy()
-		return errors.New("wayland: wl_display_connect failed")
+		return errWLDisplayConnectFailed
 	}
 	reg := C.wl_display_get_registry(c.disp)
 	if reg == nil {
-- 
2.20.1

[PATCH 2/6] ui/app: (wayland) refactor code path to allow X11 fallback Export this patch

Denys Smirnov
Move all Wayland related functionality into a wlWindow and wlEGLWindow.
Let original window and eglWindow proxy calls to those types.

Signed-off-by: Denys Smirnov <denis.smirnov.91@gmail.com>
---
 ui/app/egl.go         |  5 ++--
 ui/app/egl_linux.go   | 23 ++++++++++++++++++
 ui/app/egl_wayland.go | 16 ++++++-------
 ui/app/os_linux.go    | 35 ++++++++++++++++++++++++++++
 ui/app/os_wayland.go  | 54 ++++++++++++++++++++-----------------------
 5 files changed, 94 insertions(+), 39 deletions(-)
 create mode 100644 ui/app/os_linux.go

diff --git a/ui/app/egl.go b/ui/app/egl.go
index ee07993..6c448d9 100644
--- a/ui/app/egl.go
+++ b/ui/app/egl.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"runtime"
 	"strings"
+	"unsafe"
 
 	"gioui.org/ui/app/internal/gl"
 )
@@ -148,14 +149,14 @@ func (c *context) MakeCurrent() error {
 	}
 	if c.eglWin == nil {
 		var err error
-		c.eglWin, err = newEGLWindow(win, width, height)
+		c.eglWin, err = c.driver.newEGLWindow(unsafe.Pointer(win), width, height)
 		if err != nil {
 			return err
 		}
 	} else {
 		c.eglWin.resize(width, height)
 	}
-	eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, c.eglWin.window())
+	eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, _EGLNativeWindowType(c.eglWin.window()))
 	c.eglSurf = eglSurf
 	if err != nil {
 		c.eglWin.destroy()
diff --git a/ui/app/egl_linux.go b/ui/app/egl_linux.go
index 051c229..80bc168 100644
--- a/ui/app/egl_linux.go
+++ b/ui/app/egl_linux.go
@@ -11,6 +11,9 @@ package app
 #include <GLES3/gl3.h>
 */
 import "C"
+import (
+	"unsafe"
+)
 
 type (
 	_EGLint     = C.EGLint
@@ -81,3 +84,23 @@ func eglTerminate(disp _EGLDisplay) bool {
 func eglQueryString(disp _EGLDisplay, name _EGLint) string {
 	return C.GoString(C.eglQueryString(disp, name))
 }
+
+type eglWindow struct {
+	wl *wlEGLWindow
+}
+
+func (w *window) newEGLWindow(ew unsafe.Pointer, width, height int) (*eglWindow, error) {
+	return w.wl.newEGLWindow(ew, width, height)
+}
+
+func (w *eglWindow) window() unsafe.Pointer {
+	return w.wl.window()
+}
+
+func (w *eglWindow) resize(width, height int) {
+	w.wl.resize(width, height)
+}
+
+func (w *eglWindow) destroy() {
+	w.wl.destroy()
+}
diff --git a/ui/app/egl_wayland.go b/ui/app/egl_wayland.go
index d8fc4a5..a982ede 100644
--- a/ui/app/egl_wayland.go
+++ b/ui/app/egl_wayland.go
@@ -24,28 +24,28 @@ type (
 	_EGLNativeWindowType  = C.EGLNativeWindowType
 )
 
-type eglWindow struct {
+type wlEGLWindow struct {
 	w *C.struct_wl_egl_window
 }
 
-func newEGLWindow(w _EGLNativeWindowType, width, height int) (*eglWindow, error) {
-	surf := (*C.struct_wl_surface)(unsafe.Pointer(w))
+func (w *wlWindow) newEGLWindow(ew unsafe.Pointer, width, height int) (*eglWindow, error) {
+	surf := (*C.struct_wl_surface)(ew)
 	win := C.wl_egl_window_create(surf, C.int(width), C.int(height))
 	if win == nil {
 		return nil, errors.New("wl_egl_create_window failed")
 	}
-	return &eglWindow{win}, nil
+	return &eglWindow{wl: &wlEGLWindow{w: win}}, nil
 }
 
-func (w *eglWindow) window() _EGLNativeWindowType {
-	return w.w
+func (w *wlEGLWindow) window() unsafe.Pointer {
+	return unsafe.Pointer(w.w)
 }
 
-func (w *eglWindow) resize(width, height int) {
+func (w *wlEGLWindow) resize(width, height int) {
 	C.wl_egl_window_resize(w.w, C.int(width), C.int(height), 0, 0)
 }
 
-func (w *eglWindow) destroy() {
+func (w *wlEGLWindow) destroy() {
 	C.wl_egl_window_destroy(w.w)
 }
 
diff --git a/ui/app/os_linux.go b/ui/app/os_linux.go
new file mode 100644
index 0000000..3c37d2e
--- /dev/null
+++ b/ui/app/os_linux.go
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build linux,!android
+
+package app
+
+import "unsafe"
+
+func main() {
+	<-mainDone
+}
+
+func createWindow(window *Window, opts *windowOptions) error {
+	return createWindowWayland(window, opts)
+}
+
+type window struct {
+	wl *wlWindow
+}
+
+func (w *window) setAnimating(anim bool) {
+	w.wl.setAnimating(anim)
+}
+
+func (w *window) display() unsafe.Pointer {
+	return w.wl.display()
+}
+
+func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
+	return w.wl.nativeWindow(visID)
+}
+
+func (w *window) showTextInput(show bool) {
+	w.wl.showTextInput(show)
+}
diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go
index 5f8d654..66b9df7 100644
--- a/ui/app/os_wayland.go
+++ b/ui/app/os_wayland.go
@@ -87,7 +87,7 @@ type repeatState struct {
 	delay time.Duration
 
 	key   C.uint32_t
-	win   *window
+	win   *wlWindow
 	stopC chan struct{}
 
 	start time.Duration
@@ -96,7 +96,7 @@ type repeatState struct {
 	now   time.Duration
 }
 
-type window struct {
+type wlWindow struct {
 	w      *Window
 	disp   *C.struct_wl_display
 	surf   *C.struct_wl_surface
@@ -136,7 +136,7 @@ type wlOutput struct {
 	physHeight int
 	transform  C.int32_t
 	scale      int
-	windows    []*window
+	windows    []*wlWindow
 }
 
 var connMu sync.Mutex
@@ -144,7 +144,7 @@ var conn *wlConn
 var mainDone = make(chan struct{})
 
 var (
-	winMap       = make(map[interface{}]*window)
+	winMap       = make(map[interface{}]*wlWindow)
 	outputMap    = make(map[C.uint32_t]*C.struct_wl_output)
 	outputConfig = make(map[*C.struct_wl_output]*wlOutput)
 )
@@ -154,11 +154,7 @@ var (
 	_XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
 )
 
-func main() {
-	<-mainDone
-}
-
-func createWindow(window *Window, opts *windowOptions) error {
+func createWindowWayland(win *Window, opts *windowOptions) error {
 	connMu.Lock()
 	defer connMu.Unlock()
 	if len(winMap) > 0 {
@@ -172,9 +168,9 @@ func createWindow(window *Window, opts *windowOptions) error {
 		conn.destroy()
 		return err
 	}
-	w.w = window
+	w.w = win
 	go func() {
-		w.w.setDriver(w)
+		w.w.setDriver(&window{wl: w})
 		w.setStage(StageRunning)
 		w.loop()
 		w.destroy()
@@ -184,7 +180,7 @@ func createWindow(window *Window, opts *windowOptions) error {
 	return nil
 }
 
-func createNativeWindow(opts *windowOptions) (*window, error) {
+func createNativeWindow(opts *windowOptions) (*wlWindow, error) {
 	pipe := make([]int, 2)
 	if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
 		return nil, fmt.Errorf("createNativeWindow: failed to create pipe: %v", err)
@@ -211,7 +207,7 @@ func createNativeWindow(opts *windowOptions) (*window, error) {
 		ppsp = minDensity
 	}
 
-	w := &window{
+	w := &wlWindow{
 		disp:     conn.disp,
 		scale:    scale,
 		newScale: scale != 1,
@@ -675,7 +671,7 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
 	}
 }
 
-func (r *repeatState) Start(w *window, keyCode C.uint32_t, t time.Duration) {
+func (r *repeatState) Start(w *wlWindow, keyCode C.uint32_t, t time.Duration) {
 	if r.rate <= 0 {
 		return
 	}
@@ -756,7 +752,7 @@ func gio_onFrameDone(data unsafe.Pointer, callback *C.struct_wl_callback, t C.ui
 	}
 }
 
-func (w *window) loop() {
+func (w *wlWindow) loop() {
 	dispfd := C.wl_display_get_fd(conn.disp)
 	// Poll for events and notifications.
 	pollfds := []syscall.PollFd{
@@ -809,7 +805,7 @@ loop:
 	}
 }
 
-func (w *window) setAnimating(anim bool) {
+func (w *wlWindow) setAnimating(anim bool) {
 	w.mu.Lock()
 	w.animating = anim
 	w.mu.Unlock()
@@ -819,14 +815,14 @@ func (w *window) setAnimating(anim bool) {
 }
 
 // Wakeup wakes up the event loop through the notification pipe.
-func (w *window) notify() {
+func (w *wlWindow) notify() {
 	oneByte := make([]byte, 1)
 	if _, err := syscall.Write(w.notWrite, oneByte); err != nil && err != syscall.EAGAIN {
 		panic(fmt.Errorf("failed to write to pipe: %v", err))
 	}
 }
 
-func (w *window) destroy() {
+func (w *wlWindow) destroy() {
 	if w.notWrite != 0 {
 		syscall.Close(w.notWrite)
 		w.notWrite = 0
@@ -852,7 +848,7 @@ func (w *window) destroy() {
 	}
 }
 
-func (w *window) dispatchKey(keyCode C.uint32_t) {
+func (w *wlWindow) dispatchKey(keyCode C.uint32_t) {
 	if len(conn.utf8Buf) == 0 {
 		conn.utf8Buf = make([]byte, 1)
 	}
@@ -955,7 +951,7 @@ func (c *wlOutput) ppmm() (float32, error) {
 	return density, nil
 }
 
-func (w *window) flushScroll() {
+func (w *wlWindow) flushScroll() {
 	if w.scroll == (f32.Point{}) {
 		return
 	}
@@ -981,7 +977,7 @@ func (w *window) flushScroll() {
 	w.discScroll.y = 0
 }
 
-func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
+func (w *wlWindow) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
 	w.flushScroll()
 	w.lastPos = f32.Point{X: fromFixed(x), Y: fromFixed(y)}
 	w.w.event(pointer.Event{
@@ -992,14 +988,14 @@ func (w *window) onPointerMotion(x, y C.wl_fixed_t, t C.uint32_t) {
 	})
 }
 
-func (w *window) updateOpaqueRegion() {
+func (w *wlWindow) updateOpaqueRegion() {
 	reg := C.wl_compositor_create_region(conn.compositor)
 	C.wl_region_add(reg, 0, 0, C.int32_t(w.width), C.int32_t(w.height))
 	C.wl_surface_set_opaque_region(w.surf, reg)
 	C.wl_region_destroy(reg)
 }
 
-func (w *window) updateOutputs() {
+func (w *wlWindow) updateOutputs() {
 	scale := 1
 	var found bool
 	for _, conf := range outputConfig {
@@ -1026,7 +1022,7 @@ func (w *window) updateOutputs() {
 	}
 }
 
-func (w *window) config() (int, int, Config) {
+func (w *wlWindow) config() (int, int, Config) {
 	width, height := w.width*w.scale, w.height*w.scale
 	return width, height, Config{
 		pxPerDp: w.ppdp * float32(w.scale),
@@ -1034,7 +1030,7 @@ func (w *window) config() (int, int, Config) {
 	}
 }
 
-func (w *window) draw(sync bool) {
+func (w *wlWindow) draw(sync bool) {
 	w.mu.Lock()
 	animating := w.animating
 	dead := w.dead
@@ -1062,7 +1058,7 @@ func (w *window) draw(sync bool) {
 	})
 }
 
-func (w *window) setStage(s Stage) {
+func (w *wlWindow) setStage(s Stage) {
 	if s == w.stage {
 		return
 	}
@@ -1070,11 +1066,11 @@ func (w *window) setStage(s Stage) {
 	w.w.event(StageEvent{s})
 }
 
-func (w *window) display() unsafe.Pointer {
+func (w *wlWindow) display() unsafe.Pointer {
 	return unsafe.Pointer(w.disp)
 }
 
-func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
+func (w *wlWindow) nativeWindow(visID int) (unsafe.Pointer, int, int) {
 	w.mu.Lock()
 	defer w.mu.Unlock()
 	if w.needAck {
@@ -1089,7 +1085,7 @@ func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
 	return unsafe.Pointer(w.surf), width * scale, height * scale
 }
 
-func (w *window) showTextInput(show bool) {}
+func (w *wlWindow) showTextInput(show bool) {}
 
 // detectFontScale reports current font scale, or 1.0
 // if it fails.
-- 
2.20.1

[PATCH 3/6] ui/app: (x11) initial implementation of X11 backend Export this patch

Denys Smirnov
When connection to Wayland fails, fallback to X11. Initial X11
implementation supports rendering, resize and mouse events. Keyboard
events, animations and some other things are missing.

Xlib is used for the communication with X server, because it seems to be
impossible to get eglDisplay with a pure Go implementation.

Signed-off-by: Denys Smirnov <denis.smirnov.91@gmail.com>
---
 ui/app/egl_linux.go |  25 +++-
 ui/app/egl_x11.go   |  37 ++++++
 ui/app/os_linux.go  |  31 ++++-
 ui/app/os_x11.go    | 316 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 398 insertions(+), 11 deletions(-)
 create mode 100644 ui/app/egl_x11.go
 create mode 100644 ui/app/os_x11.go

diff --git a/ui/app/egl_linux.go b/ui/app/egl_linux.go
index 80bc168..1cd429d 100644
--- a/ui/app/egl_linux.go
+++ b/ui/app/egl_linux.go
@@ -86,21 +86,36 @@ func eglQueryString(disp _EGLDisplay, name _EGLint) string {
 }
 
 type eglWindow struct {
-	wl *wlEGLWindow
+	x11 *x11EGLWindow
+	wl  *wlEGLWindow
 }
 
 func (w *window) newEGLWindow(ew unsafe.Pointer, width, height int) (*eglWindow, error) {
-	return w.wl.newEGLWindow(ew, width, height)
+	if w.wl != nil {
+		return w.wl.newEGLWindow(ew, width, height)
+	}
+	return w.x11.newEGLWindow(ew, width, height)
 }
 
 func (w *eglWindow) window() unsafe.Pointer {
-	return w.wl.window()
+	if w.wl != nil {
+		return w.wl.window()
+	}
+	return w.x11.window()
 }
 
 func (w *eglWindow) resize(width, height int) {
-	w.wl.resize(width, height)
+	if w.wl != nil {
+		w.wl.resize(width, height)
+	} else {
+		w.x11.resize(width, height)
+	}
 }
 
 func (w *eglWindow) destroy() {
-	w.wl.destroy()
+	if w.wl != nil {
+		w.wl.destroy()
+	} else {
+		w.x11.destroy()
+	}
 }
diff --git a/ui/app/egl_x11.go b/ui/app/egl_x11.go
new file mode 100644
index 0000000..5026492
--- /dev/null
+++ b/ui/app/egl_x11.go
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build linux,!android
+
+package app
+
+/*
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+*/
+import "C"
+import "unsafe"
+
+type x11EGLWindow struct {
+	w  *x11Window
+	xw unsafe.Pointer
+}
+
+func (w *x11Window) newEGLWindow(xw unsafe.Pointer, width, height int) (*eglWindow, error) {
+	return &eglWindow{x11: &x11EGLWindow{w: w, xw: xw}}, nil
+}
+
+func (w *x11EGLWindow) window() unsafe.Pointer {
+	return w.xw
+}
+
+func (w *x11EGLWindow) resize(width, height int) {
+	var change C.XWindowChanges
+	change.width = C.int(width)
+	change.height = C.int(height)
+	C.XConfigureWindow(w.w.x, w.w.xw, C.CWWidth|C.CWHeight, &change)
+}
+
+func (w *x11EGLWindow) destroy() {
+	// destroyed by x11Window.destroy
+}
diff --git a/ui/app/os_linux.go b/ui/app/os_linux.go
index 3c37d2e..f644db8 100644
--- a/ui/app/os_linux.go
+++ b/ui/app/os_linux.go
@@ -11,25 +11,44 @@ func main() {
 }
 
 func createWindow(window *Window, opts *windowOptions) error {
-	return createWindowWayland(window, opts)
+	err := createWindowWayland(window, opts)
+	if err == errWLDisplayConnectFailed {
+		return createWindowX11(window, opts)
+	}
+	return err
 }
 
 type window struct {
-	wl *wlWindow
+	x11 *x11Window
+	wl  *wlWindow
 }
 
 func (w *window) setAnimating(anim bool) {
-	w.wl.setAnimating(anim)
+	if w.wl != nil {
+		w.wl.setAnimating(anim)
+	} else {
+		w.x11.setAnimating(anim)
+	}
 }
 
 func (w *window) display() unsafe.Pointer {
-	return w.wl.display()
+	if w.wl != nil {
+		return w.wl.display()
+	}
+	return w.x11.display()
 }
 
 func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
-	return w.wl.nativeWindow(visID)
+	if w.wl != nil {
+		return w.wl.nativeWindow(visID)
+	}
+	return w.x11.nativeWindow(visID)
 }
 
 func (w *window) showTextInput(show bool) {
-	w.wl.showTextInput(show)
+	if w.wl != nil {
+		w.wl.showTextInput(show)
+	} else {
+		w.x11.showTextInput(show)
+	}
 }
diff --git a/ui/app/os_x11.go b/ui/app/os_x11.go
new file mode 100644
index 0000000..3bbea33
--- /dev/null
+++ b/ui/app/os_x11.go
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build linux,!android
+
+package app
+
+/*
+#cgo LDFLAGS: -lX11
+
+#include <stdlib.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+
+#define GIO_FIELD_OFFSET(typ, field) const int gio_##typ##_##field##_off = offsetof(typ, field)
+
+GIO_FIELD_OFFSET(XClientMessageEvent, data);
+GIO_FIELD_OFFSET(XConfigureEvent, width);
+GIO_FIELD_OFFSET(XConfigureEvent, height);
+GIO_FIELD_OFFSET(XButtonEvent, x);
+GIO_FIELD_OFFSET(XButtonEvent, y);
+GIO_FIELD_OFFSET(XButtonEvent, state);
+GIO_FIELD_OFFSET(XButtonEvent, button);
+GIO_FIELD_OFFSET(XButtonEvent, time);
+GIO_FIELD_OFFSET(XMotionEvent, x);
+GIO_FIELD_OFFSET(XMotionEvent, y);
+GIO_FIELD_OFFSET(XMotionEvent, time);
+*/
+import "C"
+import (
+	"errors"
+	"image"
+	"sync"
+	"time"
+	"unsafe"
+
+	"gioui.org/ui/f32"
+	"gioui.org/ui/pointer"
+)
+
+type x11Window struct {
+	w  *Window
+	x  *C.Display
+	xw C.Window
+
+	evDelWindow C.Atom
+
+	width  int
+	height int
+	cfg    Config
+}
+
+func (w *x11Window) setAnimating(anim bool) {
+	// TODO(dennwc): implement animation state
+}
+
+func (w *x11Window) showTextInput(show bool) {}
+
+func (w *x11Window) display() unsafe.Pointer {
+	// TODO(dennwc): We have an awesome X library written in pure Go, however,
+	//               we can't use it because of this specific function.
+	//               The *C.Display pointer is required to call eglGetDisplay,
+	//               so we can't really implement the call in pure Go.
+	//               Thus, we have to use Xlib for everything.
+	return unsafe.Pointer(w.x)
+}
+
+func (w *x11Window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
+	return unsafe.Pointer(uintptr(w.xw)), w.width, w.height
+}
+
+func (w *x11Window) setStage(s Stage) {
+	w.w.event(StageEvent{s})
+}
+
+func (w *x11Window) loop() {
+	for {
+		var xev xEvent
+		C.XNextEvent(w.x, (*C.XEvent)(unsafe.Pointer(&xev)))
+		switch xev.Type {
+		case C.KeyPress:
+			// TODO(dennwc): keyboard press
+		case C.KeyRelease:
+			// TODO(dennwc): keyboard release
+		case C.ButtonPress, C.ButtonRelease:
+			ev := pointer.Event{
+				Type:   pointer.Press,
+				Source: pointer.Mouse,
+				Position: f32.Point{
+					X: float32(xev.GetButtonX()),
+					Y: float32(xev.GetButtonY()),
+				},
+				Time: xev.GetButtonTime(),
+			}
+			if xev.Type == C.ButtonRelease {
+				ev.Type = pointer.Release
+			}
+			const scrollScale = 10
+			switch xev.GetButtonButton() {
+			case C.Button1:
+				// left click by default
+			case C.Button4:
+				// scroll up
+				ev.Type = pointer.Move
+				ev.Scroll.Y = -scrollScale
+			case C.Button5:
+				// scroll down
+				ev.Type = pointer.Move
+				ev.Scroll.Y = +scrollScale
+			default:
+				continue
+			}
+			w.w.event(ev)
+			w.draw()
+		case C.MotionNotify:
+			w.w.event(pointer.Event{
+				Type:   pointer.Move,
+				Source: pointer.Mouse,
+				Position: f32.Point{
+					X: float32(xev.GetMotionX()),
+					Y: float32(xev.GetMotionY()),
+				},
+				Time: xev.GetMotionTime(),
+			})
+			w.draw()
+		case C.Expose: // update
+			w.draw()
+		case C.ConfigureNotify: // window configuration change
+			oldW, oldH := w.width, w.height
+			w.width = int(xev.GetConfigureWidth())
+			w.height = int(xev.GetConfigureHeight())
+			if oldW != w.width || oldH != w.height {
+				w.draw()
+			}
+		case C.ClientMessage: // extensions
+			switch xev.GetClientDataLong()[0] {
+			case C.long(w.evDelWindow):
+				return
+			}
+		}
+	}
+}
+
+func (w *x11Window) destroy() {
+	C.XDestroyWindow(w.x, w.xw)
+	C.XCloseDisplay(w.x)
+}
+
+func (w *x11Window) draw() {
+	w.w.event(UpdateEvent{
+		Size: image.Point{
+			X: w.width,
+			Y: w.height,
+		},
+		Config: w.cfg,
+		sync:   false,
+	})
+}
+
+const xEventSize = unsafe.Sizeof(C.XEvent{})
+
+// Make sure the Go struct has the same size.
+// We can't use C.XEvent directly because it's a union.
+var _ = [1]struct{}{}[unsafe.Sizeof(xEvent{})-xEventSize]
+
+type xEvent struct {
+	Type C.int
+	Data [xEventSize - unsafe.Sizeof(C.int(0))]byte
+}
+
+func (e *xEvent) getInt(off int) C.int {
+	return *(*C.int)(unsafe.Pointer(uintptr(unsafe.Pointer(e)) + uintptr(off)))
+}
+
+func (e *xEvent) getUint(off int) C.uint {
+	return *(*C.uint)(unsafe.Pointer(uintptr(unsafe.Pointer(e)) + uintptr(off)))
+}
+
+func (e *xEvent) getUlong(off int) C.ulong {
+	return *(*C.ulong)(unsafe.Pointer(uintptr(unsafe.Pointer(e)) + uintptr(off)))
+}
+
+func (e *xEvent) getUlongMs(off int) time.Duration {
+	return time.Duration(e.getUlong(off)) * time.Millisecond
+}
+
+// GetConfigureWidth returns a XEvent.xconfigure.width field.
+func (e *xEvent) GetConfigureWidth() C.int {
+	return e.getInt(int(C.gio_XConfigureEvent_width_off))
+}
+
+// GetConfigureWidth returns a XEvent.xconfigure.height field.
+func (e *xEvent) GetConfigureHeight() C.int {
+	return e.getInt(int(C.gio_XConfigureEvent_height_off))
+}
+
+// GetButtonX returns a XEvent.xbutton.x field.
+func (e *xEvent) GetButtonX() C.int {
+	return e.getInt(int(C.gio_XButtonEvent_x_off))
+}
+
+// GetButtonY returns a XEvent.xbutton.y field.
+func (e *xEvent) GetButtonY() C.int {
+	return e.getInt(int(C.gio_XButtonEvent_y_off))
+}
+
+// GetButtonState returns a XEvent.xbutton.state field.
+func (e *xEvent) GetButtonState() C.uint {
+	return e.getUint(int(C.gio_XButtonEvent_state_off))
+}
+
+// GetButtonButton returns a XEvent.xbutton.button field.
+func (e *xEvent) GetButtonButton() C.uint {
+	return e.getUint(int(C.gio_XButtonEvent_button_off))
+}
+
+// GetButtonTime returns a XEvent.xbutton.time field.
+func (e *xEvent) GetButtonTime() time.Duration {
+	return e.getUlongMs(int(C.gio_XButtonEvent_time_off))
+}
+
+// GetMotionX returns a XEvent.xmotion.x field.
+func (e *xEvent) GetMotionX() C.int {
+	return e.getInt(int(C.gio_XMotionEvent_x_off))
+}
+
+// GetMotionY returns a XEvent.xmotion.y field.
+func (e *xEvent) GetMotionY() C.int {
+	return e.getInt(int(C.gio_XMotionEvent_y_off))
+}
+
+// GetMotionTime returns a XEvent.xmotion.time field.
+func (e *xEvent) GetMotionTime() time.Duration {
+	return e.getUlongMs(int(C.gio_XMotionEvent_time_off))
+}
+
+// GetClientDataLong returns a XEvent.xclient.data.l field.
+func (e *xEvent) GetClientDataLong() [5]C.long {
+	ptr := (*[5]C.long)(unsafe.Pointer(uintptr(unsafe.Pointer(e)) + uintptr(C.gio_XClientMessageEvent_data_off)))
+	return *ptr
+}
+
+var (
+	x11Threads sync.Once
+)
+
+func createWindowX11(w *Window, opts *windowOptions) error {
+	var err error
+	x11Threads.Do(func() {
+		if C.XInitThreads() == 0 {
+			err = errors.New("x11: threads init failed")
+		}
+	})
+	if err != nil {
+		return err
+	}
+	disp := C.XOpenDisplay(nil)
+	if disp == nil {
+		return errors.New("x11: cannot connect to the X server")
+	}
+	root := C.XDefaultRootWindow(disp)
+
+	var swa C.XSetWindowAttributes
+	swa.event_mask = C.ExposureMask | // update
+		C.KeyPressMask | C.KeyReleaseMask | // keyboard
+		C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks
+		C.PointerMotionMask | // mouse movement
+		C.StructureNotifyMask // resize
+
+	cfg := Config{pxPerDp: 1, pxPerSp: 1} // TODO(dennwc): real config
+	win := C.XCreateWindow(disp, root,
+		0, 0, C.uint(cfg.Px(opts.Width)), C.uint(cfg.Px(opts.Height)), 0,
+		C.CopyFromParent, C.InputOutput,
+		nil, C.CWEventMask|C.CWBackPixel,
+		&swa,
+	)
+
+	xw := &x11Window{
+		w: w, x: disp, xw: win,
+		width:  cfg.Px(opts.Width),
+		height: cfg.Px(opts.Height),
+		cfg:    cfg,
+	}
+
+	var xattr C.XSetWindowAttributes
+	xattr.override_redirect = C.False
+	C.XChangeWindowAttributes(disp, win, C.CWOverrideRedirect, &xattr)
+
+	var hints C.XWMHints
+	hints.input = C.True
+	hints.flags = C.InputHint
+	C.XSetWMHints(disp, win, &hints)
+
+	// make the window visible on the screen
+	C.XMapWindow(disp, win)
+
+	// set the name
+	ctitle := C.CString(opts.Title)
+	C.XStoreName(disp, win, ctitle)
+	C.free(unsafe.Pointer(ctitle))
+
+	// extensions
+	ckey := C.CString("WM_DELETE_WINDOW")
+	xw.evDelWindow = C.XInternAtom(disp, ckey, C.False)
+	C.free(unsafe.Pointer(ckey))
+	C.XSetWMProtocols(disp, win, &xw.evDelWindow, 1)
+
+	go func() {
+		xw.w.setDriver(&window{x11: xw})
+		xw.setStage(StageRunning)
+		xw.loop()
+		xw.destroy()
+		close(mainDone)
+	}()
+	return nil
+}
-- 
2.20.1

[PATCH 4/6] ui/app: (x11) initial work to enable XKB in X11 Export this patch

Denys Smirnov
Query XKB information when opening an X display and add helpers to check
XKB event type.

Signed-off-by: Denys Smirnov <denis.smirnov.91@gmail.com>
---
 ui/app/os_x11.go | 68 ++++++++++++++++++++++++++++++++++--------------
 1 file changed, 49 insertions(+), 19 deletions(-)

diff --git a/ui/app/os_x11.go b/ui/app/os_x11.go
index 3bbea33..5b73d1e 100644
--- a/ui/app/os_x11.go
+++ b/ui/app/os_x11.go
@@ -11,6 +11,7 @@ package app
 #include <X11/Xlib.h>
 #include <X11/Xatom.h>
 #include <X11/Xutil.h>
+#include <X11/XKBlib.h>
 
 #define GIO_FIELD_OFFSET(typ, field) const int gio_##typ##_##field##_off = offsetof(typ, field)
 
@@ -25,6 +26,12 @@ GIO_FIELD_OFFSET(XButtonEvent, time);
 GIO_FIELD_OFFSET(XMotionEvent, x);
 GIO_FIELD_OFFSET(XMotionEvent, y);
 GIO_FIELD_OFFSET(XMotionEvent, time);
+GIO_FIELD_OFFSET(XkbAnyEvent, xkb_type);
+GIO_FIELD_OFFSET(XkbAnyEvent, time);
+GIO_FIELD_OFFSET(XkbStateNotifyEvent, keycode);
+GIO_FIELD_OFFSET(XkbStateNotifyEvent, event_type);
+GIO_FIELD_OFFSET(XkbStateNotifyEvent, req_major);
+GIO_FIELD_OFFSET(XkbStateNotifyEvent, req_minor);
 */
 import "C"
 import (
@@ -48,6 +55,14 @@ type x11Window struct {
 	width  int
 	height int
 	cfg    Config
+
+	xkb struct {
+		opcode  C.int
+		event   C.int
+		errcode C.int
+		major   C.int
+		minor   C.int
+	}
 }
 
 func (w *x11Window) setAnimating(anim bool) {
@@ -78,10 +93,6 @@ func (w *x11Window) loop() {
 		var xev xEvent
 		C.XNextEvent(w.x, (*C.XEvent)(unsafe.Pointer(&xev)))
 		switch xev.Type {
-		case C.KeyPress:
-			// TODO(dennwc): keyboard press
-		case C.KeyRelease:
-			// TODO(dennwc): keyboard release
 		case C.ButtonPress, C.ButtonRelease:
 			ev := pointer.Event{
 				Type:   pointer.Press,
@@ -137,6 +148,12 @@ func (w *x11Window) loop() {
 			case C.long(w.evDelWindow):
 				return
 			}
+		case C.KeyPress, C.KeyRelease:
+			// TODO(dennwc): keyboard press
+		case w.xkb.event:
+			switch xev.GetXkbType() {
+			// TODO(dennwc): Xkb state
+			}
 		}
 	}
 }
@@ -240,6 +257,16 @@ func (e *xEvent) GetClientDataLong() [5]C.long {
 	return *ptr
 }
 
+// GetXkbType returns a XkbEvent.any.xkb_type field.
+func (e *xEvent) GetXkbType() C.int {
+	return e.getInt(int(C.gio_XkbAnyEvent_xkb_type_off))
+}
+
+// GetXkbTime returns a XkbEvent.any.time field.
+func (e *xEvent) GetXkbTime() time.Duration {
+	return e.getUlongMs(int(C.gio_XkbAnyEvent_time_off))
+}
+
 var (
 	x11Threads sync.Once
 )
@@ -258,6 +285,16 @@ func createWindowX11(w *Window, opts *windowOptions) error {
 	if disp == nil {
 		return errors.New("x11: cannot connect to the X server")
 	}
+	xw := &x11Window{
+		w: w, x: disp,
+		cfg: Config{pxPerDp: 1, pxPerSp: 1}, // TODO(dennwc): real config
+	}
+	if C.XkbQueryExtension(disp, &xw.xkb.opcode, &xw.xkb.event, &xw.xkb.opcode, &xw.xkb.major, &xw.xkb.minor) == 0 {
+		C.XCloseDisplay(disp)
+		return errors.New("x11: Xkb is not supported")
+	}
+	C.XkbSelectEvents(disp, C.XkbUseCoreKbd, C.XkbAllEventsMask, C.XkbAllEventsMask)
+
 	root := C.XDefaultRootWindow(disp)
 
 	var swa C.XSetWindowAttributes
@@ -267,43 +304,36 @@ func createWindowX11(w *Window, opts *windowOptions) error {
 		C.PointerMotionMask | // mouse movement
 		C.StructureNotifyMask // resize
 
-	cfg := Config{pxPerDp: 1, pxPerSp: 1} // TODO(dennwc): real config
-	win := C.XCreateWindow(disp, root,
-		0, 0, C.uint(cfg.Px(opts.Width)), C.uint(cfg.Px(opts.Height)), 0,
+	xw.width, xw.height = xw.cfg.Px(opts.Width), xw.cfg.Px(opts.Width)
+	xw.xw = C.XCreateWindow(disp, root,
+		0, 0, C.uint(xw.width), C.uint(xw.height), 0,
 		C.CopyFromParent, C.InputOutput,
 		nil, C.CWEventMask|C.CWBackPixel,
 		&swa,
 	)
 
-	xw := &x11Window{
-		w: w, x: disp, xw: win,
-		width:  cfg.Px(opts.Width),
-		height: cfg.Px(opts.Height),
-		cfg:    cfg,
-	}
-
 	var xattr C.XSetWindowAttributes
 	xattr.override_redirect = C.False
-	C.XChangeWindowAttributes(disp, win, C.CWOverrideRedirect, &xattr)
+	C.XChangeWindowAttributes(disp, xw.xw, C.CWOverrideRedirect, &xattr)
 
 	var hints C.XWMHints
 	hints.input = C.True
 	hints.flags = C.InputHint
-	C.XSetWMHints(disp, win, &hints)
+	C.XSetWMHints(disp, xw.xw, &hints)
 
 	// make the window visible on the screen
-	C.XMapWindow(disp, win)
+	C.XMapWindow(disp, xw.xw)
 
 	// set the name
 	ctitle := C.CString(opts.Title)
-	C.XStoreName(disp, win, ctitle)
+	C.XStoreName(disp, xw.xw, ctitle)
 	C.free(unsafe.Pointer(ctitle))
 
 	// extensions
 	ckey := C.CString("WM_DELETE_WINDOW")
 	xw.evDelWindow = C.XInternAtom(disp, ckey, C.False)
 	C.free(unsafe.Pointer(ckey))
-	C.XSetWMProtocols(disp, win, &xw.evDelWindow, 1)
+	C.XSetWMProtocols(disp, xw.xw, &xw.evDelWindow, 1)
 
 	go func() {
 		xw.w.setDriver(&window{x11: xw})
-- 
2.20.1

[PATCH 5/6] ui/app: (x11) properly name display and window types Export this patch

Denys Smirnov
In the initial version, x11Window object was a mix of X11 display and a
window. This change renames x11Window to x11Display and defines a new
type for X11 window.

Signed-off-by: Denys Smirnov <denis.smirnov.91@gmail.com>
---
 ui/app/egl_x11.go  |  16 ++--
 ui/app/os_linux.go |   2 +-
 ui/app/os_x11.go   | 188 ++++++++++++++++++++++++++++-----------------
 3 files changed, 125 insertions(+), 81 deletions(-)

diff --git a/ui/app/egl_x11.go b/ui/app/egl_x11.go
index 5026492..a639def 100644
--- a/ui/app/egl_x11.go
+++ b/ui/app/egl_x11.go
@@ -13,25 +13,21 @@ import "C"
 import "unsafe"
 
 type x11EGLWindow struct {
-	w  *x11Window
-	xw unsafe.Pointer
+	w *x11Window
 }
 
-func (w *x11Window) newEGLWindow(xw unsafe.Pointer, width, height int) (*eglWindow, error) {
-	return &eglWindow{x11: &x11EGLWindow{w: w, xw: xw}}, nil
+func (d *x11Display) newEGLWindow(xw unsafe.Pointer, width, height int) (*eglWindow, error) {
+	return &eglWindow{x11: &x11EGLWindow{w: d.mainWin}}, nil
 }
 
 func (w *x11EGLWindow) window() unsafe.Pointer {
-	return w.xw
+	return unsafe.Pointer(uintptr(w.w.w))
 }
 
 func (w *x11EGLWindow) resize(width, height int) {
-	var change C.XWindowChanges
-	change.width = C.int(width)
-	change.height = C.int(height)
-	C.XConfigureWindow(w.w.x, w.w.xw, C.CWWidth|C.CWHeight, &change)
+	w.w.Resize(width, height)
 }
 
 func (w *x11EGLWindow) destroy() {
-	// destroyed by x11Window.destroy
+	// destroyed by x11Display.destroy
 }
diff --git a/ui/app/os_linux.go b/ui/app/os_linux.go
index f644db8..453bab9 100644
--- a/ui/app/os_linux.go
+++ b/ui/app/os_linux.go
@@ -19,7 +19,7 @@ func createWindow(window *Window, opts *windowOptions) error {
 }
 
 type window struct {
-	x11 *x11Window
+	x11 *x11Display
 	wl  *wlWindow
 }
 
diff --git a/ui/app/os_x11.go b/ui/app/os_x11.go
index 5b73d1e..433c47e 100644
--- a/ui/app/os_x11.go
+++ b/ui/app/os_x11.go
@@ -45,17 +45,16 @@ import (
 	"gioui.org/ui/pointer"
 )
 
-type x11Window struct {
-	w  *Window
-	x  *C.Display
-	xw C.Window
+type x11Display struct {
+	w *Window
 
-	evDelWindow C.Atom
-
-	width  int
-	height int
-	cfg    Config
+	cfg     Config
+	disp    *C.Display
+	mainWin *x11Window
 
+	ext struct {
+		eventDelWindow C.Atom
+	}
 	xkb struct {
 		opcode  C.int
 		event   C.int
@@ -65,33 +64,49 @@ type x11Window struct {
 	}
 }
 
-func (w *x11Window) setAnimating(anim bool) {
+func (d *x11Display) xInternAtom(key string, onlyIfExists bool) C.Atom {
+	ckey := C.CString(key)
+	cexist := C.int(C.False)
+	if onlyIfExists {
+		cexist = C.True
+	}
+	atom := C.XInternAtom(d.disp, ckey, cexist)
+	C.free(unsafe.Pointer(ckey))
+	return atom
+}
+
+func (d *x11Display) setAnimating(anim bool) {
 	// TODO(dennwc): implement animation state
 }
 
-func (w *x11Window) showTextInput(show bool) {}
+func (d *x11Display) showTextInput(show bool) {}
 
-func (w *x11Window) display() unsafe.Pointer {
+func (d *x11Display) display() unsafe.Pointer {
 	// TODO(dennwc): We have an awesome X library written in pure Go, however,
 	//               we can't use it because of this specific function.
 	//               The *C.Display pointer is required to call eglGetDisplay,
 	//               so we can't really implement the call in pure Go.
 	//               Thus, we have to use Xlib for everything.
-	return unsafe.Pointer(w.x)
+	return unsafe.Pointer(d.disp)
+}
+
+func (d *x11Display) nativeWindow(visID int) (unsafe.Pointer, int, int) {
+	return unsafe.Pointer(uintptr(d.mainWin.w)), d.mainWin.width, d.mainWin.height
 }
 
-func (w *x11Window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
-	return unsafe.Pointer(uintptr(w.xw)), w.width, w.height
+func (d *x11Display) setStage(s Stage) {
+	d.w.event(StageEvent{s})
 }
 
-func (w *x11Window) setStage(s Stage) {
-	w.w.event(StageEvent{s})
+func (d *x11Display) nextEvent() xEvent {
+	var e xEvent
+	C.XNextEvent(d.disp, (*C.XEvent)(unsafe.Pointer(&e)))
+	return e
 }
 
-func (w *x11Window) loop() {
+func (d *x11Display) loop() {
 	for {
-		var xev xEvent
-		C.XNextEvent(w.x, (*C.XEvent)(unsafe.Pointer(&xev)))
+		xev := d.nextEvent()
 		switch xev.Type {
 		case C.ButtonPress, C.ButtonRelease:
 			ev := pointer.Event{
@@ -121,10 +136,10 @@ func (w *x11Window) loop() {
 			default:
 				continue
 			}
-			w.w.event(ev)
-			w.draw()
+			d.w.event(ev)
+			d.draw()
 		case C.MotionNotify:
-			w.w.event(pointer.Event{
+			d.w.event(pointer.Event{
 				Type:   pointer.Move,
 				Source: pointer.Mouse,
 				Position: f32.Point{
@@ -133,24 +148,23 @@ func (w *x11Window) loop() {
 				},
 				Time: xev.GetMotionTime(),
 			})
-			w.draw()
+			d.draw()
 		case C.Expose: // update
-			w.draw()
+			d.draw()
 		case C.ConfigureNotify: // window configuration change
-			oldW, oldH := w.width, w.height
-			w.width = int(xev.GetConfigureWidth())
-			w.height = int(xev.GetConfigureHeight())
-			if oldW != w.width || oldH != w.height {
-				w.draw()
+			width, height := int(xev.GetConfigureWidth()), int(xev.GetConfigureHeight())
+			if curW, curH := d.mainWin.Size(); curW != width || curH != height {
+				d.mainWin.setSize(width, height)
+				d.draw()
 			}
 		case C.ClientMessage: // extensions
 			switch xev.GetClientDataLong()[0] {
-			case C.long(w.evDelWindow):
+			case C.long(d.ext.eventDelWindow):
 				return
 			}
 		case C.KeyPress, C.KeyRelease:
 			// TODO(dennwc): keyboard press
-		case w.xkb.event:
+		case d.xkb.event:
 			switch xev.GetXkbType() {
 			// TODO(dennwc): Xkb state
 			}
@@ -158,18 +172,18 @@ func (w *x11Window) loop() {
 	}
 }
 
-func (w *x11Window) destroy() {
-	C.XDestroyWindow(w.x, w.xw)
-	C.XCloseDisplay(w.x)
+func (d *x11Display) destroy() {
+	d.mainWin.Destroy()
+	C.XCloseDisplay(d.disp)
 }
 
-func (w *x11Window) draw() {
-	w.w.event(UpdateEvent{
+func (d *x11Display) draw() {
+	d.w.event(UpdateEvent{
 		Size: image.Point{
-			X: w.width,
-			Y: w.height,
+			X: d.mainWin.width,
+			Y: d.mainWin.height,
 		},
-		Config: w.cfg,
+		Config: d.cfg,
 		sync:   false,
 	})
 }
@@ -267,9 +281,7 @@ func (e *xEvent) GetXkbTime() time.Duration {
 	return e.getUlongMs(int(C.gio_XkbAnyEvent_time_off))
 }
 
-var (
-	x11Threads sync.Once
-)
+var x11Threads sync.Once
 
 func createWindowX11(w *Window, opts *windowOptions) error {
 	var err error
@@ -285,18 +297,29 @@ func createWindowX11(w *Window, opts *windowOptions) error {
 	if disp == nil {
 		return errors.New("x11: cannot connect to the X server")
 	}
-	xw := &x11Window{
-		w: w, x: disp,
+	xd := &x11Display{
+		w: w, disp: disp,
 		cfg: Config{pxPerDp: 1, pxPerSp: 1}, // TODO(dennwc): real config
 	}
-	if C.XkbQueryExtension(disp, &xw.xkb.opcode, &xw.xkb.event, &xw.xkb.opcode, &xw.xkb.major, &xw.xkb.minor) == 0 {
+	xd.ext.eventDelWindow = xd.xInternAtom("WM_DELETE_WINDOW", false)
+	if C.XkbQueryExtension(disp, &xd.xkb.opcode, &xd.xkb.event, &xd.xkb.opcode, &xd.xkb.major, &xd.xkb.minor) == 0 {
 		C.XCloseDisplay(disp)
 		return errors.New("x11: Xkb is not supported")
 	}
 	C.XkbSelectEvents(disp, C.XkbUseCoreKbd, C.XkbAllEventsMask, C.XkbAllEventsMask)
+	xd.mainWin = xd.newWindow(opts)
 
-	root := C.XDefaultRootWindow(disp)
+	go func() {
+		xd.w.setDriver(&window{x11: xd})
+		xd.setStage(StageRunning)
+		xd.loop()
+		xd.destroy()
+		close(mainDone)
+	}()
+	return nil
+}
 
+func (d *x11Display) newWindow(opts *windowOptions) *x11Window {
 	var swa C.XSetWindowAttributes
 	swa.event_mask = C.ExposureMask | // update
 		C.KeyPressMask | C.KeyReleaseMask | // keyboard
@@ -304,43 +327,68 @@ func createWindowX11(w *Window, opts *windowOptions) error {
 		C.PointerMotionMask | // mouse movement
 		C.StructureNotifyMask // resize
 
-	xw.width, xw.height = xw.cfg.Px(opts.Width), xw.cfg.Px(opts.Width)
-	xw.xw = C.XCreateWindow(disp, root,
-		0, 0, C.uint(xw.width), C.uint(xw.height), 0,
+	width, height := d.cfg.Px(opts.Width), d.cfg.Px(opts.Width)
+	xwin := C.XCreateWindow(d.disp, C.XDefaultRootWindow(d.disp),
+		0, 0, C.uint(width), C.uint(height), 0,
 		C.CopyFromParent, C.InputOutput,
 		nil, C.CWEventMask|C.CWBackPixel,
 		&swa,
 	)
+	w := &x11Window{d: d, w: xwin, width: width, height: height}
 
 	var xattr C.XSetWindowAttributes
 	xattr.override_redirect = C.False
-	C.XChangeWindowAttributes(disp, xw.xw, C.CWOverrideRedirect, &xattr)
+	C.XChangeWindowAttributes(d.disp, xwin, C.CWOverrideRedirect, &xattr)
 
 	var hints C.XWMHints
 	hints.input = C.True
 	hints.flags = C.InputHint
-	C.XSetWMHints(disp, xw.xw, &hints)
+	C.XSetWMHints(d.disp, xwin, &hints)
 
 	// make the window visible on the screen
-	C.XMapWindow(disp, xw.xw)
+	w.show()
+	w.SetTitle(opts.Title)
+	// extensions
+	w.setWMProtocols(d.ext.eventDelWindow)
+	return w
+}
+
+type x11Window struct {
+	d      *x11Display
+	w      C.Window
+	width  int
+	height int
+}
 
-	// set the name
-	ctitle := C.CString(opts.Title)
-	C.XStoreName(disp, xw.xw, ctitle)
-	C.free(unsafe.Pointer(ctitle))
+func (w *x11Window) show() {
+	C.XMapWindow(w.d.disp, w.w)
+}
 
-	// extensions
-	ckey := C.CString("WM_DELETE_WINDOW")
-	xw.evDelWindow = C.XInternAtom(disp, ckey, C.False)
-	C.free(unsafe.Pointer(ckey))
-	C.XSetWMProtocols(disp, xw.xw, &xw.evDelWindow, 1)
+func (w *x11Window) setWMProtocols(atoms ...C.Atom) {
+	C.XSetWMProtocols(w.d.disp, w.w, &atoms[0], C.int(len(atoms)))
+}
 
-	go func() {
-		xw.w.setDriver(&window{x11: xw})
-		xw.setStage(StageRunning)
-		xw.loop()
-		xw.destroy()
-		close(mainDone)
-	}()
-	return nil
+func (w *x11Window) SetTitle(s string) {
+	cs := C.CString(s)
+	C.XStoreName(w.d.disp, w.w, cs)
+	C.free(unsafe.Pointer(cs))
+}
+
+func (w *x11Window) Size() (width, height int) {
+	return w.width, w.height
+}
+
+func (w *x11Window) setSize(width, height int) {
+	w.width, w.height = width, height
+}
+
+func (w *x11Window) Resize(width, height int) {
+	var change C.XWindowChanges
+	change.width = C.int(width)
+	change.height = C.int(height)
+	C.XConfigureWindow(w.d.disp, w.w, C.CWWidth|C.CWHeight, &change)
+}
+
+func (w *x11Window) Destroy() {
+	C.XDestroyWindow(w.d.disp, w.w)
 }
-- 
2.20.1

[PATCH 6/6] ui/app: add "no X11" build tag Export this patch

Denys Smirnov
This change allows to exclude X11 functionality from the binaries and
does not require X11 headers during the build.

Signed-off-by: Denys Smirnov <denis.smirnov.91@gmail.com>
---
 ui/app/egl_linux.go        | 40 ++----------------------------------
 ui/app/egl_linux_no_x11.go | 27 ++++++++++++++++++++++++
 ui/app/egl_linux_x11.go    | 42 ++++++++++++++++++++++++++++++++++++++
 ui/app/egl_x11.go          |  2 +-
 ui/app/os_linux.go         |  2 +-
 ui/app/os_linux_no_x11.go  | 35 +++++++++++++++++++++++++++++++
 ui/app/os_x11.go           |  2 +-
 7 files changed, 109 insertions(+), 41 deletions(-)
 create mode 100644 ui/app/egl_linux_no_x11.go
 create mode 100644 ui/app/egl_linux_x11.go
 create mode 100644 ui/app/os_linux_no_x11.go

diff --git a/ui/app/egl_linux.go b/ui/app/egl_linux.go
index 1cd429d..8be6da1 100644
--- a/ui/app/egl_linux.go
+++ b/ui/app/egl_linux.go
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: Unlicense OR MIT
 
+// +build linux,!android
+
 package app
 
 /*
@@ -11,9 +13,6 @@ package app
 #include <GLES3/gl3.h>
 */
 import "C"
-import (
-	"unsafe"
-)
 
 type (
 	_EGLint     = C.EGLint
@@ -84,38 +83,3 @@ func eglTerminate(disp _EGLDisplay) bool {
 func eglQueryString(disp _EGLDisplay, name _EGLint) string {
 	return C.GoString(C.eglQueryString(disp, name))
 }
-
-type eglWindow struct {
-	x11 *x11EGLWindow
-	wl  *wlEGLWindow
-}
-
-func (w *window) newEGLWindow(ew unsafe.Pointer, width, height int) (*eglWindow, error) {
-	if w.wl != nil {
-		return w.wl.newEGLWindow(ew, width, height)
-	}
-	return w.x11.newEGLWindow(ew, width, height)
-}
-
-func (w *eglWindow) window() unsafe.Pointer {
-	if w.wl != nil {
-		return w.wl.window()
-	}
-	return w.x11.window()
-}
-
-func (w *eglWindow) resize(width, height int) {
-	if w.wl != nil {
-		w.wl.resize(width, height)
-	} else {
-		w.x11.resize(width, height)
-	}
-}
-
-func (w *eglWindow) destroy() {
-	if w.wl != nil {
-		w.wl.destroy()
-	} else {
-		w.x11.destroy()
-	}
-}
diff --git a/ui/app/egl_linux_no_x11.go b/ui/app/egl_linux_no_x11.go
new file mode 100644
index 0000000..26f9456
--- /dev/null
+++ b/ui/app/egl_linux_no_x11.go
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build linux,!android,nox11
+
+package app
+
+import "unsafe"
+
+type eglWindow struct {
+	wl *wlEGLWindow
+}
+
+func (w *window) newEGLWindow(ew unsafe.Pointer, width, height int) (*eglWindow, error) {
+	return w.wl.newEGLWindow(ew, width, height)
+}
+
+func (w *eglWindow) window() unsafe.Pointer {
+	return w.wl.window()
+}
+
+func (w *eglWindow) resize(width, height int) {
+	w.wl.resize(width, height)
+}
+
+func (w *eglWindow) destroy() {
+	w.wl.destroy()
+}
diff --git a/ui/app/egl_linux_x11.go b/ui/app/egl_linux_x11.go
new file mode 100644
index 0000000..9fef122
--- /dev/null
+++ b/ui/app/egl_linux_x11.go
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build linux,!android,!nox11
+
+package app
+
+import "unsafe"
+
+type eglWindow struct {
+	x11 *x11EGLWindow
+	wl  *wlEGLWindow
+}
+
+func (w *window) newEGLWindow(ew unsafe.Pointer, width, height int) (*eglWindow, error) {
+	if w.wl != nil {
+		return w.wl.newEGLWindow(ew, width, height)
+	}
+	return w.x11.newEGLWindow(ew, width, height)
+}
+
+func (w *eglWindow) window() unsafe.Pointer {
+	if w.wl != nil {
+		return w.wl.window()
+	}
+	return w.x11.window()
+}
+
+func (w *eglWindow) resize(width, height int) {
+	if w.wl != nil {
+		w.wl.resize(width, height)
+	} else {
+		w.x11.resize(width, height)
+	}
+}
+
+func (w *eglWindow) destroy() {
+	if w.wl != nil {
+		w.wl.destroy()
+	} else {
+		w.x11.destroy()
+	}
+}
diff --git a/ui/app/egl_x11.go b/ui/app/egl_x11.go
index a639def..7675938 100644
--- a/ui/app/egl_x11.go
+++ b/ui/app/egl_x11.go
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Unlicense OR MIT
 
-// +build linux,!android
+// +build linux,!android,!nox11
 
 package app
 
diff --git a/ui/app/os_linux.go b/ui/app/os_linux.go
index 453bab9..2a81ad5 100644
--- a/ui/app/os_linux.go
+++ b/ui/app/os_linux.go
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Unlicense OR MIT
 
-// +build linux,!android
+// +build linux,!android,!nox11
 
 package app
 
diff --git a/ui/app/os_linux_no_x11.go b/ui/app/os_linux_no_x11.go
new file mode 100644
index 0000000..47ac213
--- /dev/null
+++ b/ui/app/os_linux_no_x11.go
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build linux,!android,nox11
+
+package app
+
+import "unsafe"
+
+func main() {
+	<-mainDone
+}
+
+func createWindow(window *Window, opts *windowOptions) error {
+	return createWindowWayland(window, opts)
+}
+
+type window struct {
+	wl *wlWindow
+}
+
+func (w *window) setAnimating(anim bool) {
+	w.wl.setAnimating(anim)
+}
+
+func (w *window) display() unsafe.Pointer {
+	return w.wl.display()
+}
+
+func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) {
+	return w.wl.nativeWindow(visID)
+}
+
+func (w *window) showTextInput(show bool) {
+	w.wl.showTextInput(show)
+}
diff --git a/ui/app/os_x11.go b/ui/app/os_x11.go
index 433c47e..7a9ae1b 100644
--- a/ui/app/os_x11.go
+++ b/ui/app/os_x11.go
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Unlicense OR MIT
 
-// +build linux,!android
+// +build linux,!android,!nox11
 
 package app
 
-- 
2.20.1
Thankyou. I'm using the result of a squash of all 6 patches for this
review.

	diff --git a/ui/app/egl.go b/ui/app/egl.go
	index ee07993..6c448d9 100644
	--- a/ui/app/egl.go
	+++ b/ui/app/egl.go
	@@ -9,6 +9,7 @@ import (
		"fmt"
		"runtime"
		"strings"
	+	"unsafe"
	 
		"gioui.org/ui/app/internal/gl"
	 )
	@@ -148,14 +149,14 @@ func (c *context) MakeCurrent() error {
		}
		if c.eglWin == nil {
			var err error
	-		c.eglWin, err = newEGLWindow(win, width, height)
	+		c.eglWin, err = c.driver.newEGLWindow(unsafe.Pointer(win), width, height)

Why is the cast here and below needed? From a glance in my copy of
eglplatform.h, perhaps we need to drop WL_EGL_PLATFORM and use the
MESA_EGL_NO_X11_HEADERS define.

			if err != nil {
				return err
			}
		} else {
			c.eglWin.resize(width, height)
		}
	-	eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, c.eglWin.window())
	+	eglSurf, err := createSurfaceAndMakeCurrent(c.eglCtx, _EGLNativeWindowType(c.eglWin.window()))
		c.eglSurf = eglSurf
		if err != nil {
			c.eglWin.destroy()


	diff --git a/ui/app/egl_linux.go b/ui/app/egl_linux.go
	index 051c229..8be6da1 100644
	--- a/ui/app/egl_linux.go
	+++ b/ui/app/egl_linux.go
	@@ -1,5 +1,7 @@
	 // SPDX-License-Identifier: Unlicense OR MIT
	 
	+// +build linux,!android
	+

Why this particular change? The filename already specifies the linux
constraint, and I don't understand why the !android is added.

Speaking of which, should we rename all the "linux" specific files to
"unix"? As far as I understand it X11 (and Wayland) should run on
other platforms. The rest of this review assumes a "unix" naming
convention.


	diff --git a/ui/app/egl_linux_no_x11.go b/ui/app/egl_linux_no_x11.go
	new file mode 100644
	index 0000000..26f9456
	--- /dev/null
	+++ b/ui/app/egl_linux_no_x11.go

Let's just name this egl_nox11.go.

	diff --git a/ui/app/egl_linux_x11.go b/ui/app/egl_linux_x11.go
	new file mode 100644
	index 0000000..9fef122
	--- /dev/null
	+++ b/ui/app/egl_linux_x11.go

Rename to egl_x11.go.

	@@ -0,0 +1,42 @@

	+
	+package app
	+
	+import "unsafe"
	+
	+type eglWindow struct {
	+	x11 *x11EGLWindow
	+	wl  *wlEGLWindow
	+}

This type almost begs to be an interface. Why not?

	diff --git a/ui/app/egl_wayland.go b/ui/app/egl_wayland.go
	index d8fc4a5..a982ede 100644
	--- a/ui/app/egl_wayland.go
	+++ b/ui/app/egl_wayland.go
	@@ -24,28 +24,28 @@ type (
		_EGLNativeWindowType  = C.EGLNativeWindowType

I believe by using MESA_EGL_NO_X11_HEADERS EGLNativeWindowType (and
EGLNativeDisplayType?) can be extracted to common linux code.

	diff --git a/ui/app/os_linux.go b/ui/app/os_linux.go
	new file mode 100644
	index 0000000..2a81ad5
	--- /dev/null
	+++ b/ui/app/os_linux.go

Rename to os_unix.go?

	+type window struct {
	+	x11 *x11Display
	+	wl  *wlWindow
	+}

Why not an interface?

	diff --git a/ui/app/os_linux_no_x11.go b/ui/app/os_linux_no_x11.go

Rename to os_nox11.go.

	diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go
	index 571e8f2..66b9df7 100644
	--- a/ui/app/os_wayland.go
	+++ b/ui/app/os_wayland.go
	@@ -54,6 +54,8 @@ import (
	 */
	 import "C"
	 
	+var errWLDisplayConnectFailed = errors.New("wayland: wl_display_connect failed")
	+

I think we should try the X11 fallback regardless of the particular
error from createWaylandWindow. For example, some Wayland compositors
does not implement the xdg_wm_base extension, in which case we'd like
Gio to fallback to X11.

 
	 // detectFontScale reports current font scale, or 1.0
	 // if it fails.
	@@ -1110,7 +1108,7 @@ func waylandConnect() error {
		c.disp = C.wl_display_connect(nil)
		if c.disp == nil {
			c.destroy()
	-		return errors.New("wayland: wl_display_connect failed")
	+		return errWLDisplayConnectFailed

Let's keep this error as is.

	diff --git a/ui/app/os_x11.go b/ui/app/os_x11.go
	new file mode 100644
	index 0000000..7a9ae1b
	--- /dev/null
	+++ b/ui/app/os_x11.go

I'll leave the review of this file until the port is complete.

-- elias
View this thread in the archives