~eliasnaur/gio

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

Denys Smirnov
Denys Smirnov: 6
 ui/app: (wayland) return a specific error value when connection fails
 ui/app: (wayland) refactor code path to allow X11 fallback
 ui/app: (x11) initial implementation of X11 backend
 ui/app: (x11) initial work to enable XKB in X11
 ui/app: (x11) properly name display and window types
 ui/app: add "no X11" build tag

 21 files changed, 778 insertions(+), 192 deletions(-)
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



          
          
          
        
      

      
      

      

      
      

      

      
    
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/patches/8011/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