mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 02:15:34 +00:00
app/internal/window: x11 driver
This driver still lacks fling support and dp/sp configuration. By default, linux builds will try to use the Wayland driver then fallback to X11 if it fails. Drivers can be disabled by using either the nowayland or nox11 build tags. Signed-off-by: Denis Bernard <db047h@gmail.com>
This commit is contained in:
committed by
Elias Naur
parent
ec672ca06a
commit
547ff2e484
@@ -6,6 +6,7 @@ package window
|
|||||||
#include <EGL/egl.h>
|
#include <EGL/egl.h>
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
import "gioui.org/app/internal/gl"
|
||||||
|
|
||||||
func (w *window) eglDestroy() {
|
func (w *window) eglDestroy() {
|
||||||
}
|
}
|
||||||
@@ -19,4 +20,8 @@ func (w *window) eglWindow(visID int) (_EGLNativeWindowType, int, int, error) {
|
|||||||
return _EGLNativeWindowType(win), width, height, nil
|
return _EGLNativeWindowType(win), width, height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) NewContext() (gl.Context, error) {
|
||||||
|
return newContext(w)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) needVSync() bool { return false }
|
func (w *window) needVSync() bool { return false }
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ package window
|
|||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import "gioui.org/app/internal/gl"
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
_EGLint = C.EGLint
|
_EGLint = C.EGLint
|
||||||
_EGLDisplay = C.EGLDisplay
|
_EGLDisplay = C.EGLDisplay
|
||||||
@@ -94,7 +92,3 @@ func eglCreateWindowSurface(disp _EGLDisplay, conf _EGLConfig, win _EGLNativeWin
|
|||||||
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
|
eglSurf := C.eglCreateWindowSurface(disp, conf, win, &attribs[0])
|
||||||
return eglSurf
|
return eglSurf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) NewContext() (gl.Context, error) {
|
|
||||||
return newContext(w)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// +build linux,!android
|
// +build linux,!android,!nowayland
|
||||||
|
|
||||||
package window
|
package window
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"gioui.org/app/internal/gl"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -63,4 +65,8 @@ func (w *window) eglWindow(visID int) (_EGLNativeWindowType, int, int, error) {
|
|||||||
return _EGLNativeWindowType(uintptr(unsafe.Pointer(eglWin))), width, height, nil
|
return _EGLNativeWindowType(uintptr(unsafe.Pointer(eglWin))), width, height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *window) NewContext() (gl.Context, error) {
|
||||||
|
return newContext(w)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *window) needVSync() bool { return false }
|
func (w *window) needVSync() bool { return false }
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
// +build linux,!android,!nox11
|
||||||
|
|
||||||
|
package window
|
||||||
|
|
||||||
|
import "gioui.org/app/internal/gl"
|
||||||
|
|
||||||
|
func (w *x11Window) NewContext() (gl.Context, error) {
|
||||||
|
return newContext(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) eglDestroy() {
|
||||||
|
w.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) eglDisplay() _EGLNativeDisplayType {
|
||||||
|
return _EGLNativeDisplayType(w.display())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) eglWindow(visID int) (_EGLNativeWindowType, int, int, error) {
|
||||||
|
return _EGLNativeWindowType(uintptr(w.xw)), w.width, w.height, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) needVSync() bool { return true }
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
// +build linux,!android
|
||||||
|
|
||||||
|
package window
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mainDone = make(chan struct{})
|
||||||
|
|
||||||
|
func Main() {
|
||||||
|
<-mainDone
|
||||||
|
}
|
||||||
|
|
||||||
|
// instead of creating files with build tags for each combination of wayland +/- x11
|
||||||
|
// let each driver initialize these variables with their own version of createWindow.
|
||||||
|
var wlDriver, x11Driver func(Callbacks, *Options) error
|
||||||
|
|
||||||
|
func NewWindow(window Callbacks, opts *Options) error {
|
||||||
|
var errFirst, err error
|
||||||
|
if wlDriver != nil {
|
||||||
|
if err = wlDriver(window, opts); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errFirst = err
|
||||||
|
}
|
||||||
|
if x11Driver != nil {
|
||||||
|
if err = x11Driver(window, opts); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errFirst == nil {
|
||||||
|
errFirst = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errFirst != nil {
|
||||||
|
return errFirst
|
||||||
|
}
|
||||||
|
return errors.New("app: no window driver available")
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// +build linux,!android
|
// +build linux,!android,!nowayland
|
||||||
|
|
||||||
#include <wayland-client.h>
|
#include <wayland-client.h>
|
||||||
#include "wayland_xdg_shell.h"
|
#include "wayland_xdg_shell.h"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: Unlicense OR MIT
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// +build linux,!android
|
// +build linux,!android,!nowayland
|
||||||
|
|
||||||
package window
|
package window
|
||||||
|
|
||||||
@@ -35,9 +35,9 @@ import (
|
|||||||
//go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h
|
//go:generate wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.h
|
||||||
//go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c
|
//go:generate wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml wayland_xdg_decoration.c
|
||||||
|
|
||||||
//go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_xdg_shell.c
|
//go:generate sed -i "1s;^;// +build linux,!android,!nowayland\\n\\n;" wayland_xdg_shell.c
|
||||||
//go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_xdg_decoration.c
|
//go:generate sed -i "1s;^;// +build linux,!android,!nowayland\\n\\n;" wayland_xdg_decoration.c
|
||||||
//go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_text_input.c
|
//go:generate sed -i "1s;^;// +build linux,!android,!nowayland\\n\\n;" wayland_text_input.c
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo LDFLAGS: -lwayland-client -lwayland-cursor
|
#cgo LDFLAGS: -lwayland-client -lwayland-cursor
|
||||||
@@ -145,7 +145,6 @@ type wlOutput struct {
|
|||||||
|
|
||||||
var connMu sync.Mutex
|
var connMu sync.Mutex
|
||||||
var conn *wlConn
|
var conn *wlConn
|
||||||
var mainDone = make(chan struct{})
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
winMap = make(map[interface{}]*window)
|
winMap = make(map[interface{}]*window)
|
||||||
@@ -153,11 +152,11 @@ var (
|
|||||||
outputConfig = make(map[*C.struct_wl_output]*wlOutput)
|
outputConfig = make(map[*C.struct_wl_output]*wlOutput)
|
||||||
)
|
)
|
||||||
|
|
||||||
func Main() {
|
func init() {
|
||||||
<-mainDone
|
wlDriver = newWLWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWindow(window Callbacks, opts *Options) error {
|
func newWLWindow(window Callbacks, opts *Options) error {
|
||||||
connMu.Lock()
|
connMu.Lock()
|
||||||
defer connMu.Unlock()
|
defer connMu.Unlock()
|
||||||
if len(winMap) > 0 {
|
if len(winMap) > 0 {
|
||||||
|
|||||||
@@ -0,0 +1,581 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
// +build linux,!android,!nox11
|
||||||
|
|
||||||
|
package window
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lX11
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <locale.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(XExposeEvent, count);
|
||||||
|
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);
|
||||||
|
GIO_FIELD_OFFSET(XKeyEvent, state);
|
||||||
|
|
||||||
|
void gio_x11_init_ime(Display *dpy, Window win, XIM *xim, XIC *xic) {
|
||||||
|
// adjust locale temporarily for XOpenIM
|
||||||
|
char *lc = setlocale(LC_CTYPE, NULL);
|
||||||
|
setlocale(LC_CTYPE, "");
|
||||||
|
XSetLocaleModifiers("");
|
||||||
|
|
||||||
|
*xim = XOpenIM(dpy, 0, 0, 0);
|
||||||
|
if (!*xim) {
|
||||||
|
// fallback to internal input method
|
||||||
|
XSetLocaleModifiers("@im=none");
|
||||||
|
*xim = XOpenIM(dpy, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// revert locale to prevent any unexpected side effects
|
||||||
|
setlocale(LC_CTYPE, lc);
|
||||||
|
|
||||||
|
*xic = XCreateIC(*xim,
|
||||||
|
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
|
||||||
|
XNClientWindow, win,
|
||||||
|
XNFocusWindow, win,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
XSetICFocus(*xic);
|
||||||
|
}
|
||||||
|
|
||||||
|
int gio_x11_connection_number(Display *dpy) {
|
||||||
|
return ConnectionNumber(dpy);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"gioui.org/f32"
|
||||||
|
"gioui.org/io/key"
|
||||||
|
"gioui.org/io/pointer"
|
||||||
|
"gioui.org/io/system"
|
||||||
|
syscall "golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type x11Window struct {
|
||||||
|
w Callbacks
|
||||||
|
x *C.Display
|
||||||
|
xw C.Window
|
||||||
|
|
||||||
|
evDelWindow C.Atom
|
||||||
|
stage system.Stage
|
||||||
|
cfg config
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
xim C.XIM
|
||||||
|
xic C.XIC
|
||||||
|
notify struct {
|
||||||
|
read, write int
|
||||||
|
}
|
||||||
|
dead bool
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
animating bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) SetAnimating(anim bool) {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.animating = anim
|
||||||
|
w.mu.Unlock()
|
||||||
|
if anim {
|
||||||
|
w.wakeup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) ShowTextInput(show bool) {}
|
||||||
|
|
||||||
|
var x11OneByte = make([]byte, 1)
|
||||||
|
|
||||||
|
func (w *x11Window) wakeup() {
|
||||||
|
if _, err := syscall.Write(w.notify.write, x11OneByte); err != nil && err != syscall.EAGAIN {
|
||||||
|
panic(fmt.Errorf("failed to write to pipe: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) setStage(s system.Stage) {
|
||||||
|
if s == w.stage {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.stage = s
|
||||||
|
w.w.Event(system.StageEvent{s})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) loop() {
|
||||||
|
h := x11EventHandler{w: w, xev: new(xEvent), text: make([]byte, 4)}
|
||||||
|
xfd := C.gio_x11_connection_number(w.x)
|
||||||
|
|
||||||
|
// Poll for events and notifications.
|
||||||
|
pollfds := []syscall.PollFd{
|
||||||
|
{Fd: int32(xfd), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
|
{Fd: int32(w.notify.read), Events: syscall.POLLIN | syscall.POLLERR},
|
||||||
|
}
|
||||||
|
dispEvents := &pollfds[0].Revents
|
||||||
|
// Plenty of room for a backlog of notifications.
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
|
||||||
|
var syn, redraw bool
|
||||||
|
loop:
|
||||||
|
for !w.dead {
|
||||||
|
w.mu.Lock()
|
||||||
|
animating := w.animating
|
||||||
|
w.mu.Unlock()
|
||||||
|
// Clear poll events.
|
||||||
|
*dispEvents = 0
|
||||||
|
if animating {
|
||||||
|
redraw = true
|
||||||
|
syn = h.handleEvents()
|
||||||
|
} else if _, err := syscall.Ppoll(pollfds, nil, nil); err != nil && err != syscall.EINTR {
|
||||||
|
panic(fmt.Errorf("x11 loop: poll failed: %w", err))
|
||||||
|
}
|
||||||
|
// Clear notifications.
|
||||||
|
for {
|
||||||
|
_, err := syscall.Read(w.notify.read, buf)
|
||||||
|
if err == syscall.EAGAIN {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("x11 loop: read from notify pipe failed: %w", err))
|
||||||
|
}
|
||||||
|
redraw = true
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case *dispEvents&syscall.POLLIN != 0:
|
||||||
|
syn = h.handleEvents() || syn
|
||||||
|
if w.dead {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
case *dispEvents&(syscall.POLLERR|syscall.POLLHUP) != 0:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
|
if redraw || syn {
|
||||||
|
w.cfg.now = time.Now()
|
||||||
|
w.w.Event(FrameEvent{
|
||||||
|
FrameEvent: system.FrameEvent{
|
||||||
|
Size: image.Point{
|
||||||
|
X: w.width,
|
||||||
|
Y: w.height,
|
||||||
|
},
|
||||||
|
Config: &w.cfg,
|
||||||
|
},
|
||||||
|
Sync: syn,
|
||||||
|
})
|
||||||
|
redraw = false
|
||||||
|
syn = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.w.Event(system.DestroyEvent{Err: nil})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *x11Window) destroy() {
|
||||||
|
if w.notify.write != 0 {
|
||||||
|
syscall.Close(w.notify.write)
|
||||||
|
w.notify.write = 0
|
||||||
|
}
|
||||||
|
if w.notify.read != 0 {
|
||||||
|
syscall.Close(w.notify.read)
|
||||||
|
w.notify.read = 0
|
||||||
|
}
|
||||||
|
C.XDestroyIC(w.xic)
|
||||||
|
C.XCloseIM(w.xim)
|
||||||
|
C.XDestroyWindow(w.x, w.xw)
|
||||||
|
C.XCloseDisplay(w.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// x11EventHandler wraps static variables for the main event loop.
|
||||||
|
// Its sole purpose is to prevent heap allocation and reduce clutter
|
||||||
|
// in x11window.loop.
|
||||||
|
//
|
||||||
|
type x11EventHandler struct {
|
||||||
|
w *x11Window
|
||||||
|
text []byte
|
||||||
|
xev *xEvent
|
||||||
|
status C.Status
|
||||||
|
keysym C.KeySym
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEvents returns true if the window needs to be redrawn.
|
||||||
|
//
|
||||||
|
func (h *x11EventHandler) handleEvents() bool {
|
||||||
|
w := h.w
|
||||||
|
xev := h.xev
|
||||||
|
redraw := false
|
||||||
|
for C.XPending(w.x) != 0 {
|
||||||
|
C.XNextEvent(w.x, (*C.XEvent)(unsafe.Pointer(xev)))
|
||||||
|
if C.XFilterEvent((*C.XEvent)(unsafe.Pointer(xev)), C.None) == C.True {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch xev.Type {
|
||||||
|
case C.KeyPress:
|
||||||
|
lookup:
|
||||||
|
l := int(C.Xutf8LookupString(w.xic,
|
||||||
|
(*C.XKeyPressedEvent)(unsafe.Pointer(xev)),
|
||||||
|
(*C.char)(unsafe.Pointer(&h.text[0])), C.int(len(h.text)),
|
||||||
|
&h.keysym, &h.status))
|
||||||
|
switch h.status {
|
||||||
|
case C.XBufferOverflow:
|
||||||
|
h.text = make([]byte, l)
|
||||||
|
goto lookup
|
||||||
|
case C.XLookupChars:
|
||||||
|
w.w.Event(key.EditEvent{Text: string(h.text[:l])})
|
||||||
|
case C.XLookupKeySym:
|
||||||
|
if r, ok := x11KeySymToRune(h.keysym); ok {
|
||||||
|
w.w.Event(key.Event{
|
||||||
|
Name: r,
|
||||||
|
Modifiers: x11KeyStateToModifiers(xev.GetKeyState()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case C.XLookupBoth:
|
||||||
|
// here we need to choose if we send a key.Event or key.EditEvent
|
||||||
|
mods := x11KeyStateToModifiers(xev.GetKeyState())
|
||||||
|
if mods&key.ModCommand != 0 {
|
||||||
|
r, ok := x11KeySymToRune(h.keysym)
|
||||||
|
if !ok {
|
||||||
|
// on AZERTY keyboards, CTRL-1, 2, etc do not have a consistent behavior.
|
||||||
|
// Since keysim as set by Xutf8LookupString is layout dependent, get its layout independent
|
||||||
|
// version and use that instead (i.e. send CTRL-1, CTRL-2, etc. instead of CTRL-&, CTRL-é, …)
|
||||||
|
r, ok = x11KeySymToRune(C.XLookupKeysym((*C.XKeyEvent)(unsafe.Pointer(xev)), 0))
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
w.w.Event(key.Event{Name: r, Modifiers: mods})
|
||||||
|
}
|
||||||
|
} else if r, ok := x11SpecialKeySymToRune(h.keysym); ok {
|
||||||
|
w.w.Event(key.Event{Name: r, Modifiers: mods})
|
||||||
|
} else {
|
||||||
|
w.w.Event(key.EditEvent{Text: string(h.text[:l])})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case C.KeyRelease:
|
||||||
|
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)
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
case C.Expose: // update
|
||||||
|
// redraw only on the last expose event
|
||||||
|
redraw = xev.GetExposeCount() == 0
|
||||||
|
case C.FocusIn:
|
||||||
|
w.w.Event(key.FocusEvent{Focus: true})
|
||||||
|
case C.FocusOut:
|
||||||
|
w.w.Event(key.FocusEvent{Focus: false})
|
||||||
|
case C.ConfigureNotify: // window configuration change
|
||||||
|
w.width = int(xev.GetConfigureWidth())
|
||||||
|
w.height = int(xev.GetConfigureHeight())
|
||||||
|
// redraw will be done by a later expose event
|
||||||
|
case C.ClientMessage: // extensions
|
||||||
|
switch xev.GetClientDataLong()[0] {
|
||||||
|
case C.long(w.evDelWindow):
|
||||||
|
w.dead = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return redraw
|
||||||
|
}
|
||||||
|
|
||||||
|
func x11KeyStateToModifiers(s C.uint) key.Modifiers {
|
||||||
|
var m key.Modifiers
|
||||||
|
if s&C.ControlMask != 0 {
|
||||||
|
m |= key.ModCommand
|
||||||
|
}
|
||||||
|
if s&C.ShiftMask != 0 {
|
||||||
|
m |= key.ModShift
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func x11KeySymToRune(s C.KeySym) (rune, bool) {
|
||||||
|
if '0' <= s && s <= '9' || 'A' <= s && s <= 'Z' {
|
||||||
|
return rune(s), true
|
||||||
|
}
|
||||||
|
if 'a' <= s && s <= 'z' {
|
||||||
|
return rune(s - 0x20), true
|
||||||
|
}
|
||||||
|
return x11SpecialKeySymToRune(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func x11SpecialKeySymToRune(s C.KeySym) (rune, bool) {
|
||||||
|
var n rune
|
||||||
|
switch s {
|
||||||
|
case C.XK_Escape:
|
||||||
|
n = key.NameEscape
|
||||||
|
case C.XK_Left, C.XK_KP_Left:
|
||||||
|
n = key.NameLeftArrow
|
||||||
|
case C.XK_Right, C.XK_KP_Right:
|
||||||
|
n = key.NameRightArrow
|
||||||
|
case C.XK_Return:
|
||||||
|
n = key.NameReturn
|
||||||
|
case C.XK_KP_Enter:
|
||||||
|
n = key.NameEnter
|
||||||
|
case C.XK_Up, C.XK_KP_Up:
|
||||||
|
n = key.NameUpArrow
|
||||||
|
case C.XK_Down, C.XK_KP_Down:
|
||||||
|
n = key.NameDownArrow
|
||||||
|
case C.XK_Home, C.XK_KP_Home:
|
||||||
|
n = key.NameHome
|
||||||
|
case C.XK_End, C.XK_KP_End:
|
||||||
|
n = key.NameEnd
|
||||||
|
case C.XK_BackSpace:
|
||||||
|
n = key.NameDeleteBackward
|
||||||
|
case C.XK_Delete, C.XK_KP_Delete:
|
||||||
|
n = key.NameDeleteForward
|
||||||
|
case C.XK_Page_Up, C.XK_KP_Prior:
|
||||||
|
n = key.NamePageUp
|
||||||
|
case C.XK_Page_Down, C.XK_KP_Next:
|
||||||
|
n = key.NamePageDown
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExposeCount returns a XEvent.xexpose.count field.
|
||||||
|
func (e *xEvent) GetExposeCount() C.int {
|
||||||
|
return e.getInt(int(C.gio_XExposeEvent_count_off))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyState returns a XKeyEvent.state field.
|
||||||
|
func (e *xEvent) GetKeyState() C.uint {
|
||||||
|
return e.getUint(int(C.gio_XKeyEvent_state_off))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
x11Threads sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
x11Driver = newX11Window
|
||||||
|
}
|
||||||
|
|
||||||
|
func newX11Window(gioWin Callbacks, opts *Options) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
pipe := make([]int, 2)
|
||||||
|
if err := syscall.Pipe2(pipe, syscall.O_NONBLOCK|syscall.O_CLOEXEC); err != nil {
|
||||||
|
return fmt.Errorf("NewX11Window: failed to create pipe: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x11Threads.Do(func() {
|
||||||
|
if C.XInitThreads() == 0 {
|
||||||
|
err = errors.New("x11: threads init failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dpy := C.XOpenDisplay(nil)
|
||||||
|
if dpy == nil {
|
||||||
|
return errors.New("x11: cannot connect to the X server")
|
||||||
|
}
|
||||||
|
root := C.XDefaultRootWindow(dpy)
|
||||||
|
|
||||||
|
var (
|
||||||
|
swa C.XSetWindowAttributes
|
||||||
|
xim C.XIM
|
||||||
|
xic C.XIC
|
||||||
|
)
|
||||||
|
|
||||||
|
cfg := config{pxPerDp: 1, pxPerSp: 1} // TODO(dennwc): real config
|
||||||
|
swa.event_mask = C.ExposureMask | C.PointerMotionMask | C.KeyPressMask
|
||||||
|
win := C.XCreateWindow(dpy, 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)
|
||||||
|
C.gio_x11_init_ime(dpy, win, &xim, &xic)
|
||||||
|
C.XSelectInput(dpy, win, 0|
|
||||||
|
C.ExposureMask|C.FocusChangeMask| // update
|
||||||
|
C.KeyPressMask|C.KeyReleaseMask| // keyboard
|
||||||
|
C.ButtonPressMask|C.ButtonReleaseMask| // mouse clicks
|
||||||
|
C.PointerMotionMask| // mouse movement
|
||||||
|
C.StructureNotifyMask, // resize
|
||||||
|
)
|
||||||
|
|
||||||
|
w := &x11Window{
|
||||||
|
w: gioWin, x: dpy, xw: win,
|
||||||
|
width: cfg.Px(opts.Width),
|
||||||
|
height: cfg.Px(opts.Height),
|
||||||
|
cfg: cfg,
|
||||||
|
xim: xim,
|
||||||
|
xic: xic,
|
||||||
|
}
|
||||||
|
w.notify.read = pipe[0]
|
||||||
|
w.notify.write = pipe[1]
|
||||||
|
|
||||||
|
var xattr C.XSetWindowAttributes
|
||||||
|
xattr.override_redirect = C.False
|
||||||
|
C.XChangeWindowAttributes(dpy, win, C.CWOverrideRedirect, &xattr)
|
||||||
|
|
||||||
|
var hints C.XWMHints
|
||||||
|
hints.input = C.True
|
||||||
|
hints.flags = C.InputHint
|
||||||
|
C.XSetWMHints(dpy, win, &hints)
|
||||||
|
|
||||||
|
// make the window visible on the screen
|
||||||
|
C.XMapWindow(dpy, win)
|
||||||
|
|
||||||
|
// set the name
|
||||||
|
ctitle := C.CString(opts.Title)
|
||||||
|
C.XStoreName(dpy, win, ctitle)
|
||||||
|
C.free(unsafe.Pointer(ctitle))
|
||||||
|
|
||||||
|
// extensions
|
||||||
|
ckey := C.CString("WM_DELETE_WINDOW")
|
||||||
|
w.evDelWindow = C.XInternAtom(dpy, ckey, C.False)
|
||||||
|
C.free(unsafe.Pointer(ckey))
|
||||||
|
C.XSetWMProtocols(dpy, win, &w.evDelWindow, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
w.w.SetDriver(w)
|
||||||
|
w.setStage(system.StageRunning)
|
||||||
|
w.loop()
|
||||||
|
w.destroy()
|
||||||
|
close(mainDone)
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build linux,!android
|
// +build linux,!android,!nowayland
|
||||||
|
|
||||||
/* Generated by wayland-scanner 1.16.0 */
|
/* Generated by wayland-scanner 1.16.0 */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build linux,!android
|
// +build linux,!android,!nowayland
|
||||||
|
|
||||||
/* Generated by wayland-scanner 1.16.0 */
|
/* Generated by wayland-scanner 1.16.0 */
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build linux,!android
|
// +build linux,!android,!nowayland
|
||||||
|
|
||||||
/* Generated by wayland-scanner 1.16.0 */
|
/* Generated by wayland-scanner 1.16.0 */
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user