From f3019b94a86ac4f086836fdf95469b295804254c Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Sun, 31 Jan 2021 07:50:37 +0700 Subject: [PATCH 01/11] fix macro --- debian/changelog | 6 ++++++ src/ibus-bamboo/engine.go | 2 +- src/ibus-bamboo/engine_utils.go | 6 +++--- src/ibus-bamboo/utils.go | 2 +- src/ibus-bamboo/version.go | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/debian/changelog b/debian/changelog index e9e98e39..6761aaaf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +ibus-bamboo (0.6.8-2) stable; urgency=medium + + * New upstream release + + -- Luong Thanh Lam Sun, 31 Jan 2021 09:21:06 +0700 + ibus-bamboo (0.6.8-1) stable; urgency=medium * New upstream release diff --git a/src/ibus-bamboo/engine.go b/src/ibus-bamboo/engine.go index d350925f..8dfee310 100644 --- a/src/ibus-bamboo/engine.go +++ b/src/ibus-bamboo/engine.go @@ -346,7 +346,7 @@ func (e *IBusBambooEngine) PropertyActivate(propName string, propState uint32) * if propName == PropKeyMacroEnabled { if propState == ibus.PROP_STATE_CHECKED { e.config.IBflags |= IBmacroEnabled - e.config.IBflags |= IBautoCapitalizeMacro + // e.config.IBflags |= IBautoCapitalizeMacro e.macroTable.Enable(e.engineName) } else { e.config.IBflags &= ^IBmacroEnabled diff --git a/src/ibus-bamboo/engine_utils.go b/src/ibus-bamboo/engine_utils.go index cc5fdb12..ce4bfe5a 100644 --- a/src/ibus-bamboo/engine_utils.go +++ b/src/ibus-bamboo/engine_utils.go @@ -314,12 +314,12 @@ func (e *IBusBambooEngine) getMacroText() (bool, string) { if e.config.IBflags&IBmacroEnabled == 0 { return false, "" } - var text = e.preeditor.GetProcessedString(bamboo.VietnameseMode | bamboo.LowerCase) - if e.macroTable.HasKey(text) { + var text = e.preeditor.GetProcessedString(bamboo.VietnameseMode) + if e.macroTable.HasKey(strings.ToLower(text)) { return true, e.expandMacro(text) } else { text = e.preeditor.GetProcessedString(bamboo.PunctuationMode) - if e.macroTable.HasKey(text) { + if e.macroTable.HasKey(strings.ToLower(text)) { return true, e.expandMacro(text) } } diff --git a/src/ibus-bamboo/utils.go b/src/ibus-bamboo/utils.go index 4274baa6..84e9d5b9 100644 --- a/src/ibus-bamboo/utils.go +++ b/src/ibus-bamboo/utils.go @@ -87,7 +87,7 @@ const ( IBrestoreKeyStrokesEnabled IBmouseCapturing IBstdFlags = IBspellCheckEnabled | IBspellCheckWithRules | IBautoNonVnRestore | IBddFreeStyle | - IBemojiDisabled | IBinputModeLookupTableEnabled | IBmouseCapturing + IBemojiDisabled | IBinputModeLookupTableEnabled | IBmouseCapturing | IBautoCapitalizeMacro ) const ( diff --git a/src/ibus-bamboo/version.go b/src/ibus-bamboo/version.go index 0e7fa8e0..30c77ea3 100644 --- a/src/ibus-bamboo/version.go +++ b/src/ibus-bamboo/version.go @@ -19,4 +19,4 @@ package main -const Version = "v0.6.7" +const Version = "v0.6.8" From 56367456e88d2cabd1d6a269df1e0bd9a735d358 Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Tue, 23 Feb 2021 08:19:05 +0700 Subject: [PATCH 02/11] fix build error on go1.16 --- Makefile | 2 +- src/ibus-bamboo/x11_keyboard.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 6788271d..dcc4eee4 100755 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ rpm_src_tar=$(rpm_src_dir)/$(tar_file) tar_options_src=--transform "s/^\./$(pkg_name)-$(version)/" --exclude={"*.tar.gz",".git",".idea"} . build: - GOPATH=$(CURDIR) go build -ldflags="-s -w" -o $(ibus_e_name) ibus-$(engine_name) + GOPATH=$(CURDIR) GO111MODULE=off go build -ldflags="-s -w" -o $(ibus_e_name) ibus-$(engine_name) clean: rm -f ibus-engine-* *_linux *_cover.html go_test_* go_build_* test *.gz test diff --git a/src/ibus-bamboo/x11_keyboard.c b/src/ibus-bamboo/x11_keyboard.c index b7ecef65..edb189a6 100644 --- a/src/ibus-bamboo/x11_keyboard.c +++ b/src/ibus-bamboo/x11_keyboard.c @@ -80,16 +80,17 @@ void x11SendShiftLeft(int n, int r, int timeout) { void x11SendBackspace(int n, int timeout) { Display *display = XOpenDisplay(NULL); if (display) { - XSynchronize(display, 1); + /* XSynchronize(display, 1); */ KeyCode modcode; modcode = XKeysymToKeycode(display, XStringToKeysym("BackSpace")); for (int i=0; i Date: Tue, 23 Feb 2021 08:25:43 +0700 Subject: [PATCH 03/11] Bump version to v0.6.9 --- Makefile | 2 +- archlinux/PKGBUILD-git | 2 +- archlinux/PKGBUILD-release | 2 +- bamboo.xml | 2 +- debian/control | 2 +- ibus-bamboo.dsc | 4 ++-- ibus-bamboo.spec | 2 +- src/ibus-bamboo/version.go | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index dcc4eee4..c41eb042 100755 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ PREFIX=/usr engine_name=bamboo ibus_e_name=ibus-engine-$(engine_name) pkg_name=ibus-$(engine_name) -version=0.6.8 +version=0.6.9 engine_dir=$(PREFIX)/share/$(pkg_name) ibus_dir=$(PREFIX)/share/ibus diff --git a/archlinux/PKGBUILD-git b/archlinux/PKGBUILD-git index a83e0443..c0ad66a1 100644 --- a/archlinux/PKGBUILD-git +++ b/archlinux/PKGBUILD-git @@ -17,7 +17,7 @@ # # Maintainer: Luong Thanh Lam pkgname=ibus-bamboo-git -pkgver=0.6.8 +pkgver=0.6.9 pkgrel=1 pkgdesc='A Vietnamese IME for IBus' arch=(any) diff --git a/archlinux/PKGBUILD-release b/archlinux/PKGBUILD-release index 88426c2a..a4d308c0 100644 --- a/archlinux/PKGBUILD-release +++ b/archlinux/PKGBUILD-release @@ -17,7 +17,7 @@ # # Maintainer: Luong Thanh Lam pkgname=ibus-bamboo -pkgver=0.6.8 +pkgver=0.6.9 pkgrel=1 pkgdesc='A Vietnamese IME for IBus' arch=(any) diff --git a/bamboo.xml b/bamboo.xml index 1695a4bc..d6e0cf75 100644 --- a/bamboo.xml +++ b/bamboo.xml @@ -22,7 +22,7 @@ org.freedesktop.IBus.bamboo Vietnamese input engine for IBus /usr/lib/ibus-engine-bamboo --ibus - 0.6.8 + 0.6.9 Luong Thanh Lam <ltlam93@gmail.com> GPLv3 https://github.com/BambooEngine/ibus-bamboo/ diff --git a/debian/control b/debian/control index b190a33e..65c906d9 100644 --- a/debian/control +++ b/debian/control @@ -20,7 +20,7 @@ Section: utils Priority: extra Maintainer: Luong Thanh Lam Build-Depends: debhelper, golang, libx11-dev, libxtst-dev -Standards-Version: 0.6.8 +Standards-Version: 0.6.9 Homepage: https://github.com/BambooEngine/ibus-bamboo Package: ibus-bamboo diff --git a/ibus-bamboo.dsc b/ibus-bamboo.dsc index b1d45a56..1880e354 100644 --- a/ibus-bamboo.dsc +++ b/ibus-bamboo.dsc @@ -4,12 +4,12 @@ Format: 1.0 Source: ibus-bamboo Binary: ibus-bamboo Architecture: any -Version: 0.6.8-0 +Version: 0.6.9-0 Maintainer: Luong Thanh Lam Homepage: https://github.com/BambooEngine/ibus-bamboo Build-Depends: debhelper, golang, libx11-dev, libxt-dev, libxtst-dev Files: - 0 0 ibus-bamboo-0.6.8.tar.gz + 0 0 ibus-bamboo-0.6.9.tar.gz -----BEGIN PGP SIGNATURE----- diff --git a/ibus-bamboo.spec b/ibus-bamboo.spec index d6436229..276eb5a6 100644 --- a/ibus-bamboo.spec +++ b/ibus-bamboo.spec @@ -4,7 +4,7 @@ %define ibus_comp_dir /usr/share/ibus/component Name: ibus-bamboo -Version: 0.6.8 +Version: 0.6.9 Release: 1%{?dist} Summary: A Vietnamese input method for IBus diff --git a/src/ibus-bamboo/version.go b/src/ibus-bamboo/version.go index 30c77ea3..2ef87c97 100644 --- a/src/ibus-bamboo/version.go +++ b/src/ibus-bamboo/version.go @@ -19,4 +19,4 @@ package main -const Version = "v0.6.8" +const Version = "v0.6.9" From 9ff3af717ceda8a5779a59f172c0ae0f69f9a87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A2m?= Date: Tue, 23 Feb 2021 09:25:49 +0700 Subject: [PATCH 04/11] Update README.md --- README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7714bdf5..6c3ac74d 100644 --- a/README.md +++ b/README.md @@ -48,19 +48,23 @@ gsettings set org.gnome.desktop.input-sources sources "[('xkb', 'us'), ('ibus', ``` ### Arch Linux và các distro tương tự -Với Arch Linux, bạn có thể build từ AUR bằng yay: +Với Arch Linux, bạn thêm các dòng sau vào cuối file `/etc/pacman.conf`: ```sh -yay -S ibus-bamboo +[home_lamlng_Arch] +SigLevel = Never +Server = https://download.opensuse.org/repositories/home:/lamlng/Arch/$arch ``` -Hoặc bằng pamac: +rồi chạy lệnh: ```sh -pamac build ibus-bamboo +pacman -Syu +pacman -S home_lamlng_Arch/ibus-bamboo ``` -Hoặc build build từ file PKGBUILD: + +Hoặc build trực tiếp từ mã nguồn: ```sh -git clone https://aur.archlinux.org/ibus-bamboo.git -cd ibus-bamboo -makepkg -si +wget https://raw.githubusercontent.com/BambooEngine/ibus-bamboo/master/archlinux/install.sh +chmod +x install.sh +./install.sh ``` ### Void Linux From 163744f3bf8f01b5d0e9fbbe6049b98f03113ae2 Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Wed, 10 Mar 2021 21:58:51 +0700 Subject: [PATCH 05/11] use XGetInputFocus to handle focus window on Activity --- src/ibus-bamboo/engine.go | 16 +++---- src/ibus-bamboo/engine_utils.go | 24 +++++++--- src/ibus-bamboo/x11.go | 11 ++++- src/ibus-bamboo/x11_prop.c | 85 +++++++++++++++++++++++++++++---- 4 files changed, 112 insertions(+), 24 deletions(-) diff --git a/src/ibus-bamboo/engine.go b/src/ibus-bamboo/engine.go index 8dfee310..a9156af4 100644 --- a/src/ibus-bamboo/engine.go +++ b/src/ibus-bamboo/engine.go @@ -72,6 +72,7 @@ Return: This function gets called whenever a key is pressed. */ func (e *IBusBambooEngine) ProcessKeyEvent(keyVal uint32, keyCode uint32, state uint32) (bool, *dbus.Error) { + e.checkWmClass() if e.checkInputMode(usIM) { if e.isInputModeLTOpened || keyVal == IBusOpenLookupTable { // return false, nil @@ -86,7 +87,7 @@ func (e *IBusBambooEngine) ProcessKeyEvent(keyVal uint32, keyCode uint32, state return false, nil } log.Printf(">ProcessKeyEvent > %c | keyCode 0x%04x keyVal 0x%04x | %d\n", rune(keyVal), keyCode, keyVal, len(keyPressChan)) - if e.config.IBflags&IBinputModeLookupTableEnabled != 0 && keyVal == IBusOpenLookupTable && !e.isInputModeLTOpened && e.wmClasses != "" { + if e.config.IBflags&IBinputModeLookupTableEnabled != 0 && keyVal == IBusOpenLookupTable && !e.isInputModeLTOpened && e.getWmClass() != "" { e.resetBuffer() e.isInputModeLTOpened = true e.lastKeyWithShift = true @@ -118,22 +119,17 @@ func (e *IBusBambooEngine) ProcessKeyEvent(keyVal uint32, keyCode uint32, state func (e *IBusBambooEngine) FocusIn() *dbus.Error { log.Print("FocusIn.") - var oldWmClasses = e.wmClasses - e.wmClasses = x11GetFocusWindowClass() - fmt.Printf("WM_CLASS=(%s)\n", e.wmClasses) + fmt.Printf("WM_CLASS=(%s)\n", e.getWmClass()) + e.checkWmClass() e.RegisterProperties(e.propList) e.RequireSurroundingText() - if oldWmClasses != e.wmClasses { - e.resetBuffer() - e.resetFakeBackspace() - } return nil } func (e *IBusBambooEngine) FocusOut() *dbus.Error { log.Print("FocusOut.") - //e.wmClasses = "" + // e.checkWmClass() return nil } @@ -148,11 +144,13 @@ func (e *IBusBambooEngine) Reset() *dbus.Error { func (e *IBusBambooEngine) Enable() *dbus.Error { fmt.Print("Enable.") e.RequireSurroundingText() + startInputWatching() return nil } func (e *IBusBambooEngine) Disable() *dbus.Error { fmt.Print("Disable.") + stopInputWatching() return nil } diff --git a/src/ibus-bamboo/engine_utils.go b/src/ibus-bamboo/engine_utils.go index ce4bfe5a..e9a01547 100644 --- a/src/ibus-bamboo/engine_utils.go +++ b/src/ibus-bamboo/engine_utils.go @@ -131,6 +131,14 @@ func (e *IBusBambooEngine) resetBuffer() { } } +func (e *IBusBambooEngine) checkWmClass() { + if e.wmClasses != e.getWmClass() { + e.wmClasses = e.getWmClass() + e.resetBuffer() + e.resetFakeBackspace() + } +} + func (e *IBusBambooEngine) processShiftKey(keyVal, state uint32) bool { if keyVal == IBusShiftL || keyVal == IBusShiftR { // when press one Shift key @@ -189,8 +197,8 @@ func (e *IBusBambooEngine) getRawKeyLen() int { } func (e *IBusBambooEngine) getInputMode() int { - if e.wmClasses != "" { - if im, ok := e.config.InputModeMapping[e.wmClasses]; ok && imLookupTable[im] != "" { + if e.getWmClass() != "" { + if im, ok := e.config.InputModeMapping[e.getWmClass()]; ok && imLookupTable[im] != "" { return im } } @@ -201,8 +209,8 @@ func (e *IBusBambooEngine) getInputMode() int { } func (e *IBusBambooEngine) openLookupTable() { - var wmClasses = strings.Split(e.wmClasses, ":") - var wmClass = e.wmClasses + var wmClasses = strings.Split(e.getWmClass(), ":") + var wmClass = e.getWmClass() if len(wmClasses) == 2 { wmClass = wmClasses[1] } @@ -277,7 +285,7 @@ func (e *IBusBambooEngine) ltProcessKeyEvent(keyVal uint32, keyCode uint32, stat func (e *IBusBambooEngine) commitInputModeCandidate() { var im = e.inputModeLookupTable.CursorPos + 1 - e.config.InputModeMapping[e.wmClasses] = int(im) + e.config.InputModeMapping[e.getWmClass()] = int(im) saveConfig(e.config, e.engineName) e.propList = GetPropListByConfig(e.config) @@ -366,7 +374,11 @@ func (e *IBusBambooEngine) inBackspaceWhiteList() bool { } func (e *IBusBambooEngine) inBrowserList() bool { - return inStringList(DefaultBrowserList, e.wmClasses) + return inStringList(DefaultBrowserList, e.getWmClass()) +} + +func (e *IBusBambooEngine) getWmClass() string { + return x11GetFocusWindowClass() } func (e *IBusBambooEngine) checkInputMode(im int) bool { diff --git a/src/ibus-bamboo/x11.go b/src/ibus-bamboo/x11.go index 9b8db7b3..99038825 100644 --- a/src/ibus-bamboo/x11.go +++ b/src/ibus-bamboo/x11.go @@ -40,6 +40,8 @@ extern void x11SendShiftR(); extern void x11SendShiftLeft(int n, int r, int timeout); extern void setXIgnoreErrorHandler(); extern char* x11GetFocusWindowClass(); +extern void start_input_watching(); +extern void stop_input_watching(); */ import "C" import ( @@ -63,6 +65,14 @@ func mouse_click_handler() { var onMouseMove func() var onMouseClick func() +func startInputWatching() { + C.start_input_watching() +} + +func stopInputWatching() { + C.stop_input_watching() +} + func startMouseRecording() { C.mouse_recording_init() } @@ -128,7 +138,6 @@ func x11SendBackspace(n int, timeout int) { func x11GetFocusWindowClass() string { var wmClass = C.x11GetFocusWindowClass() if wmClass != nil { - defer C.free(unsafe.Pointer(wmClass)) return C.GoString(wmClass) } return "" diff --git a/src/ibus-bamboo/x11_prop.c b/src/ibus-bamboo/x11_prop.c index 92008d45..a9344d91 100644 --- a/src/ibus-bamboo/x11_prop.c +++ b/src/ibus-bamboo/x11_prop.c @@ -20,12 +20,17 @@ #include #include #include +#include #include +#include // strlen +#define MAX_TEXT_LEN 100 +static pthread_t th_input_watch; #define MaxPropertyLen 128 #define MaxWmClassesLen 5 static char * WM_CLASS = "WM_CLASS"; static char * WM_NAME = "WM_NAME"; +static char * text = NULL; static int ignore_x_error(Display *display, XErrorEvent *error) { return 0; @@ -59,7 +64,7 @@ char * x11GetStringProperty(Display *display, Window window, char * propName) { return NULL; } -char * x11GetFocusWindowClasses(Display *display, char * propName) { +char * x11GetFocusWindowClassByProp(Display *display, char * propName) { Window w; int revertTo; XGetInputFocus(display, &w, &revertTo); @@ -83,15 +88,79 @@ char * x11GetFocusWindowClasses(Display *display, char * propName) { return NULL; } +char * x11GetFocusWindowClassByDpy(Display *display) { + char * strClass = x11GetFocusWindowClassByProp(display, WM_CLASS); + if (strClass == NULL) { + strClass = x11GetFocusWindowClassByProp(display, "_GTK_APPLICATION_ID"); + } + return strClass; +} + char * x11GetFocusWindowClass() { - Display *display = XOpenDisplay(NULL); - if (!display) { + return text; +} + +static int input_watching = 0; +static int th_count = 0; +static void* thread_input_watching(void* data) +{ + XEvent event; + int x_old, y_old, x_root_old, y_root_old, rt; + unsigned int mask; + Window w, w_root_return, w_child_return; + Display * dpy; + + dpy = XOpenDisplay(NULL); + setXIgnoreErrorHandler(); + if (!dpy) { return NULL; } - char * strClass = x11GetFocusWindowClasses(display, WM_CLASS); - if (strClass == NULL) { - strClass = x11GetFocusWindowClasses(display, WM_NAME); + int revertTo; + XGetInputFocus(dpy, &w, &revertTo); + XSelectInput(dpy, w, FocusChangeMask); + char * name; + text = (char*)calloc(MAX_TEXT_LEN, sizeof(char)); + char * cl = x11GetFocusWindowClassByDpy(dpy); + if (cl != NULL) { + strcpy(text, cl); } - XCloseDisplay(display); - return strClass; + while (input_watching == 1) { + XNextEvent(dpy, &event); + /* text = (char*)calloc(MAX_TEXT_LEN, sizeof(char)); */ + memset(text, 0, MAX_TEXT_LEN * sizeof(char)); + if (event.type == FocusIn) { + cl = x11GetFocusWindowClassByDpy(dpy); + if (cl != NULL) { + strcpy(text, cl); + } + } + XSync(dpy, 0); + XGetInputFocus(dpy, &w, &revertTo); + XFetchName(dpy, w, &name); + /* printf("window:%lu name:%s class:%s\n", w, name, text); */ + XFree(name); + XSelectInput(dpy, w, FocusChangeMask); + } + input_watching = 0; + th_count--; + XCloseDisplay(dpy); + return NULL; +} + +void start_input_watching() +{ + setbuf(stdout, NULL); + setbuf(stderr, NULL); + if (input_watching || th_count) { + return; + } + XInitThreads(); + input_watching = 1; + th_count++; + pthread_create(&th_input_watch, NULL, &thread_input_watching, NULL); + pthread_detach(th_input_watch); +} + +void stop_input_watching() { + input_watching = 0; } From 3d27e24a29e58271b41c34a78acbe309aeb9c2b7 Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Thu, 11 Mar 2021 18:29:09 +0700 Subject: [PATCH 06/11] protocols: Add wlr-foreign-toplevel-management-unstable-v1 --- src/github.com/dkolbly/wl | 1 + src/github.com/dkolbly/wl-scanner | 1 + src/ibus-bamboo/client.go | 575 ++++++++++++++++++ src/ibus-bamboo/main.go | 3 + src/ibus-bamboo/wl_introspect.go | 79 +++ ...oreign-toplevel-management-unstable-v1.xml | 270 ++++++++ 6 files changed, 929 insertions(+) create mode 160000 src/github.com/dkolbly/wl create mode 160000 src/github.com/dkolbly/wl-scanner create mode 100644 src/ibus-bamboo/client.go create mode 100644 src/ibus-bamboo/wl_introspect.go create mode 100644 wlr-foreign-toplevel-management-unstable-v1.xml diff --git a/src/github.com/dkolbly/wl b/src/github.com/dkolbly/wl new file mode 160000 index 00000000..b06f57e7 --- /dev/null +++ b/src/github.com/dkolbly/wl @@ -0,0 +1 @@ +Subproject commit b06f57e7e2e603371342106c436b79bfc2bf00cf diff --git a/src/github.com/dkolbly/wl-scanner b/src/github.com/dkolbly/wl-scanner new file mode 160000 index 00000000..635e0a21 --- /dev/null +++ b/src/github.com/dkolbly/wl-scanner @@ -0,0 +1 @@ +Subproject commit 635e0a21c6427d55da1f6678c61d3230a59eeecf diff --git a/src/ibus-bamboo/client.go b/src/ibus-bamboo/client.go new file mode 100644 index 00000000..e648d3f5 --- /dev/null +++ b/src/ibus-bamboo/client.go @@ -0,0 +1,575 @@ +// package wl acts as a client for the wlr_foreign_toplevel_management_unstable_v1 wayland protocol. + +// generated by wl-scanner +// https://github.com/dkolbly/wl-scanner +// from: https://raw.githubusercontent.com/swaywm/wlr-protocols/master/unstable/wlr-foreign-toplevel-management-unstable-v1.xml +// on 2021-03-11 15:02:55 +0700 +package main + +import ( + "sync" + + wl "github.com/dkolbly/wl" +) + +type ZwlrForeignToplevelManagerV1ToplevelEvent struct { + Toplevel *ZwlrForeignToplevelHandleV1 +} + +type ZwlrForeignToplevelManagerV1ToplevelHandler interface { + HandleZwlrForeignToplevelManagerV1Toplevel(ZwlrForeignToplevelManagerV1ToplevelEvent) +} + +func (p *ZwlrForeignToplevelManagerV1) AddToplevelHandler(h ZwlrForeignToplevelManagerV1ToplevelHandler) { + if h != nil { + p.mu.Lock() + p.toplevelHandlers = append(p.toplevelHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelManagerV1) RemoveToplevelHandler(h ZwlrForeignToplevelManagerV1ToplevelHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.toplevelHandlers { + if e == h { + p.toplevelHandlers = append(p.toplevelHandlers[:i], p.toplevelHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelManagerV1FinishedEvent struct { +} + +type ZwlrForeignToplevelManagerV1FinishedHandler interface { + HandleZwlrForeignToplevelManagerV1Finished(ZwlrForeignToplevelManagerV1FinishedEvent) +} + +func (p *ZwlrForeignToplevelManagerV1) AddFinishedHandler(h ZwlrForeignToplevelManagerV1FinishedHandler) { + if h != nil { + p.mu.Lock() + p.finishedHandlers = append(p.finishedHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelManagerV1) RemoveFinishedHandler(h ZwlrForeignToplevelManagerV1FinishedHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.finishedHandlers { + if e == h { + p.finishedHandlers = append(p.finishedHandlers[:i], p.finishedHandlers[i+1:]...) + break + } + } +} + +func (p *ZwlrForeignToplevelManagerV1) Dispatch(event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.toplevelHandlers) > 0 { + ev := ZwlrForeignToplevelManagerV1ToplevelEvent{} + ev.Toplevel = event.Proxy(p.Context()).(*ZwlrForeignToplevelHandleV1) + p.mu.RLock() + for _, h := range p.toplevelHandlers { + h.HandleZwlrForeignToplevelManagerV1Toplevel(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.finishedHandlers) > 0 { + ev := ZwlrForeignToplevelManagerV1FinishedEvent{} + p.mu.RLock() + for _, h := range p.finishedHandlers { + h.HandleZwlrForeignToplevelManagerV1Finished(ev) + } + p.mu.RUnlock() + } + } +} + +type ZwlrForeignToplevelManagerV1 struct { + wl.BaseProxy + mu sync.RWMutex + toplevelHandlers []ZwlrForeignToplevelManagerV1ToplevelHandler + finishedHandlers []ZwlrForeignToplevelManagerV1FinishedHandler +} + +func NewZwlrForeignToplevelManagerV1(ctx *wl.Context) *ZwlrForeignToplevelManagerV1 { + ret := new(ZwlrForeignToplevelManagerV1) + ctx.Register(ret) + return ret +} + +// Stop will stop sending events. +// +// +// Indicates the client no longer wishes to receive events for new toplevels. +// However the compositor may emit further toplevel_created events, until +// the finished event is emitted. +// +// The client must not send any more requests after this one. +// +func (p *ZwlrForeignToplevelManagerV1) Stop() error { + return p.Context().SendRequest(p, 0) +} + +type ZwlrForeignToplevelHandleV1TitleEvent struct { + Title string +} + +type ZwlrForeignToplevelHandleV1TitleHandler interface { + HandleZwlrForeignToplevelHandleV1Title(ZwlrForeignToplevelHandleV1TitleEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddTitleHandler(h ZwlrForeignToplevelHandleV1TitleHandler) { + if h != nil { + p.mu.Lock() + p.titleHandlers = append(p.titleHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveTitleHandler(h ZwlrForeignToplevelHandleV1TitleHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.titleHandlers { + if e == h { + p.titleHandlers = append(p.titleHandlers[:i], p.titleHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1AppIdEvent struct { + AppId string +} + +type ZwlrForeignToplevelHandleV1AppIdHandler interface { + HandleZwlrForeignToplevelHandleV1AppId(ZwlrForeignToplevelHandleV1AppIdEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddAppIdHandler(h ZwlrForeignToplevelHandleV1AppIdHandler) { + if h != nil { + p.mu.Lock() + p.appIdHandlers = append(p.appIdHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveAppIdHandler(h ZwlrForeignToplevelHandleV1AppIdHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.appIdHandlers { + if e == h { + p.appIdHandlers = append(p.appIdHandlers[:i], p.appIdHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1OutputEnterEvent struct { + Output *wl.Output +} + +type ZwlrForeignToplevelHandleV1OutputEnterHandler interface { + HandleZwlrForeignToplevelHandleV1OutputEnter(ZwlrForeignToplevelHandleV1OutputEnterEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddOutputEnterHandler(h ZwlrForeignToplevelHandleV1OutputEnterHandler) { + if h != nil { + p.mu.Lock() + p.outputEnterHandlers = append(p.outputEnterHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveOutputEnterHandler(h ZwlrForeignToplevelHandleV1OutputEnterHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.outputEnterHandlers { + if e == h { + p.outputEnterHandlers = append(p.outputEnterHandlers[:i], p.outputEnterHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1OutputLeaveEvent struct { + Output *wl.Output +} + +type ZwlrForeignToplevelHandleV1OutputLeaveHandler interface { + HandleZwlrForeignToplevelHandleV1OutputLeave(ZwlrForeignToplevelHandleV1OutputLeaveEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddOutputLeaveHandler(h ZwlrForeignToplevelHandleV1OutputLeaveHandler) { + if h != nil { + p.mu.Lock() + p.outputLeaveHandlers = append(p.outputLeaveHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveOutputLeaveHandler(h ZwlrForeignToplevelHandleV1OutputLeaveHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.outputLeaveHandlers { + if e == h { + p.outputLeaveHandlers = append(p.outputLeaveHandlers[:i], p.outputLeaveHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1StateEvent struct { + State []int32 +} + +type ZwlrForeignToplevelHandleV1StateHandler interface { + HandleZwlrForeignToplevelHandleV1State(ZwlrForeignToplevelHandleV1StateEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddStateHandler(h ZwlrForeignToplevelHandleV1StateHandler) { + if h != nil { + p.mu.Lock() + p.stateHandlers = append(p.stateHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveStateHandler(h ZwlrForeignToplevelHandleV1StateHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.stateHandlers { + if e == h { + p.stateHandlers = append(p.stateHandlers[:i], p.stateHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1DoneEvent struct { +} + +type ZwlrForeignToplevelHandleV1DoneHandler interface { + HandleZwlrForeignToplevelHandleV1Done(ZwlrForeignToplevelHandleV1DoneEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddDoneHandler(h ZwlrForeignToplevelHandleV1DoneHandler) { + if h != nil { + p.mu.Lock() + p.doneHandlers = append(p.doneHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveDoneHandler(h ZwlrForeignToplevelHandleV1DoneHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.doneHandlers { + if e == h { + p.doneHandlers = append(p.doneHandlers[:i], p.doneHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1ClosedEvent struct { +} + +type ZwlrForeignToplevelHandleV1ClosedHandler interface { + HandleZwlrForeignToplevelHandleV1Closed(ZwlrForeignToplevelHandleV1ClosedEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddClosedHandler(h ZwlrForeignToplevelHandleV1ClosedHandler) { + if h != nil { + p.mu.Lock() + p.closedHandlers = append(p.closedHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveClosedHandler(h ZwlrForeignToplevelHandleV1ClosedHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.closedHandlers { + if e == h { + p.closedHandlers = append(p.closedHandlers[:i], p.closedHandlers[i+1:]...) + break + } + } +} + +type ZwlrForeignToplevelHandleV1ParentEvent struct { + Parent *ZwlrForeignToplevelHandleV1 +} + +type ZwlrForeignToplevelHandleV1ParentHandler interface { + HandleZwlrForeignToplevelHandleV1Parent(ZwlrForeignToplevelHandleV1ParentEvent) +} + +func (p *ZwlrForeignToplevelHandleV1) AddParentHandler(h ZwlrForeignToplevelHandleV1ParentHandler) { + if h != nil { + p.mu.Lock() + p.parentHandlers = append(p.parentHandlers, h) + p.mu.Unlock() + } +} + +func (p *ZwlrForeignToplevelHandleV1) RemoveParentHandler(h ZwlrForeignToplevelHandleV1ParentHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.parentHandlers { + if e == h { + p.parentHandlers = append(p.parentHandlers[:i], p.parentHandlers[i+1:]...) + break + } + } +} + +func (p *ZwlrForeignToplevelHandleV1) Dispatch(event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.titleHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1TitleEvent{} + ev.Title = event.String() + p.mu.RLock() + for _, h := range p.titleHandlers { + h.HandleZwlrForeignToplevelHandleV1Title(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.appIdHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1AppIdEvent{} + ev.AppId = event.String() + p.mu.RLock() + for _, h := range p.appIdHandlers { + h.HandleZwlrForeignToplevelHandleV1AppId(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.outputEnterHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1OutputEnterEvent{} + ev.Output = event.Proxy(p.Context()).(*wl.Output) + p.mu.RLock() + for _, h := range p.outputEnterHandlers { + h.HandleZwlrForeignToplevelHandleV1OutputEnter(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.outputLeaveHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1OutputLeaveEvent{} + ev.Output = event.Proxy(p.Context()).(*wl.Output) + p.mu.RLock() + for _, h := range p.outputLeaveHandlers { + h.HandleZwlrForeignToplevelHandleV1OutputLeave(ev) + } + p.mu.RUnlock() + } + case 4: + if len(p.stateHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1StateEvent{} + ev.State = event.Array() + p.mu.RLock() + for _, h := range p.stateHandlers { + h.HandleZwlrForeignToplevelHandleV1State(ev) + } + p.mu.RUnlock() + } + case 5: + if len(p.doneHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1DoneEvent{} + p.mu.RLock() + for _, h := range p.doneHandlers { + h.HandleZwlrForeignToplevelHandleV1Done(ev) + } + p.mu.RUnlock() + } + case 6: + if len(p.closedHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1ClosedEvent{} + p.mu.RLock() + for _, h := range p.closedHandlers { + h.HandleZwlrForeignToplevelHandleV1Closed(ev) + } + p.mu.RUnlock() + } + case 7: + if len(p.parentHandlers) > 0 { + ev := ZwlrForeignToplevelHandleV1ParentEvent{} + ev.Parent = event.Proxy(p.Context()).(*ZwlrForeignToplevelHandleV1) + p.mu.RLock() + for _, h := range p.parentHandlers { + h.HandleZwlrForeignToplevelHandleV1Parent(ev) + } + p.mu.RUnlock() + } + } +} + +type ZwlrForeignToplevelHandleV1 struct { + wl.BaseProxy + mu sync.RWMutex + titleHandlers []ZwlrForeignToplevelHandleV1TitleHandler + appIdHandlers []ZwlrForeignToplevelHandleV1AppIdHandler + outputEnterHandlers []ZwlrForeignToplevelHandleV1OutputEnterHandler + outputLeaveHandlers []ZwlrForeignToplevelHandleV1OutputLeaveHandler + stateHandlers []ZwlrForeignToplevelHandleV1StateHandler + doneHandlers []ZwlrForeignToplevelHandleV1DoneHandler + closedHandlers []ZwlrForeignToplevelHandleV1ClosedHandler + parentHandlers []ZwlrForeignToplevelHandleV1ParentHandler +} + +func NewZwlrForeignToplevelHandleV1(ctx *wl.Context) *ZwlrForeignToplevelHandleV1 { + ret := new(ZwlrForeignToplevelHandleV1) + ctx.Register(ret) + return ret +} + +// SetMaximized will requests that the toplevel be maximized. +// +// +// Requests that the toplevel be maximized. If the maximized state actually +// changes, this will be indicated by the state event. +// +func (p *ZwlrForeignToplevelHandleV1) SetMaximized() error { + return p.Context().SendRequest(p, 0) +} + +// UnsetMaximized will requests that the toplevel be unmaximized. +// +// +// Requests that the toplevel be unmaximized. If the maximized state actually +// changes, this will be indicated by the state event. +// +func (p *ZwlrForeignToplevelHandleV1) UnsetMaximized() error { + return p.Context().SendRequest(p, 1) +} + +// SetMinimized will requests that the toplevel be minimized. +// +// +// Requests that the toplevel be minimized. If the minimized state actually +// changes, this will be indicated by the state event. +// +func (p *ZwlrForeignToplevelHandleV1) SetMinimized() error { + return p.Context().SendRequest(p, 2) +} + +// UnsetMinimized will requests that the toplevel be unminimized. +// +// +// Requests that the toplevel be unminimized. If the minimized state actually +// changes, this will be indicated by the state event. +// +func (p *ZwlrForeignToplevelHandleV1) UnsetMinimized() error { + return p.Context().SendRequest(p, 3) +} + +// Activate will activate the toplevel. +// +// +// Request that this toplevel be activated on the given seat. +// There is no guarantee the toplevel will be actually activated. +// +func (p *ZwlrForeignToplevelHandleV1) Activate(seat *wl.Seat) error { + return p.Context().SendRequest(p, 4, seat) +} + +// Close will request that the toplevel be closed. +// +// +// Send a request to the toplevel to close itself. The compositor would +// typically use a shell-specific method to carry out this request, for +// example by sending the xdg_toplevel.close event. However, this gives +// no guarantees the toplevel will actually be destroyed. If and when +// this happens, the zwlr_foreign_toplevel_handle_v1.closed event will +// be emitted. +// +func (p *ZwlrForeignToplevelHandleV1) Close() error { + return p.Context().SendRequest(p, 5) +} + +// SetRectangle will the rectangle which represents the toplevel. +// +// +// The rectangle of the surface specified in this request corresponds to +// the place where the app using this protocol represents the given toplevel. +// It can be used by the compositor as a hint for some operations, e.g +// minimizing. The client is however not required to set this, in which +// case the compositor is free to decide some default value. +// +// If the client specifies more than one rectangle, only the last one is +// considered. +// +// The dimensions are given in surface-local coordinates. +// Setting width=height=0 removes the already-set rectangle. +// +func (p *ZwlrForeignToplevelHandleV1) SetRectangle(surface *wl.Surface, x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 6, surface, x, y, width, height) +} + +// Destroy will destroy the zwlr_foreign_toplevel_handle_v1 object. +// +// +// Destroys the zwlr_foreign_toplevel_handle_v1 object. +// +// This request should be called either when the client does not want to +// use the toplevel anymore or after the closed event to finalize the +// destruction of the object. +// +func (p *ZwlrForeignToplevelHandleV1) Destroy() error { + return p.Context().SendRequest(p, 7) +} + +// SetFullscreen will request that the toplevel be fullscreened. +// +// +// Requests that the toplevel be fullscreened on the given output. If the +// fullscreen state and/or the outputs the toplevel is visible on actually +// change, this will be indicated by the state and output_enter/leave +// events. +// +// The output parameter is only a hint to the compositor. Also, if output +// is NULL, the compositor should decide which output the toplevel will be +// fullscreened on, if at all. +// +func (p *ZwlrForeignToplevelHandleV1) SetFullscreen(output *wl.Output) error { + return p.Context().SendRequest(p, 8, output) +} + +// UnsetFullscreen will request that the toplevel be unfullscreened. +// +// +// Requests that the toplevel be unfullscreened. If the fullscreen state +// actually changes, this will be indicated by the state event. +// +func (p *ZwlrForeignToplevelHandleV1) UnsetFullscreen() error { + return p.Context().SendRequest(p, 9) +} + +const ( + ZwlrForeignToplevelHandleV1StateMaximized = 0 + ZwlrForeignToplevelHandleV1StateMinimized = 1 + ZwlrForeignToplevelHandleV1StateActivated = 2 + ZwlrForeignToplevelHandleV1StateFullscreen = 3 +) + +const ( + ZwlrForeignToplevelHandleV1ErrorInvalidRectangle = 0 +) diff --git a/src/ibus-bamboo/main.go b/src/ibus-bamboo/main.go index ec299422..ca0870c3 100644 --- a/src/ibus-bamboo/main.go +++ b/src/ibus-bamboo/main.go @@ -54,6 +54,9 @@ func main() { if *embedded { os.Chdir(DataDir) } + if isWayland && !isGnome { + go wlGetFocusWindowClass() + } if *version { fmt.Println(Version) } else if *embedded { diff --git a/src/ibus-bamboo/wl_introspect.go b/src/ibus-bamboo/wl_introspect.go new file mode 100644 index 00000000..de64d02e --- /dev/null +++ b/src/ibus-bamboo/wl_introspect.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + + wl "github.com/dkolbly/wl" +) + +var wlAppId string + +func wlGetFocusWindowClass() error { + display, err := wl.Connect("") + if err != nil { + return fmt.Errorf("Connect to Wayland server failed %s", err) + } + registry, err := display.GetRegistry() + if err != nil { + return fmt.Errorf("Display.GetRegistry failed : %s", err) + } + _, err = display.Sync() + if err != nil { + return fmt.Errorf("Display.Sync failed %s", err) + } + appIdChan := make(chan string, 10) + rgeChan := make(chan wl.RegistryGlobalEvent) + rgeHandler := registrar{rgeChan} + registry.AddGlobalHandler(rgeHandler) + for { + select { + case ev := <-rgeChan: + if err := registerInterface(registry, ev, display.Context(), appIdChan); err != nil { + return err + } + case wlAppId = <-appIdChan: + case display.Context().Dispatch() <- struct{}{}: + } + } + registry.RemoveGlobalHandler(rgeHandler) + display.Context().Close() + return nil +} + +func registerInterface(registry *wl.Registry, ev wl.RegistryGlobalEvent, ctx *wl.Context, appIdChan chan string) error { + switch ev.Interface { + case "zwlr_foreign_toplevel_manager_v1": + manager := NewZwlrForeignToplevelManagerV1(ctx) + manager.AddToplevelHandler(toplevelHandlers{appIdChan}) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, manager) + if err != nil { + return fmt.Errorf("Unable to bind ZwlrForeignToplevelManagerV1 interface: %s", err) + } + } + return nil +} + +type registrar struct { + ch chan wl.RegistryGlobalEvent +} + +func (r registrar) HandleRegistryGlobal(ev wl.RegistryGlobalEvent) { + r.ch <- ev +} +type toplevelHandlers struct { + ch chan string +} + +func (t toplevelHandlers) HandleZwlrForeignToplevelManagerV1Toplevel(ev ZwlrForeignToplevelManagerV1ToplevelEvent) { + ev.Toplevel.AddAppIdHandler(appIdHandler{t.ch}) +} + +type appIdHandler struct { + ch chan string +} + +func (a appIdHandler) HandleZwlrForeignToplevelHandleV1AppId(ev ZwlrForeignToplevelHandleV1AppIdEvent) { + print(ev.AppId) + a.ch <- ev.AppId +} + diff --git a/wlr-foreign-toplevel-management-unstable-v1.xml b/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 00000000..10813371 --- /dev/null +++ b/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,270 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + + + From 93b2a7311debf91ebf5aed7a182916825dd31570 Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Thu, 11 Mar 2021 18:31:02 +0700 Subject: [PATCH 07/11] support Gnome Introspect API --- src/ibus-bamboo/engine_utils.go | 13 ++++++- src/ibus-bamboo/gnome_introspect.go | 37 +++++++++++++++++++ .../{x11_prop.c => x11_introspect.c} | 0 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/ibus-bamboo/gnome_introspect.go rename src/ibus-bamboo/{x11_prop.c => x11_introspect.c} (100%) diff --git a/src/ibus-bamboo/engine_utils.go b/src/ibus-bamboo/engine_utils.go index e9a01547..aa1f828c 100644 --- a/src/ibus-bamboo/engine_utils.go +++ b/src/ibus-bamboo/engine_utils.go @@ -378,7 +378,18 @@ func (e *IBusBambooEngine) inBrowserList() bool { } func (e *IBusBambooEngine) getWmClass() string { - return x11GetFocusWindowClass() + var wmClass string + if isWayland { + if isGnome { + wmClass = gnomeGetFocusWindowClass() + } else { + wmClass = wlAppId + } + } + if wmClass == "" { + wmClass = x11GetFocusWindowClass() + } + return wmClass } func (e *IBusBambooEngine) checkInputMode(im int) bool { diff --git a/src/ibus-bamboo/gnome_introspect.go b/src/ibus-bamboo/gnome_introspect.go new file mode 100644 index 00000000..f7068214 --- /dev/null +++ b/src/ibus-bamboo/gnome_introspect.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/godbus/dbus" +) + +func gnomeGetFocusWindowClass() string { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + defer conn.Close() + + js_code := ` + global._ib_current_window = () => { + var window_list = global.get_window_actors(); + var active_window_actor = window_list.find(window => window.meta_window.has_focus()); + var active_window = active_window_actor.get_meta_window(); + var vm_class = active_window.get_wm_class(); + var title = active_window.get_title(); + var result = vm_class; + return result; + } + ` + obj := conn.Object("org.gnome.Shell", "/org/gnome/Shell") + call := obj.Call("org.gnome.Shell.Eval", 0, js_code) + if call.Err != nil { + panic(call.Err) + } + var s string + var ok bool + err = obj.Call("org.gnome.Shell.Eval", 0, "global._ib_current_window()").Store(&ok, &s) + if (err != nil) { + panic(err) + } + return s +} diff --git a/src/ibus-bamboo/x11_prop.c b/src/ibus-bamboo/x11_introspect.c similarity index 100% rename from src/ibus-bamboo/x11_prop.c rename to src/ibus-bamboo/x11_introspect.c From e093616dddb0fd167d72f01ec83f22a2061c6f2b Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Thu, 11 Mar 2021 20:37:31 +0700 Subject: [PATCH 08/11] add dependencies --- src/github.com/dkolbly/wl | 1 - src/github.com/dkolbly/wl-scanner | 1 - src/github.com/dkolbly/wl-scanner/LICENSE | 25 + src/github.com/dkolbly/wl-scanner/README.md | 60 + src/github.com/dkolbly/wl-scanner/TODO | 2 + .../dkolbly/wl-scanner/wl-scanner.go | 635 +++ src/github.com/dkolbly/wl/LICENSE | 29 + src/github.com/dkolbly/wl/README.md | 25 + src/github.com/dkolbly/wl/client.go | 4079 +++++++++++++++++ src/github.com/dkolbly/wl/common.go | 55 + src/github.com/dkolbly/wl/context.go | 129 + src/github.com/dkolbly/wl/event.go | 121 + src/github.com/dkolbly/wl/request.go | 112 + src/github.com/dkolbly/wl/screenshooter.go | 24 + .../dkolbly/wl/text_cursor_position.go | 19 + src/github.com/dkolbly/wl/ui/bgra.go | 180 + src/github.com/dkolbly/wl/ui/configure.go | 112 + src/github.com/dkolbly/wl/ui/display.go | 360 ++ .../dkolbly/wl/ui/examples/img/bsd_daemon.jpg | Bin 0 -> 26609 bytes .../dkolbly/wl/ui/examples/img/image.go | 38 + .../dkolbly/wl/ui/examples/img/img.go | 84 + src/github.com/dkolbly/wl/ui/utils.go | 27 + src/github.com/dkolbly/wl/ui/window.go | 112 + src/github.com/dkolbly/wl/utils.go | 59 + src/github.com/dkolbly/wl/utils_test.go | 35 + .../dkolbly/wl/xdg-unstable-v6/shell.go | 1060 +++++ src/github.com/dkolbly/wl/xdg/shell.go | 1116 +++++ 27 files changed, 8498 insertions(+), 2 deletions(-) delete mode 160000 src/github.com/dkolbly/wl delete mode 160000 src/github.com/dkolbly/wl-scanner create mode 100644 src/github.com/dkolbly/wl-scanner/LICENSE create mode 100644 src/github.com/dkolbly/wl-scanner/README.md create mode 100644 src/github.com/dkolbly/wl-scanner/TODO create mode 100644 src/github.com/dkolbly/wl-scanner/wl-scanner.go create mode 100644 src/github.com/dkolbly/wl/LICENSE create mode 100644 src/github.com/dkolbly/wl/README.md create mode 100644 src/github.com/dkolbly/wl/client.go create mode 100644 src/github.com/dkolbly/wl/common.go create mode 100644 src/github.com/dkolbly/wl/context.go create mode 100644 src/github.com/dkolbly/wl/event.go create mode 100644 src/github.com/dkolbly/wl/request.go create mode 100644 src/github.com/dkolbly/wl/screenshooter.go create mode 100644 src/github.com/dkolbly/wl/text_cursor_position.go create mode 100644 src/github.com/dkolbly/wl/ui/bgra.go create mode 100644 src/github.com/dkolbly/wl/ui/configure.go create mode 100644 src/github.com/dkolbly/wl/ui/display.go create mode 100644 src/github.com/dkolbly/wl/ui/examples/img/bsd_daemon.jpg create mode 100644 src/github.com/dkolbly/wl/ui/examples/img/image.go create mode 100644 src/github.com/dkolbly/wl/ui/examples/img/img.go create mode 100644 src/github.com/dkolbly/wl/ui/utils.go create mode 100644 src/github.com/dkolbly/wl/ui/window.go create mode 100644 src/github.com/dkolbly/wl/utils.go create mode 100644 src/github.com/dkolbly/wl/utils_test.go create mode 100644 src/github.com/dkolbly/wl/xdg-unstable-v6/shell.go create mode 100644 src/github.com/dkolbly/wl/xdg/shell.go diff --git a/src/github.com/dkolbly/wl b/src/github.com/dkolbly/wl deleted file mode 160000 index b06f57e7..00000000 --- a/src/github.com/dkolbly/wl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b06f57e7e2e603371342106c436b79bfc2bf00cf diff --git a/src/github.com/dkolbly/wl-scanner b/src/github.com/dkolbly/wl-scanner deleted file mode 160000 index 635e0a21..00000000 --- a/src/github.com/dkolbly/wl-scanner +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 635e0a21c6427d55da1f6678c61d3230a59eeecf diff --git a/src/github.com/dkolbly/wl-scanner/LICENSE b/src/github.com/dkolbly/wl-scanner/LICENSE new file mode 100644 index 00000000..65d3adea --- /dev/null +++ b/src/github.com/dkolbly/wl-scanner/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2016, sternix +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/github.com/dkolbly/wl-scanner/README.md b/src/github.com/dkolbly/wl-scanner/README.md new file mode 100644 index 00000000..ec0d8a92 --- /dev/null +++ b/src/github.com/dkolbly/wl-scanner/README.md @@ -0,0 +1,60 @@ +wl-scanner +========== + +The `wl-scanner` project is designed parse and generate Go client code +from a wayland protocol file. + +In its base form, it produces the `client.go` code in +`github.com/dkolbly/wl` from the canonical definition of the wayland +protocol at +https://cgit.freedesktop.org/wayland/wayland/plain/protocol/wayland.xml + +It is similar in concept to the wayland-scanner tool which was +developed for generating the C client library. + +This is a hobby project intended to help people understand the +wayland.xml protocol file and the generation go code, as well as to +help build client libraries for protocols around Wayland. + +## Usage + +``` +go get github.com/dkolbly/wl-scanner + +# generate a client for the base protocol +wl-scanner -source https://cgit.freedesktop.org/wayland/wayland/plain/protocol/wayland.xml \ + -output $GOPATH/src/github.com/dkolbly/wl/client.go + +# generate a client for the xdg-shell protocol +wl-scanner -pkg xdg \ + -source https://raw.githubusercontent.com/wayland-project/wayland-protocols/master/stable/xdg-shell/xdg-shell.xml \ + -output $GOPATH/src/github.com/dkolbly/wl/xdg/shell.go +``` + +### Unstable Protocols + +Unstable protocols can be generated in a way that makes it relatively +easy to upgrade to newer versions of the protocol, under the assumption +that changes are relatively small. This is done by stripping the "_vN" suffix, +as for example: + +``` +wl-scanner -pkg zxdg \ + -source https://raw.githubusercontent.com/wayland-project/wayland-protocols/master/unstable/xdg-shell/xdg-shell-unstable-v6.xml \ + -output $GOPATH/src/github.com/dkolbly/wl/xdg-unstable-v6/shell.go +``` + +The `zxdg` package name is preserved, but this can be imported in the client application +as: + +``` +import ( + xdg "github.com/dkolbly/wl/xdg-unstable-v6" +) +``` + +and then most code will hopefully be able to port from one major +change to another. Obviously, mileage will vary depending on the +extent of the change. + + diff --git a/src/github.com/dkolbly/wl-scanner/TODO b/src/github.com/dkolbly/wl-scanner/TODO new file mode 100644 index 00000000..5462b3c8 --- /dev/null +++ b/src/github.com/dkolbly/wl-scanner/TODO @@ -0,0 +1,2 @@ +allow-null +bitfield diff --git a/src/github.com/dkolbly/wl-scanner/wl-scanner.go b/src/github.com/dkolbly/wl-scanner/wl-scanner.go new file mode 100644 index 00000000..cba23095 --- /dev/null +++ b/src/github.com/dkolbly/wl-scanner/wl-scanner.go @@ -0,0 +1,635 @@ +package main + +import ( + "bytes" + "encoding/xml" + "flag" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "strings" + "text/template" + "time" +) + +var source = flag.String("source", "", "Where to get the XML from") +var output = flag.String("output", "", "Where to put the output go file") +var pkgName = flag.String("pkg", "wl", "Name of the package") +var unstable = flag.String("unstable", "", "Unstable suffix name to strip (e.g., v6)") + +// xml types +type Protocol struct { + XMLName xml.Name `xml:"protocol"` + Name string `xml:"name,attr"` + Copyright string `xml:"copyright"` + Interfaces []Interface `xml:"interface"` +} + +type Description struct { + XMLName xml.Name `xml:"description"` + Summary string `xml:"summary,attr"` + Text string `xml:",chardata"` +} + +type Interface struct { + XMLName xml.Name `xml:"interface"` + Name string `xml:"name,attr"` + Version int `xml:"version,attr"` + Since int `xml:"since,attr"` // maybe in future versions + Description Description `xml:"description"` + Requests []Request `xml:"request"` + Events []Event `xml:"event"` + Enums []Enum `xml:"enum"` +} + +type Request struct { + XMLName xml.Name `xml:"request"` + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Since int `xml:"since,attr"` + Description Description `xml:"description"` + Args []Arg `xml:"arg"` +} + +type Arg struct { + XMLName xml.Name `xml:"arg"` + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Interface string `xml:"interface,attr"` + Enum string `xml:"enum,attr"` + AllowNull bool `xml:"allow-null,attr"` + Summary string `xml:"summary,attr"` +} + +type Event struct { + XMLName xml.Name `xml:"event"` + Name string `xml:"name,attr"` + Since int `xml:"since,attr"` + Description Description `xml:"description"` + Args []Arg `xml:"arg"` +} + +type Enum struct { + XMLName xml.Name `xml:"enum"` + Name string `xml:"name,attr"` + BitField bool `xml:"bitfield,attr"` + Description Description `xml:"description"` + Entries []Entry `xml:"entry"` +} + +type Entry struct { + XMLName xml.Name `xml:"entry"` + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` + Summary string `xml:"summary,attr"` +} + +// go types +type ( + GoInterface struct { + Name string + WL string + WlInterface Interface + Requests []GoRequest + Events []GoEvent + Enums []GoEnum + } + + GoRequest struct { + Name string + IfaceName string + Params string + Returns string + Args string + HasNewId bool + NewIdInterface string + Order int + Summary string + Description string + } + + GoEvent struct { + WL string + Name string + IfaceName string + PName string + EName string + Args []GoArg + } + + GoArg struct { + Name string + Type string + PName string + BufMethod string + } + + GoEnum struct { + Name string + IfaceName string + Entries []GoEntry + } + + GoEntry struct { + Name string + Value string + } +) + +var ( + wlTypes map[string]string = map[string]string{ + "int": "int32", + "uint": "uint32", + "string": "string", + "fd": "uintptr", + "fixed": "float32", + "array": "[]int32", + } + + // sync with event.go + bufTypesMap map[string]string = map[string]string{ + "int32": "Int32()", + "uint32": "Uint32()", + "string": "String()", + "float32": "Float32()", + "[]int32": "Array()", + "uintptr": "FD()", + } + + wlNames map[string]string + fileBuffer = &bytes.Buffer{} +) + +func sourceData() io.Reader { + if *source == "" { + log.Fatal("Must specify a -source") + } + + if strings.HasPrefix(*source, "http:") || strings.HasPrefix(*source, "https:") { + resp, err := http.Get(*source) + if err != nil { + log.Fatal(err) + } + return resp.Body + } else { + f, err := os.Open(*source) + if err != nil { + log.Fatal(err) + } + return f + } +} + +var wlPrefix string + +func main() { + log.SetFlags(0) + flag.Parse() + + dest := *output + if dest == "" { + log.Fatal("Must specify -output") + } + + var protocol Protocol + + file := sourceData() + + err := decodeWlXML(file, &protocol) + if err != nil { + log.Fatal(err) + } + + wlNames = make(map[string]string) + wlPrefix = "" + + if protocol.Name != "wayland" { + for _, inherit := range inheritedNames { + wlNames[inherit] = "wl." + CamelCase(inherit) + } + } + if *pkgName != "wl" { + wlPrefix = "wl." + trimPrefix = *pkgName + "_" + } + if *unstable != "" { + ifTrimSuffix = "_" + *unstable + } + + // required for request and event parameters + for _, iface := range protocol.Interfaces { + caseAndRegister(stripUnstable(iface.Name)) + } + + fmt.Fprintf(fileBuffer, "// package %s acts as a client for the %s wayland protocol.\n\n", + *pkgName, + protocol.Name) + + fmt.Fprintf(fileBuffer, "// generated by wl-scanner\n// https://github.com/dkolbly/wl-scanner\n") + fmt.Fprintf(fileBuffer, "// from: %s\n", *source) + t := time.Now() + fmt.Fprintf(fileBuffer, "// on %s\n", t.Format("2006-01-02 15:04:05 -0700")) + fmt.Fprintf(fileBuffer, "package %s\n", *pkgName) + fmt.Fprintf(fileBuffer, "import (\n") + fmt.Fprintf(fileBuffer, " \"sync\"\n") + if *pkgName != "wl" { + fmt.Fprintf(fileBuffer, " \"github.com/dkolbly/wl\"\n") + } + fmt.Fprintf(fileBuffer, ")\n") + + for _, iface := range protocol.Interfaces { + goIface := GoInterface{ + Name: wlNames[stripUnstable(iface.Name)], + WlInterface: iface, + WL: wlPrefix, + } + + goIface.ProcessEvents() + goIface.Constructor() + goIface.ProcessRequests() + goIface.ProcessEnums() + } + + out, err := os.Create(dest) + if err != nil { + log.Fatal(err) + } + defer out.Close() + + fileBuffer.WriteTo(out) + + fmtFile() +} + +func decodeWlXML(file io.Reader, prot *Protocol) error { + err := xml.NewDecoder(file).Decode(&prot) + if err != nil { + return fmt.Errorf("Cannot decode wayland.xml: %s", err) + } + return nil +} + +// register names to map +func caseAndRegister(wlName string) string { + var orj string = wlName + wlName = CamelCase(wlName) + wlNames[orj] = wlName + return wlName +} + +func executeTemplate(name string, tpl string, data interface{}) { + tmpl := template.Must(template.New(name).Parse(tpl)) + err := tmpl.Execute(fileBuffer, data) + if err != nil { + log.Fatal(err) + } +} + +func (i *GoInterface) Constructor() { + executeTemplate("InterfaceTypeTemplate", ifaceTypeTemplate, i) + executeTemplate("InterfaceConstructorTemplate", ifaceConstructorTemplate, i) +} + +func (i *GoInterface) ProcessRequests() { + for order, wlReq := range i.WlInterface.Requests { + var ( + returns []string + params []string + sendRequestArgs []string // for sendRequest + ) + + req := GoRequest{ + Name: CamelCase(wlReq.Name), + IfaceName: stripUnstable(i.Name), + Order: order, + Summary: wlReq.Description.Summary, + Description: reflow(wlReq.Description.Text), + } + + for _, arg := range wlReq.Args { + if arg.Type == "new_id" { + if arg.Interface != "" { + newIdIface := wlNames[stripUnstable(arg.Interface)] + req.NewIdInterface = newIdIface + sendRequestArgs = append(params, wlPrefix+"Proxy(ret)") + req.HasNewId = true + + returns = append(returns, "*"+newIdIface) + } else { //special for registry.Bind + sendRequestArgs = append(sendRequestArgs, "iface") + sendRequestArgs = append(sendRequestArgs, "version") + sendRequestArgs = append(sendRequestArgs, arg.Name) + + params = append(params, "iface string") + params = append(params, "version uint32") + params = append(params, fmt.Sprintf("%s %sProxy", arg.Name, wlPrefix)) + } + } else if arg.Type == "object" && arg.Interface != "" { + paramTypeName := wlNames[stripUnstable(arg.Interface)] + params = append(params, fmt.Sprintf("%s *%s", arg.Name, paramTypeName)) + sendRequestArgs = append(sendRequestArgs, arg.Name) + /*} else if arg.Type == "uint" && arg.Enum != "" { + params = append(params, fmt.Sprintf("%s %s", arg.Name, enumArgName(ifaceName, arg.Enum))) + }*/ + } else { + sendRequestArgs = append(sendRequestArgs, arg.Name) + params = append(params, fmt.Sprintf("%s %s", arg.Name, wlTypes[arg.Type])) + } + } + + req.Params = strings.Join(params, ",") + + if len(sendRequestArgs) > 0 { + req.Args = "," + strings.Join(sendRequestArgs, ",") + } + + if len(returns) > 0 { // ( ret , error ) + req.Returns = fmt.Sprintf("(%s , error)", strings.Join(returns, ",")) + } else { // returns only error + req.Returns = "error" + } + + executeTemplate("RequestTemplate", requestTemplate, req) + i.Requests = append(i.Requests, req) + } +} + +func (i *GoInterface) ProcessEvents() { + // Event struct types + for _, wlEv := range i.WlInterface.Events { + ev := GoEvent{ + Name: CamelCase(wlEv.Name), + PName: snakeCase(wlEv.Name), + IfaceName: i.Name, + WL: wlPrefix, + } + ev.EName = i.Name + ev.Name + + for _, arg := range wlEv.Args { + goarg := GoArg{ + Name: CamelCase(arg.Name), + PName: snakeCase(arg.Name), + } + if t, ok := wlTypes[arg.Type]; ok { // if basic type + bufMethod, ok := bufTypesMap[t] + if !ok { + log.Printf("%s not registered", t) + } else { + goarg.BufMethod = bufMethod + } + /* + if arg.Type == "uint" && arg.Enum != "" { // enum type + enumTypeName := ifaceName + CamelCase(arg.Enum) + fmt.Fprintf(&eventBuffer, "%s %s\n", CamelCase(arg.Name), enumTypeName) + } else { + fmt.Fprintf(&eventBuffer, "%s %s\n", CamelCase(arg.Name), t) + }*/ + goarg.Type = t + } else { // interface type + if (arg.Type == "object" || arg.Type == "new_id") && arg.Interface != "" { + t = "*" + wlNames[stripUnstable(arg.Interface)] + goarg.BufMethod = fmt.Sprintf("%sProxy(p.Context()).(%s)", wlPrefix, t) + } else { + t = wlPrefix + "Proxy" + goarg.BufMethod = wlPrefix + "Proxy(p.Context())" + } + goarg.Type = t + } + + ev.Args = append(ev.Args, goarg) + } + + executeTemplate("EventTemplate", eventTemplate, ev) + executeTemplate("AddRemoveHandlerTemplate", ifaceAddRemoveHandlerTemplate, ev) + + i.Events = append(i.Events, ev) + } + + if len(i.Events) > 0 { + executeTemplate("InterfaceDispatchTemplate", ifaceDispatchTemplate, i) + } +} + +func (i *GoInterface) ProcessEnums() { + // Enums - Constants + for _, wlEnum := range i.WlInterface.Enums { + goEnum := GoEnum{ + Name: CamelCase(wlEnum.Name), + IfaceName: i.Name, + } + + for _, wlEntry := range wlEnum.Entries { + goEntry := GoEntry{ + Name: CamelCase(wlEntry.Name), + Value: wlEntry.Value, + } + goEnum.Entries = append(goEnum.Entries, goEntry) + } + + executeTemplate("InterfaceEnumsTemplate", ifaceEnums, goEnum) + } +} + +/* +func enumArgName(ifaceName, enumName string) string { + if strings.Index(enumName, ".") == -1 { + return ifaceName + CamelCase(enumName) + } + + parts := strings.Split(enumName, ".") + if len(parts) != 2 { + log.Fatalf("enum args must be \"interface.enum\" format: we get %s",enumName) + } + return CamelCase(parts[0]) + CamelCase(parts[1]) +} +*/ + +var trimPrefix = "wl_" +var ifTrimSuffix = "" + +func CamelCase(wlName string) string { + wlName = strings.TrimPrefix(wlName, trimPrefix) + + // replace all "_" chars to " " chars + wlName = strings.Replace(wlName, "_", " ", -1) + + // Capitalize first chars + wlName = strings.Title(wlName) + + // remove all spaces + wlName = strings.Replace(wlName, " ", "", -1) + + return wlName +} + +func snakeCase(wlName string) string { + if strings.HasPrefix(wlName, "wl_") { + wlName = strings.TrimPrefix(wlName, "wl_") + } + + // replace all "_" chars to " " chars + wlName = strings.Replace(wlName, "_", " ", -1) + parts := strings.Split(wlName, " ") + for i, p := range parts { + if i == 0 { + continue + } + parts[i] = strings.Title(p) + } + + return strings.Join(parts, "") +} + +func fmtFile() { + goex, err := exec.LookPath("go") + if err != nil { + log.Printf("go executable cannot found run \"go fmt %s\" yourself: %s", *output, err) + return + } + + cmd := exec.Command(goex, "fmt", *output) + er2 := cmd.Run() + if er2 != nil { + log.Fatalf("Cannot run cmd: %s", er2) + } +} + +// templates +var ( + ifaceTypeTemplate = ` +type {{.Name}} struct { + {{.WL}}BaseProxy + {{- if gt (len .Events) 0 }} + mu sync.RWMutex + {{- end}} + + {{- range .Events}} + {{.PName}}Handlers []{{.EName}}Handler + {{- end}} +} +` + ifaceConstructorTemplate = ` +func New{{.Name}}(ctx *{{.WL}}Context) *{{.Name}} { + ret := new({{.Name}}) + ctx.Register(ret) + return ret +} +` + ifaceAddRemoveHandlerTemplate = ` +func (p *{{.IfaceName}}) Add{{.Name}}Handler(h {{.EName}}Handler) { + if h != nil { + p.mu.Lock() + p.{{.PName}}Handlers = append(p.{{.PName}}Handlers , h) + p.mu.Unlock() + } +} + +func (p *{{.IfaceName}}) Remove{{.Name}}Handler(h {{.EName}}Handler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i , e := range p.{{.PName}}Handlers { + if e == h { + p.{{.PName}}Handlers = append(p.{{.PName}}Handlers[:i] , p.{{.PName}}Handlers[i+1:]...) + break + } + } +} +` + + requestTemplate = ` +// {{.Name}} will {{.Summary}}. +// +{{.Description}}func (p *{{.IfaceName}}) {{.Name}}({{.Params}}) {{.Returns}} { + {{- if .HasNewId}} + ret := New{{.NewIdInterface}}(p.Context()) + return ret , p.Context().SendRequest(p,{{.Order}}{{.Args}}) + {{- else}} + return p.Context().SendRequest(p,{{.Order}}{{.Args}}) + {{- end}} +} +` + + eventTemplate = ` +type {{.IfaceName}}{{.Name}}Event struct { + {{- range .Args }} + {{.Name}} {{.Type}} + {{- end }} +} + +type {{.IfaceName}}{{.Name}}Handler interface { + Handle{{.EName}}({{.EName}}Event) +} +` + + ifaceDispatchTemplate = ` +func (p *{{.Name}}) Dispatch(event *{{.WL}}Event) { + {{- $ifaceName := .Name }} + switch event.Opcode { + {{- range $i , $event := .Events }} + case {{$i}}: + if len(p.{{.PName}}Handlers) > 0 { + ev := {{$ifaceName}}{{.Name}}Event{} + {{- range $event.Args}} + ev.{{.Name}} = event.{{.BufMethod}} + {{- end}} + p.mu.RLock() + for _, h := range p.{{.PName}}Handlers { + h.Handle{{.EName}}(ev) + } + p.mu.RUnlock() + } + {{- end}} + } +} +` + ifaceEnums = ` +const ( + {{- $ifaceName := .IfaceName }} + {{- $enumName := .Name }} + {{- range .Entries}} + {{$ifaceName}}{{$enumName}}{{.Name}} = {{.Value}} + {{- end}} +) +` +) + +var inheritedNames = []string{ + "wl_display", + "wl_registry", + "wl_callback", + "wl_compositor", + "wl_shm_pool", + "wl_shm", + "wl_buffer", + "wl_data_offer", + "wl_data_source", + "wl_data_device", + "wl_data_device_manager", + "wl_shell", + "wl_shell_surface", + "wl_surface", + "wl_seat", + "wl_pointer", + "wl_keyboard", + "wl_touch", + "wl_output", + "wl_region", + "wl_subcompositor", + "wl_subsurface", +} + +func reflow(text string) string { + ret := "" + for _, line := range strings.Split(text, "\n") { + ret = ret + "// " + strings.TrimSpace(line) + "\n" + } + return ret +} + +func stripUnstable(ifname string) string { + return strings.TrimSuffix(ifname, ifTrimSuffix) +} diff --git a/src/github.com/dkolbly/wl/LICENSE b/src/github.com/dkolbly/wl/LICENSE new file mode 100644 index 00000000..3ebed1ba --- /dev/null +++ b/src/github.com/dkolbly/wl/LICENSE @@ -0,0 +1,29 @@ +BSD 2-Clause License + +Copyright (c) 2015 , 2016 + +stanluk - https://github.com/stanluk +sternix - https://github.com/sternix + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/github.com/dkolbly/wl/README.md b/src/github.com/dkolbly/wl/README.md new file mode 100644 index 00000000..a31c135b --- /dev/null +++ b/src/github.com/dkolbly/wl/README.md @@ -0,0 +1,25 @@ +# wl + +A wayland protocol implementation in pure Go. + +This is a Go implementation of the Wayland protocol. The protocol +files themselves (`client.go` and `xdg/shell.go`) are built using the +tool in `github.com/dkolbly/wl-scanner` from the XML protocol +specification files. + +To test: +``` +go get github.com/dkolbly/wl/ui/examples/img +``` + +then: +``` +$GOPATH/bin/img $GOPATH/src/github.com/dkolbly/wl/ui/examples/img/bsd_daemon.jpg +``` + +This is a hobby project, forked from a hobby project, `github.com/sternix/wl`. + + +## Desktops + +The image program (`img`) works in both weston and in Ubuntu Gnome in wayland mode. diff --git a/src/github.com/dkolbly/wl/client.go b/src/github.com/dkolbly/wl/client.go new file mode 100644 index 00000000..1c3fd661 --- /dev/null +++ b/src/github.com/dkolbly/wl/client.go @@ -0,0 +1,4079 @@ +// package wl acts as a client for the wayland wayland protocol. + +// generated by wl-scanner +// https://github.com/dkolbly/wl-scanner +// from: https://cgit.freedesktop.org/wayland/wayland/plain/protocol/wayland.xml +// on 2018-02-19 14:50:40 -0600 +package wl + +import ( + "context" + "sync" +) + +type DisplayErrorEvent struct { + EventContext context.Context + ObjectId Proxy + Code uint32 + Message string +} + +type DisplayErrorHandler interface { + HandleDisplayError(DisplayErrorEvent) +} + +func (p *Display) AddErrorHandler(h DisplayErrorHandler) { + if h != nil { + p.mu.Lock() + p.errorHandlers = append(p.errorHandlers, h) + p.mu.Unlock() + } +} + +func (p *Display) RemoveErrorHandler(h DisplayErrorHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.errorHandlers { + if e == h { + p.errorHandlers = append(p.errorHandlers[:i], p.errorHandlers[i+1:]...) + break + } + } +} + +type DisplayDeleteIdEvent struct { + EventContext context.Context + Id uint32 +} + +type DisplayDeleteIdHandler interface { + HandleDisplayDeleteId(DisplayDeleteIdEvent) +} + +func (p *Display) AddDeleteIdHandler(h DisplayDeleteIdHandler) { + if h != nil { + p.mu.Lock() + p.deleteIdHandlers = append(p.deleteIdHandlers, h) + p.mu.Unlock() + } +} + +func (p *Display) RemoveDeleteIdHandler(h DisplayDeleteIdHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.deleteIdHandlers { + if e == h { + p.deleteIdHandlers = append(p.deleteIdHandlers[:i], p.deleteIdHandlers[i+1:]...) + break + } + } +} + +func (p *Display) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.errorHandlers) > 0 { + ev := DisplayErrorEvent{} + ev.EventContext = ctx + ev.ObjectId = event.Proxy(p.Context()) + ev.Code = event.Uint32() + ev.Message = event.String() + p.mu.RLock() + for _, h := range p.errorHandlers { + h.HandleDisplayError(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.deleteIdHandlers) > 0 { + ev := DisplayDeleteIdEvent{} + ev.EventContext = ctx + ev.Id = event.Uint32() + p.mu.RLock() + for _, h := range p.deleteIdHandlers { + h.HandleDisplayDeleteId(ev) + } + p.mu.RUnlock() + } + } +} + +type Display struct { + BaseProxy + mu sync.RWMutex + errorHandlers []DisplayErrorHandler + deleteIdHandlers []DisplayDeleteIdHandler +} + +func NewDisplay(ctx *Context) *Display { + ret := new(Display) + ctx.Register(ret) + return ret +} + +// Sync will asynchronous roundtrip. +// +// +// The sync request asks the server to emit the 'done' event +// on the returned wl_callback object. Since requests are +// handled in-order and events are delivered in-order, this can +// be used as a barrier to ensure all previous requests and the +// resulting events have been handled. +// +// The object returned by this request will be destroyed by the +// compositor after the callback is fired and as such the client must not +// attempt to use it after that point. +// +// The callback_data passed in the callback is the event serial. +// +func (p *Display) Sync() (*Callback, error) { + ret := NewCallback(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret)) +} + +// GetRegistry will get global registry object. +// +// +// This request creates a registry object that allows the client +// to list and bind the global objects available from the +// compositor. +// +// It should be noted that the server side resources consumed in +// response to a get_registry request can only be released when the +// client disconnects, not when the client side proxy is destroyed. +// Therefore, clients should invoke get_registry as infrequently as +// possible to avoid wasting memory. +// +func (p *Display) GetRegistry() (*Registry, error) { + ret := NewRegistry(p.Context()) + return ret, p.Context().SendRequest(p, 1, Proxy(ret)) +} + +const ( + DisplayErrorInvalidObject = 0 + DisplayErrorInvalidMethod = 1 + DisplayErrorNoMemory = 2 +) + +type RegistryGlobalEvent struct { + EventContext context.Context + Name uint32 + Interface string + Version uint32 +} + +type RegistryGlobalHandler interface { + HandleRegistryGlobal(RegistryGlobalEvent) +} + +func (p *Registry) AddGlobalHandler(h RegistryGlobalHandler) { + if h != nil { + p.mu.Lock() + p.globalHandlers = append(p.globalHandlers, h) + p.mu.Unlock() + } +} + +func (p *Registry) RemoveGlobalHandler(h RegistryGlobalHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.globalHandlers { + if e == h { + p.globalHandlers = append(p.globalHandlers[:i], p.globalHandlers[i+1:]...) + break + } + } +} + +type RegistryGlobalRemoveEvent struct { + EventContext context.Context + Name uint32 +} + +type RegistryGlobalRemoveHandler interface { + HandleRegistryGlobalRemove(RegistryGlobalRemoveEvent) +} + +func (p *Registry) AddGlobalRemoveHandler(h RegistryGlobalRemoveHandler) { + if h != nil { + p.mu.Lock() + p.globalRemoveHandlers = append(p.globalRemoveHandlers, h) + p.mu.Unlock() + } +} + +func (p *Registry) RemoveGlobalRemoveHandler(h RegistryGlobalRemoveHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.globalRemoveHandlers { + if e == h { + p.globalRemoveHandlers = append(p.globalRemoveHandlers[:i], p.globalRemoveHandlers[i+1:]...) + break + } + } +} + +func (p *Registry) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.globalHandlers) > 0 { + ev := RegistryGlobalEvent{} + ev.EventContext = ctx + ev.Name = event.Uint32() + ev.Interface = event.String() + ev.Version = event.Uint32() + p.mu.RLock() + for _, h := range p.globalHandlers { + h.HandleRegistryGlobal(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.globalRemoveHandlers) > 0 { + ev := RegistryGlobalRemoveEvent{} + ev.EventContext = ctx + ev.Name = event.Uint32() + p.mu.RLock() + for _, h := range p.globalRemoveHandlers { + h.HandleRegistryGlobalRemove(ev) + } + p.mu.RUnlock() + } + } +} + +type Registry struct { + BaseProxy + mu sync.RWMutex + globalHandlers []RegistryGlobalHandler + globalRemoveHandlers []RegistryGlobalRemoveHandler +} + +func NewRegistry(ctx *Context) *Registry { + ret := new(Registry) + ctx.Register(ret) + return ret +} + +// Bind will bind an object to the display. +// +// +// Binds a new, client-created object to the server using the +// specified name as the identifier. +// +func (p *Registry) Bind(name uint32, iface string, version uint32, id Proxy) error { + return p.Context().SendRequest(p, 0, name, iface, version, id) +} + +type CallbackDoneEvent struct { + EventContext context.Context + CallbackData uint32 +} + +type CallbackDoneHandler interface { + HandleCallbackDone(CallbackDoneEvent) +} + +func (p *Callback) AddDoneHandler(h CallbackDoneHandler) { + if h != nil { + p.mu.Lock() + p.doneHandlers = append(p.doneHandlers, h) + p.mu.Unlock() + } +} + +func (p *Callback) RemoveDoneHandler(h CallbackDoneHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.doneHandlers { + if e == h { + p.doneHandlers = append(p.doneHandlers[:i], p.doneHandlers[i+1:]...) + break + } + } +} + +func (p *Callback) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.doneHandlers) > 0 { + ev := CallbackDoneEvent{} + ev.EventContext = ctx + ev.CallbackData = event.Uint32() + p.mu.RLock() + for _, h := range p.doneHandlers { + h.HandleCallbackDone(ev) + } + p.mu.RUnlock() + } + } +} + +type Callback struct { + BaseProxy + mu sync.RWMutex + doneHandlers []CallbackDoneHandler +} + +func NewCallback(ctx *Context) *Callback { + ret := new(Callback) + ctx.Register(ret) + return ret +} + +type Compositor struct { + BaseProxy +} + +func NewCompositor(ctx *Context) *Compositor { + ret := new(Compositor) + ctx.Register(ret) + return ret +} + +// CreateSurface will create new surface. +// +// +// Ask the compositor to create a new surface. +// +func (p *Compositor) CreateSurface() (*Surface, error) { + ret := NewSurface(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret)) +} + +// CreateRegion will create new region. +// +// +// Ask the compositor to create a new region. +// +func (p *Compositor) CreateRegion() (*Region, error) { + ret := NewRegion(p.Context()) + return ret, p.Context().SendRequest(p, 1, Proxy(ret)) +} + +type ShmPool struct { + BaseProxy +} + +func NewShmPool(ctx *Context) *ShmPool { + ret := new(ShmPool) + ctx.Register(ret) + return ret +} + +// CreateBuffer will create a buffer from the pool. +// +// +// Create a wl_buffer object from the pool. +// +// The buffer is created offset bytes into the pool and has +// width and height as specified. The stride argument specifies +// the number of bytes from the beginning of one row to the beginning +// of the next. The format is the pixel format of the buffer and +// must be one of those advertised through the wl_shm.format event. +// +// A buffer will keep a reference to the pool it was created from +// so it is valid to destroy the pool immediately after creating +// a buffer from it. +// +func (p *ShmPool) CreateBuffer(offset int32, width int32, height int32, stride int32, format uint32) (*Buffer, error) { + ret := NewBuffer(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret), offset, width, height, stride, format) +} + +// Destroy will destroy the pool. +// +// +// Destroy the shared memory pool. +// +// The mmapped memory will be released when all +// buffers that have been created from this pool +// are gone. +// +func (p *ShmPool) Destroy() error { + return p.Context().SendRequest(p, 1) +} + +// Resize will change the size of the pool mapping. +// +// +// This request will cause the server to remap the backing memory +// for the pool from the file descriptor passed when the pool was +// created, but using the new size. This request can only be +// used to make the pool bigger. +// +func (p *ShmPool) Resize(size int32) error { + return p.Context().SendRequest(p, 2, size) +} + +type ShmFormatEvent struct { + EventContext context.Context + Format uint32 +} + +type ShmFormatHandler interface { + HandleShmFormat(ShmFormatEvent) +} + +func (p *Shm) AddFormatHandler(h ShmFormatHandler) { + if h != nil { + p.mu.Lock() + p.formatHandlers = append(p.formatHandlers, h) + p.mu.Unlock() + } +} + +func (p *Shm) RemoveFormatHandler(h ShmFormatHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.formatHandlers { + if e == h { + p.formatHandlers = append(p.formatHandlers[:i], p.formatHandlers[i+1:]...) + break + } + } +} + +func (p *Shm) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.formatHandlers) > 0 { + ev := ShmFormatEvent{} + ev.EventContext = ctx + ev.Format = event.Uint32() + p.mu.RLock() + for _, h := range p.formatHandlers { + h.HandleShmFormat(ev) + } + p.mu.RUnlock() + } + } +} + +type Shm struct { + BaseProxy + mu sync.RWMutex + formatHandlers []ShmFormatHandler +} + +func NewShm(ctx *Context) *Shm { + ret := new(Shm) + ctx.Register(ret) + return ret +} + +// CreatePool will create a shm pool. +// +// +// Create a new wl_shm_pool object. +// +// The pool can be used to create shared memory based buffer +// objects. The server will mmap size bytes of the passed file +// descriptor, to use as backing memory for the pool. +// +func (p *Shm) CreatePool(fd uintptr, size int32) (*ShmPool, error) { + ret := NewShmPool(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret), fd, size) +} + +const ( + ShmErrorInvalidFormat = 0 + ShmErrorInvalidStride = 1 + ShmErrorInvalidFd = 2 +) + +const ( + ShmFormatArgb8888 = 0 + ShmFormatXrgb8888 = 1 + ShmFormatC8 = 0x20203843 + ShmFormatRgb332 = 0x38424752 + ShmFormatBgr233 = 0x38524742 + ShmFormatXrgb4444 = 0x32315258 + ShmFormatXbgr4444 = 0x32314258 + ShmFormatRgbx4444 = 0x32315852 + ShmFormatBgrx4444 = 0x32315842 + ShmFormatArgb4444 = 0x32315241 + ShmFormatAbgr4444 = 0x32314241 + ShmFormatRgba4444 = 0x32314152 + ShmFormatBgra4444 = 0x32314142 + ShmFormatXrgb1555 = 0x35315258 + ShmFormatXbgr1555 = 0x35314258 + ShmFormatRgbx5551 = 0x35315852 + ShmFormatBgrx5551 = 0x35315842 + ShmFormatArgb1555 = 0x35315241 + ShmFormatAbgr1555 = 0x35314241 + ShmFormatRgba5551 = 0x35314152 + ShmFormatBgra5551 = 0x35314142 + ShmFormatRgb565 = 0x36314752 + ShmFormatBgr565 = 0x36314742 + ShmFormatRgb888 = 0x34324752 + ShmFormatBgr888 = 0x34324742 + ShmFormatXbgr8888 = 0x34324258 + ShmFormatRgbx8888 = 0x34325852 + ShmFormatBgrx8888 = 0x34325842 + ShmFormatAbgr8888 = 0x34324241 + ShmFormatRgba8888 = 0x34324152 + ShmFormatBgra8888 = 0x34324142 + ShmFormatXrgb2101010 = 0x30335258 + ShmFormatXbgr2101010 = 0x30334258 + ShmFormatRgbx1010102 = 0x30335852 + ShmFormatBgrx1010102 = 0x30335842 + ShmFormatArgb2101010 = 0x30335241 + ShmFormatAbgr2101010 = 0x30334241 + ShmFormatRgba1010102 = 0x30334152 + ShmFormatBgra1010102 = 0x30334142 + ShmFormatYuyv = 0x56595559 + ShmFormatYvyu = 0x55595659 + ShmFormatUyvy = 0x59565955 + ShmFormatVyuy = 0x59555956 + ShmFormatAyuv = 0x56555941 + ShmFormatNv12 = 0x3231564e + ShmFormatNv21 = 0x3132564e + ShmFormatNv16 = 0x3631564e + ShmFormatNv61 = 0x3136564e + ShmFormatYuv410 = 0x39565559 + ShmFormatYvu410 = 0x39555659 + ShmFormatYuv411 = 0x31315559 + ShmFormatYvu411 = 0x31315659 + ShmFormatYuv420 = 0x32315559 + ShmFormatYvu420 = 0x32315659 + ShmFormatYuv422 = 0x36315559 + ShmFormatYvu422 = 0x36315659 + ShmFormatYuv444 = 0x34325559 + ShmFormatYvu444 = 0x34325659 +) + +type BufferReleaseEvent struct { + EventContext context.Context +} + +type BufferReleaseHandler interface { + HandleBufferRelease(BufferReleaseEvent) +} + +func (p *Buffer) AddReleaseHandler(h BufferReleaseHandler) { + if h != nil { + p.mu.Lock() + p.releaseHandlers = append(p.releaseHandlers, h) + p.mu.Unlock() + } +} + +func (p *Buffer) RemoveReleaseHandler(h BufferReleaseHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.releaseHandlers { + if e == h { + p.releaseHandlers = append(p.releaseHandlers[:i], p.releaseHandlers[i+1:]...) + break + } + } +} + +func (p *Buffer) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.releaseHandlers) > 0 { + ev := BufferReleaseEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.releaseHandlers { + h.HandleBufferRelease(ev) + } + p.mu.RUnlock() + } + } +} + +type Buffer struct { + BaseProxy + mu sync.RWMutex + releaseHandlers []BufferReleaseHandler +} + +func NewBuffer(ctx *Context) *Buffer { + ret := new(Buffer) + ctx.Register(ret) + return ret +} + +// Destroy will destroy a buffer. +// +// +// Destroy a buffer. If and how you need to release the backing +// storage is defined by the buffer factory interface. +// +// For possible side-effects to a surface, see wl_surface.attach. +// +func (p *Buffer) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +type DataOfferOfferEvent struct { + EventContext context.Context + MimeType string +} + +type DataOfferOfferHandler interface { + HandleDataOfferOffer(DataOfferOfferEvent) +} + +func (p *DataOffer) AddOfferHandler(h DataOfferOfferHandler) { + if h != nil { + p.mu.Lock() + p.offerHandlers = append(p.offerHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataOffer) RemoveOfferHandler(h DataOfferOfferHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.offerHandlers { + if e == h { + p.offerHandlers = append(p.offerHandlers[:i], p.offerHandlers[i+1:]...) + break + } + } +} + +type DataOfferSourceActionsEvent struct { + EventContext context.Context + SourceActions uint32 +} + +type DataOfferSourceActionsHandler interface { + HandleDataOfferSourceActions(DataOfferSourceActionsEvent) +} + +func (p *DataOffer) AddSourceActionsHandler(h DataOfferSourceActionsHandler) { + if h != nil { + p.mu.Lock() + p.sourceActionsHandlers = append(p.sourceActionsHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataOffer) RemoveSourceActionsHandler(h DataOfferSourceActionsHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.sourceActionsHandlers { + if e == h { + p.sourceActionsHandlers = append(p.sourceActionsHandlers[:i], p.sourceActionsHandlers[i+1:]...) + break + } + } +} + +type DataOfferActionEvent struct { + EventContext context.Context + DndAction uint32 +} + +type DataOfferActionHandler interface { + HandleDataOfferAction(DataOfferActionEvent) +} + +func (p *DataOffer) AddActionHandler(h DataOfferActionHandler) { + if h != nil { + p.mu.Lock() + p.actionHandlers = append(p.actionHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataOffer) RemoveActionHandler(h DataOfferActionHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.actionHandlers { + if e == h { + p.actionHandlers = append(p.actionHandlers[:i], p.actionHandlers[i+1:]...) + break + } + } +} + +func (p *DataOffer) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.offerHandlers) > 0 { + ev := DataOfferOfferEvent{} + ev.EventContext = ctx + ev.MimeType = event.String() + p.mu.RLock() + for _, h := range p.offerHandlers { + h.HandleDataOfferOffer(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.sourceActionsHandlers) > 0 { + ev := DataOfferSourceActionsEvent{} + ev.EventContext = ctx + ev.SourceActions = event.Uint32() + p.mu.RLock() + for _, h := range p.sourceActionsHandlers { + h.HandleDataOfferSourceActions(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.actionHandlers) > 0 { + ev := DataOfferActionEvent{} + ev.EventContext = ctx + ev.DndAction = event.Uint32() + p.mu.RLock() + for _, h := range p.actionHandlers { + h.HandleDataOfferAction(ev) + } + p.mu.RUnlock() + } + } +} + +type DataOffer struct { + BaseProxy + mu sync.RWMutex + offerHandlers []DataOfferOfferHandler + sourceActionsHandlers []DataOfferSourceActionsHandler + actionHandlers []DataOfferActionHandler +} + +func NewDataOffer(ctx *Context) *DataOffer { + ret := new(DataOffer) + ctx.Register(ret) + return ret +} + +// Accept will accept one of the offered mime types. +// +// +// Indicate that the client can accept the given mime type, or +// NULL for not accepted. +// +// For objects of version 2 or older, this request is used by the +// client to give feedback whether the client can receive the given +// mime type, or NULL if none is accepted; the feedback does not +// determine whether the drag-and-drop operation succeeds or not. +// +// For objects of version 3 or newer, this request determines the +// final result of the drag-and-drop operation. If the end result +// is that no mime types were accepted, the drag-and-drop operation +// will be cancelled and the corresponding drag source will receive +// wl_data_source.cancelled. Clients may still use this event in +// conjunction with wl_data_source.action for feedback. +// +func (p *DataOffer) Accept(serial uint32, mime_type string) error { + return p.Context().SendRequest(p, 0, serial, mime_type) +} + +// Receive will request that the data is transferred. +// +// +// To transfer the offered data, the client issues this request +// and indicates the mime type it wants to receive. The transfer +// happens through the passed file descriptor (typically created +// with the pipe system call). The source client writes the data +// in the mime type representation requested and then closes the +// file descriptor. +// +// The receiving client reads from the read end of the pipe until +// EOF and then closes its end, at which point the transfer is +// complete. +// +// This request may happen multiple times for different mime types, +// both before and after wl_data_device.drop. Drag-and-drop destination +// clients may preemptively fetch data or examine it more closely to +// determine acceptance. +// +func (p *DataOffer) Receive(mime_type string, fd uintptr) error { + return p.Context().SendRequest(p, 1, mime_type, fd) +} + +// Destroy will destroy data offer. +// +// +// Destroy the data offer. +// +func (p *DataOffer) Destroy() error { + return p.Context().SendRequest(p, 2) +} + +// Finish will the offer will no longer be used. +// +// +// Notifies the compositor that the drag destination successfully +// finished the drag-and-drop operation. +// +// Upon receiving this request, the compositor will emit +// wl_data_source.dnd_finished on the drag source client. +// +// It is a client error to perform other requests than +// wl_data_offer.destroy after this one. It is also an error to perform +// this request after a NULL mime type has been set in +// wl_data_offer.accept or no action was received through +// wl_data_offer.action. +// +func (p *DataOffer) Finish() error { + return p.Context().SendRequest(p, 3) +} + +// SetActions will set the available/preferred drag-and-drop actions. +// +// +// Sets the actions that the destination side client supports for +// this operation. This request may trigger the emission of +// wl_data_source.action and wl_data_offer.action events if the compositor +// needs to change the selected action. +// +// This request can be called multiple times throughout the +// drag-and-drop operation, typically in response to wl_data_device.enter +// or wl_data_device.motion events. +// +// This request determines the final result of the drag-and-drop +// operation. If the end result is that no action is accepted, +// the drag source will receive wl_drag_source.cancelled. +// +// The dnd_actions argument must contain only values expressed in the +// wl_data_device_manager.dnd_actions enum, and the preferred_action +// argument must only contain one of those values set, otherwise it +// will result in a protocol error. +// +// While managing an "ask" action, the destination drag-and-drop client +// may perform further wl_data_offer.receive requests, and is expected +// to perform one last wl_data_offer.set_actions request with a preferred +// action other than "ask" (and optionally wl_data_offer.accept) before +// requesting wl_data_offer.finish, in order to convey the action selected +// by the user. If the preferred action is not in the +// wl_data_offer.source_actions mask, an error will be raised. +// +// If the "ask" action is dismissed (e.g. user cancellation), the client +// is expected to perform wl_data_offer.destroy right away. +// +// This request can only be made on drag-and-drop offers, a protocol error +// will be raised otherwise. +// +func (p *DataOffer) SetActions(dnd_actions uint32, preferred_action uint32) error { + return p.Context().SendRequest(p, 4, dnd_actions, preferred_action) +} + +const ( + DataOfferErrorInvalidFinish = 0 + DataOfferErrorInvalidActionMask = 1 + DataOfferErrorInvalidAction = 2 + DataOfferErrorInvalidOffer = 3 +) + +type DataSourceTargetEvent struct { + EventContext context.Context + MimeType string +} + +type DataSourceTargetHandler interface { + HandleDataSourceTarget(DataSourceTargetEvent) +} + +func (p *DataSource) AddTargetHandler(h DataSourceTargetHandler) { + if h != nil { + p.mu.Lock() + p.targetHandlers = append(p.targetHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataSource) RemoveTargetHandler(h DataSourceTargetHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.targetHandlers { + if e == h { + p.targetHandlers = append(p.targetHandlers[:i], p.targetHandlers[i+1:]...) + break + } + } +} + +type DataSourceSendEvent struct { + EventContext context.Context + MimeType string + Fd uintptr +} + +type DataSourceSendHandler interface { + HandleDataSourceSend(DataSourceSendEvent) +} + +func (p *DataSource) AddSendHandler(h DataSourceSendHandler) { + if h != nil { + p.mu.Lock() + p.sendHandlers = append(p.sendHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataSource) RemoveSendHandler(h DataSourceSendHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.sendHandlers { + if e == h { + p.sendHandlers = append(p.sendHandlers[:i], p.sendHandlers[i+1:]...) + break + } + } +} + +type DataSourceCancelledEvent struct { + EventContext context.Context +} + +type DataSourceCancelledHandler interface { + HandleDataSourceCancelled(DataSourceCancelledEvent) +} + +func (p *DataSource) AddCancelledHandler(h DataSourceCancelledHandler) { + if h != nil { + p.mu.Lock() + p.cancelledHandlers = append(p.cancelledHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataSource) RemoveCancelledHandler(h DataSourceCancelledHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.cancelledHandlers { + if e == h { + p.cancelledHandlers = append(p.cancelledHandlers[:i], p.cancelledHandlers[i+1:]...) + break + } + } +} + +type DataSourceDndDropPerformedEvent struct { + EventContext context.Context +} + +type DataSourceDndDropPerformedHandler interface { + HandleDataSourceDndDropPerformed(DataSourceDndDropPerformedEvent) +} + +func (p *DataSource) AddDndDropPerformedHandler(h DataSourceDndDropPerformedHandler) { + if h != nil { + p.mu.Lock() + p.dndDropPerformedHandlers = append(p.dndDropPerformedHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataSource) RemoveDndDropPerformedHandler(h DataSourceDndDropPerformedHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.dndDropPerformedHandlers { + if e == h { + p.dndDropPerformedHandlers = append(p.dndDropPerformedHandlers[:i], p.dndDropPerformedHandlers[i+1:]...) + break + } + } +} + +type DataSourceDndFinishedEvent struct { + EventContext context.Context +} + +type DataSourceDndFinishedHandler interface { + HandleDataSourceDndFinished(DataSourceDndFinishedEvent) +} + +func (p *DataSource) AddDndFinishedHandler(h DataSourceDndFinishedHandler) { + if h != nil { + p.mu.Lock() + p.dndFinishedHandlers = append(p.dndFinishedHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataSource) RemoveDndFinishedHandler(h DataSourceDndFinishedHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.dndFinishedHandlers { + if e == h { + p.dndFinishedHandlers = append(p.dndFinishedHandlers[:i], p.dndFinishedHandlers[i+1:]...) + break + } + } +} + +type DataSourceActionEvent struct { + EventContext context.Context + DndAction uint32 +} + +type DataSourceActionHandler interface { + HandleDataSourceAction(DataSourceActionEvent) +} + +func (p *DataSource) AddActionHandler(h DataSourceActionHandler) { + if h != nil { + p.mu.Lock() + p.actionHandlers = append(p.actionHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataSource) RemoveActionHandler(h DataSourceActionHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.actionHandlers { + if e == h { + p.actionHandlers = append(p.actionHandlers[:i], p.actionHandlers[i+1:]...) + break + } + } +} + +func (p *DataSource) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.targetHandlers) > 0 { + ev := DataSourceTargetEvent{} + ev.EventContext = ctx + ev.MimeType = event.String() + p.mu.RLock() + for _, h := range p.targetHandlers { + h.HandleDataSourceTarget(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.sendHandlers) > 0 { + ev := DataSourceSendEvent{} + ev.EventContext = ctx + ev.MimeType = event.String() + ev.Fd = event.FD() + p.mu.RLock() + for _, h := range p.sendHandlers { + h.HandleDataSourceSend(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.cancelledHandlers) > 0 { + ev := DataSourceCancelledEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.cancelledHandlers { + h.HandleDataSourceCancelled(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.dndDropPerformedHandlers) > 0 { + ev := DataSourceDndDropPerformedEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.dndDropPerformedHandlers { + h.HandleDataSourceDndDropPerformed(ev) + } + p.mu.RUnlock() + } + case 4: + if len(p.dndFinishedHandlers) > 0 { + ev := DataSourceDndFinishedEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.dndFinishedHandlers { + h.HandleDataSourceDndFinished(ev) + } + p.mu.RUnlock() + } + case 5: + if len(p.actionHandlers) > 0 { + ev := DataSourceActionEvent{} + ev.EventContext = ctx + ev.DndAction = event.Uint32() + p.mu.RLock() + for _, h := range p.actionHandlers { + h.HandleDataSourceAction(ev) + } + p.mu.RUnlock() + } + } +} + +type DataSource struct { + BaseProxy + mu sync.RWMutex + targetHandlers []DataSourceTargetHandler + sendHandlers []DataSourceSendHandler + cancelledHandlers []DataSourceCancelledHandler + dndDropPerformedHandlers []DataSourceDndDropPerformedHandler + dndFinishedHandlers []DataSourceDndFinishedHandler + actionHandlers []DataSourceActionHandler +} + +func NewDataSource(ctx *Context) *DataSource { + ret := new(DataSource) + ctx.Register(ret) + return ret +} + +// Offer will add an offered mime type. +// +// +// This request adds a mime type to the set of mime types +// advertised to targets. Can be called several times to offer +// multiple types. +// +func (p *DataSource) Offer(mime_type string) error { + return p.Context().SendRequest(p, 0, mime_type) +} + +// Destroy will destroy the data source. +// +// +// Destroy the data source. +// +func (p *DataSource) Destroy() error { + return p.Context().SendRequest(p, 1) +} + +// SetActions will set the available drag-and-drop actions. +// +// +// Sets the actions that the source side client supports for this +// operation. This request may trigger wl_data_source.action and +// wl_data_offer.action events if the compositor needs to change the +// selected action. +// +// The dnd_actions argument must contain only values expressed in the +// wl_data_device_manager.dnd_actions enum, otherwise it will result +// in a protocol error. +// +// This request must be made once only, and can only be made on sources +// used in drag-and-drop, so it must be performed before +// wl_data_device.start_drag. Attempting to use the source other than +// for drag-and-drop will raise a protocol error. +// +func (p *DataSource) SetActions(dnd_actions uint32) error { + return p.Context().SendRequest(p, 2, dnd_actions) +} + +const ( + DataSourceErrorInvalidActionMask = 0 + DataSourceErrorInvalidSource = 1 +) + +type DataDeviceDataOfferEvent struct { + EventContext context.Context + Id *DataOffer +} + +type DataDeviceDataOfferHandler interface { + HandleDataDeviceDataOffer(DataDeviceDataOfferEvent) +} + +func (p *DataDevice) AddDataOfferHandler(h DataDeviceDataOfferHandler) { + if h != nil { + p.mu.Lock() + p.dataOfferHandlers = append(p.dataOfferHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataDevice) RemoveDataOfferHandler(h DataDeviceDataOfferHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.dataOfferHandlers { + if e == h { + p.dataOfferHandlers = append(p.dataOfferHandlers[:i], p.dataOfferHandlers[i+1:]...) + break + } + } +} + +type DataDeviceEnterEvent struct { + EventContext context.Context + Serial uint32 + Surface *Surface + X float32 + Y float32 + Id *DataOffer +} + +type DataDeviceEnterHandler interface { + HandleDataDeviceEnter(DataDeviceEnterEvent) +} + +func (p *DataDevice) AddEnterHandler(h DataDeviceEnterHandler) { + if h != nil { + p.mu.Lock() + p.enterHandlers = append(p.enterHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataDevice) RemoveEnterHandler(h DataDeviceEnterHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.enterHandlers { + if e == h { + p.enterHandlers = append(p.enterHandlers[:i], p.enterHandlers[i+1:]...) + break + } + } +} + +type DataDeviceLeaveEvent struct { + EventContext context.Context +} + +type DataDeviceLeaveHandler interface { + HandleDataDeviceLeave(DataDeviceLeaveEvent) +} + +func (p *DataDevice) AddLeaveHandler(h DataDeviceLeaveHandler) { + if h != nil { + p.mu.Lock() + p.leaveHandlers = append(p.leaveHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataDevice) RemoveLeaveHandler(h DataDeviceLeaveHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.leaveHandlers { + if e == h { + p.leaveHandlers = append(p.leaveHandlers[:i], p.leaveHandlers[i+1:]...) + break + } + } +} + +type DataDeviceMotionEvent struct { + EventContext context.Context + Time uint32 + X float32 + Y float32 +} + +type DataDeviceMotionHandler interface { + HandleDataDeviceMotion(DataDeviceMotionEvent) +} + +func (p *DataDevice) AddMotionHandler(h DataDeviceMotionHandler) { + if h != nil { + p.mu.Lock() + p.motionHandlers = append(p.motionHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataDevice) RemoveMotionHandler(h DataDeviceMotionHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.motionHandlers { + if e == h { + p.motionHandlers = append(p.motionHandlers[:i], p.motionHandlers[i+1:]...) + break + } + } +} + +type DataDeviceDropEvent struct { + EventContext context.Context +} + +type DataDeviceDropHandler interface { + HandleDataDeviceDrop(DataDeviceDropEvent) +} + +func (p *DataDevice) AddDropHandler(h DataDeviceDropHandler) { + if h != nil { + p.mu.Lock() + p.dropHandlers = append(p.dropHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataDevice) RemoveDropHandler(h DataDeviceDropHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.dropHandlers { + if e == h { + p.dropHandlers = append(p.dropHandlers[:i], p.dropHandlers[i+1:]...) + break + } + } +} + +type DataDeviceSelectionEvent struct { + EventContext context.Context + Id *DataOffer +} + +type DataDeviceSelectionHandler interface { + HandleDataDeviceSelection(DataDeviceSelectionEvent) +} + +func (p *DataDevice) AddSelectionHandler(h DataDeviceSelectionHandler) { + if h != nil { + p.mu.Lock() + p.selectionHandlers = append(p.selectionHandlers, h) + p.mu.Unlock() + } +} + +func (p *DataDevice) RemoveSelectionHandler(h DataDeviceSelectionHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.selectionHandlers { + if e == h { + p.selectionHandlers = append(p.selectionHandlers[:i], p.selectionHandlers[i+1:]...) + break + } + } +} + +func (p *DataDevice) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.dataOfferHandlers) > 0 { + ev := DataDeviceDataOfferEvent{} + ev.EventContext = ctx + ev.Id = event.Proxy(p.Context()).(*DataOffer) + p.mu.RLock() + for _, h := range p.dataOfferHandlers { + h.HandleDataDeviceDataOffer(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.enterHandlers) > 0 { + ev := DataDeviceEnterEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Surface = event.Proxy(p.Context()).(*Surface) + ev.X = event.Float32() + ev.Y = event.Float32() + ev.Id = event.Proxy(p.Context()).(*DataOffer) + p.mu.RLock() + for _, h := range p.enterHandlers { + h.HandleDataDeviceEnter(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.leaveHandlers) > 0 { + ev := DataDeviceLeaveEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.leaveHandlers { + h.HandleDataDeviceLeave(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.motionHandlers) > 0 { + ev := DataDeviceMotionEvent{} + ev.EventContext = ctx + ev.Time = event.Uint32() + ev.X = event.Float32() + ev.Y = event.Float32() + p.mu.RLock() + for _, h := range p.motionHandlers { + h.HandleDataDeviceMotion(ev) + } + p.mu.RUnlock() + } + case 4: + if len(p.dropHandlers) > 0 { + ev := DataDeviceDropEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.dropHandlers { + h.HandleDataDeviceDrop(ev) + } + p.mu.RUnlock() + } + case 5: + if len(p.selectionHandlers) > 0 { + ev := DataDeviceSelectionEvent{} + ev.EventContext = ctx + ev.Id = event.Proxy(p.Context()).(*DataOffer) + p.mu.RLock() + for _, h := range p.selectionHandlers { + h.HandleDataDeviceSelection(ev) + } + p.mu.RUnlock() + } + } +} + +type DataDevice struct { + BaseProxy + mu sync.RWMutex + dataOfferHandlers []DataDeviceDataOfferHandler + enterHandlers []DataDeviceEnterHandler + leaveHandlers []DataDeviceLeaveHandler + motionHandlers []DataDeviceMotionHandler + dropHandlers []DataDeviceDropHandler + selectionHandlers []DataDeviceSelectionHandler +} + +func NewDataDevice(ctx *Context) *DataDevice { + ret := new(DataDevice) + ctx.Register(ret) + return ret +} + +// StartDrag will start drag-and-drop operation. +// +// +// This request asks the compositor to start a drag-and-drop +// operation on behalf of the client. +// +// The source argument is the data source that provides the data +// for the eventual data transfer. If source is NULL, enter, leave +// and motion events are sent only to the client that initiated the +// drag and the client is expected to handle the data passing +// internally. +// +// The origin surface is the surface where the drag originates and +// the client must have an active implicit grab that matches the +// serial. +// +// The icon surface is an optional (can be NULL) surface that +// provides an icon to be moved around with the cursor. Initially, +// the top-left corner of the icon surface is placed at the cursor +// hotspot, but subsequent wl_surface.attach request can move the +// relative position. Attach requests must be confirmed with +// wl_surface.commit as usual. The icon surface is given the role of +// a drag-and-drop icon. If the icon surface already has another role, +// it raises a protocol error. +// +// The current and pending input regions of the icon wl_surface are +// cleared, and wl_surface.set_input_region is ignored until the +// wl_surface is no longer used as the icon surface. When the use +// as an icon ends, the current and pending input regions become +// undefined, and the wl_surface is unmapped. +// +func (p *DataDevice) StartDrag(source *DataSource, origin *Surface, icon *Surface, serial uint32) error { + return p.Context().SendRequest(p, 0, source, origin, icon, serial) +} + +// SetSelection will copy data to the selection. +// +// +// This request asks the compositor to set the selection +// to the data from the source on behalf of the client. +// +// To unset the selection, set the source to NULL. +// +func (p *DataDevice) SetSelection(source *DataSource, serial uint32) error { + return p.Context().SendRequest(p, 1, source, serial) +} + +// Release will destroy data device. +// +// +// This request destroys the data device. +// +func (p *DataDevice) Release() error { + return p.Context().SendRequest(p, 2) +} + +const ( + DataDeviceErrorRole = 0 +) + +type DataDeviceManager struct { + BaseProxy +} + +func NewDataDeviceManager(ctx *Context) *DataDeviceManager { + ret := new(DataDeviceManager) + ctx.Register(ret) + return ret +} + +// CreateDataSource will create a new data source. +// +// +// Create a new data source. +// +func (p *DataDeviceManager) CreateDataSource() (*DataSource, error) { + ret := NewDataSource(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret)) +} + +// GetDataDevice will create a new data device. +// +// +// Create a new data device for a given seat. +// +func (p *DataDeviceManager) GetDataDevice(seat *Seat) (*DataDevice, error) { + ret := NewDataDevice(p.Context()) + return ret, p.Context().SendRequest(p, 1, Proxy(ret), seat) +} + +const ( + DataDeviceManagerDndActionNone = 0 + DataDeviceManagerDndActionCopy = 1 + DataDeviceManagerDndActionMove = 2 + DataDeviceManagerDndActionAsk = 4 +) + +type Shell struct { + BaseProxy +} + +func NewShell(ctx *Context) *Shell { + ret := new(Shell) + ctx.Register(ret) + return ret +} + +// GetShellSurface will create a shell surface from a surface. +// +// +// Create a shell surface for an existing surface. This gives +// the wl_surface the role of a shell surface. If the wl_surface +// already has another role, it raises a protocol error. +// +// Only one shell surface can be associated with a given surface. +// +func (p *Shell) GetShellSurface(surface *Surface) (*ShellSurface, error) { + ret := NewShellSurface(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret), surface) +} + +const ( + ShellErrorRole = 0 +) + +type ShellSurfacePingEvent struct { + EventContext context.Context + Serial uint32 +} + +type ShellSurfacePingHandler interface { + HandleShellSurfacePing(ShellSurfacePingEvent) +} + +func (p *ShellSurface) AddPingHandler(h ShellSurfacePingHandler) { + if h != nil { + p.mu.Lock() + p.pingHandlers = append(p.pingHandlers, h) + p.mu.Unlock() + } +} + +func (p *ShellSurface) RemovePingHandler(h ShellSurfacePingHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.pingHandlers { + if e == h { + p.pingHandlers = append(p.pingHandlers[:i], p.pingHandlers[i+1:]...) + break + } + } +} + +type ShellSurfaceConfigureEvent struct { + EventContext context.Context + Edges uint32 + Width int32 + Height int32 +} + +type ShellSurfaceConfigureHandler interface { + HandleShellSurfaceConfigure(ShellSurfaceConfigureEvent) +} + +func (p *ShellSurface) AddConfigureHandler(h ShellSurfaceConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *ShellSurface) RemoveConfigureHandler(h ShellSurfaceConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +type ShellSurfacePopupDoneEvent struct { + EventContext context.Context +} + +type ShellSurfacePopupDoneHandler interface { + HandleShellSurfacePopupDone(ShellSurfacePopupDoneEvent) +} + +func (p *ShellSurface) AddPopupDoneHandler(h ShellSurfacePopupDoneHandler) { + if h != nil { + p.mu.Lock() + p.popupDoneHandlers = append(p.popupDoneHandlers, h) + p.mu.Unlock() + } +} + +func (p *ShellSurface) RemovePopupDoneHandler(h ShellSurfacePopupDoneHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.popupDoneHandlers { + if e == h { + p.popupDoneHandlers = append(p.popupDoneHandlers[:i], p.popupDoneHandlers[i+1:]...) + break + } + } +} + +func (p *ShellSurface) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.pingHandlers) > 0 { + ev := ShellSurfacePingEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + p.mu.RLock() + for _, h := range p.pingHandlers { + h.HandleShellSurfacePing(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.configureHandlers) > 0 { + ev := ShellSurfaceConfigureEvent{} + ev.EventContext = ctx + ev.Edges = event.Uint32() + ev.Width = event.Int32() + ev.Height = event.Int32() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandleShellSurfaceConfigure(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.popupDoneHandlers) > 0 { + ev := ShellSurfacePopupDoneEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.popupDoneHandlers { + h.HandleShellSurfacePopupDone(ev) + } + p.mu.RUnlock() + } + } +} + +type ShellSurface struct { + BaseProxy + mu sync.RWMutex + pingHandlers []ShellSurfacePingHandler + configureHandlers []ShellSurfaceConfigureHandler + popupDoneHandlers []ShellSurfacePopupDoneHandler +} + +func NewShellSurface(ctx *Context) *ShellSurface { + ret := new(ShellSurface) + ctx.Register(ret) + return ret +} + +// Pong will respond to a ping event. +// +// +// A client must respond to a ping event with a pong request or +// the client may be deemed unresponsive. +// +func (p *ShellSurface) Pong(serial uint32) error { + return p.Context().SendRequest(p, 0, serial) +} + +// Move will start an interactive move. +// +// +// Start a pointer-driven move of the surface. +// +// This request must be used in response to a button press event. +// The server may ignore move requests depending on the state of +// the surface (e.g. fullscreen or maximized). +// +func (p *ShellSurface) Move(seat *Seat, serial uint32) error { + return p.Context().SendRequest(p, 1, seat, serial) +} + +// Resize will start an interactive resize. +// +// +// Start a pointer-driven resizing of the surface. +// +// This request must be used in response to a button press event. +// The server may ignore resize requests depending on the state of +// the surface (e.g. fullscreen or maximized). +// +func (p *ShellSurface) Resize(seat *Seat, serial uint32, edges uint32) error { + return p.Context().SendRequest(p, 2, seat, serial, edges) +} + +// SetToplevel will make the surface a toplevel surface. +// +// +// Map the surface as a toplevel surface. +// +// A toplevel surface is not fullscreen, maximized or transient. +// +func (p *ShellSurface) SetToplevel() error { + return p.Context().SendRequest(p, 3) +} + +// SetTransient will make the surface a transient surface. +// +// +// Map the surface relative to an existing surface. +// +// The x and y arguments specify the location of the upper left +// corner of the surface relative to the upper left corner of the +// parent surface, in surface-local coordinates. +// +// The flags argument controls details of the transient behaviour. +// +func (p *ShellSurface) SetTransient(parent *Surface, x int32, y int32, flags uint32) error { + return p.Context().SendRequest(p, 4, parent, x, y, flags) +} + +// SetFullscreen will make the surface a fullscreen surface. +// +// +// Map the surface as a fullscreen surface. +// +// If an output parameter is given then the surface will be made +// fullscreen on that output. If the client does not specify the +// output then the compositor will apply its policy - usually +// choosing the output on which the surface has the biggest surface +// area. +// +// The client may specify a method to resolve a size conflict +// between the output size and the surface size - this is provided +// through the method parameter. +// +// The framerate parameter is used only when the method is set +// to "driver", to indicate the preferred framerate. A value of 0 +// indicates that the client does not care about framerate. The +// framerate is specified in mHz, that is framerate of 60000 is 60Hz. +// +// A method of "scale" or "driver" implies a scaling operation of +// the surface, either via a direct scaling operation or a change of +// the output mode. This will override any kind of output scaling, so +// that mapping a surface with a buffer size equal to the mode can +// fill the screen independent of buffer_scale. +// +// A method of "fill" means we don't scale up the buffer, however +// any output scale is applied. This means that you may run into +// an edge case where the application maps a buffer with the same +// size of the output mode but buffer_scale 1 (thus making a +// surface larger than the output). In this case it is allowed to +// downscale the results to fit the screen. +// +// The compositor must reply to this request with a configure event +// with the dimensions for the output on which the surface will +// be made fullscreen. +// +func (p *ShellSurface) SetFullscreen(method uint32, framerate uint32, output *Output) error { + return p.Context().SendRequest(p, 5, method, framerate, output) +} + +// SetPopup will make the surface a popup surface. +// +// +// Map the surface as a popup. +// +// A popup surface is a transient surface with an added pointer +// grab. +// +// An existing implicit grab will be changed to owner-events mode, +// and the popup grab will continue after the implicit grab ends +// (i.e. releasing the mouse button does not cause the popup to +// be unmapped). +// +// The popup grab continues until the window is destroyed or a +// mouse button is pressed in any other client's window. A click +// in any of the client's surfaces is reported as normal, however, +// clicks in other clients' surfaces will be discarded and trigger +// the callback. +// +// The x and y arguments specify the location of the upper left +// corner of the surface relative to the upper left corner of the +// parent surface, in surface-local coordinates. +// +func (p *ShellSurface) SetPopup(seat *Seat, serial uint32, parent *Surface, x int32, y int32, flags uint32) error { + return p.Context().SendRequest(p, 6, seat, serial, parent, x, y, flags) +} + +// SetMaximized will make the surface a maximized surface. +// +// +// Map the surface as a maximized surface. +// +// If an output parameter is given then the surface will be +// maximized on that output. If the client does not specify the +// output then the compositor will apply its policy - usually +// choosing the output on which the surface has the biggest surface +// area. +// +// The compositor will reply with a configure event telling +// the expected new surface size. The operation is completed +// on the next buffer attach to this surface. +// +// A maximized surface typically fills the entire output it is +// bound to, except for desktop elements such as panels. This is +// the main difference between a maximized shell surface and a +// fullscreen shell surface. +// +// The details depend on the compositor implementation. +// +func (p *ShellSurface) SetMaximized(output *Output) error { + return p.Context().SendRequest(p, 7, output) +} + +// SetTitle will set surface title. +// +// +// Set a short title for the surface. +// +// This string may be used to identify the surface in a task bar, +// window list, or other user interface elements provided by the +// compositor. +// +// The string must be encoded in UTF-8. +// +func (p *ShellSurface) SetTitle(title string) error { + return p.Context().SendRequest(p, 8, title) +} + +// SetClass will set surface class. +// +// +// Set a class for the surface. +// +// The surface class identifies the general class of applications +// to which the surface belongs. A common convention is to use the +// file name (or the full path if it is a non-standard location) of +// the application's .desktop file as the class. +// +func (p *ShellSurface) SetClass(class_ string) error { + return p.Context().SendRequest(p, 9, class_) +} + +const ( + ShellSurfaceResizeNone = 0 + ShellSurfaceResizeTop = 1 + ShellSurfaceResizeBottom = 2 + ShellSurfaceResizeLeft = 4 + ShellSurfaceResizeTopLeft = 5 + ShellSurfaceResizeBottomLeft = 6 + ShellSurfaceResizeRight = 8 + ShellSurfaceResizeTopRight = 9 + ShellSurfaceResizeBottomRight = 10 +) + +const ( + ShellSurfaceTransientInactive = 0x1 +) + +const ( + ShellSurfaceFullscreenMethodDefault = 0 + ShellSurfaceFullscreenMethodScale = 1 + ShellSurfaceFullscreenMethodDriver = 2 + ShellSurfaceFullscreenMethodFill = 3 +) + +type SurfaceEnterEvent struct { + EventContext context.Context + Output *Output +} + +type SurfaceEnterHandler interface { + HandleSurfaceEnter(SurfaceEnterEvent) +} + +func (p *Surface) AddEnterHandler(h SurfaceEnterHandler) { + if h != nil { + p.mu.Lock() + p.enterHandlers = append(p.enterHandlers, h) + p.mu.Unlock() + } +} + +func (p *Surface) RemoveEnterHandler(h SurfaceEnterHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.enterHandlers { + if e == h { + p.enterHandlers = append(p.enterHandlers[:i], p.enterHandlers[i+1:]...) + break + } + } +} + +type SurfaceLeaveEvent struct { + EventContext context.Context + Output *Output +} + +type SurfaceLeaveHandler interface { + HandleSurfaceLeave(SurfaceLeaveEvent) +} + +func (p *Surface) AddLeaveHandler(h SurfaceLeaveHandler) { + if h != nil { + p.mu.Lock() + p.leaveHandlers = append(p.leaveHandlers, h) + p.mu.Unlock() + } +} + +func (p *Surface) RemoveLeaveHandler(h SurfaceLeaveHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.leaveHandlers { + if e == h { + p.leaveHandlers = append(p.leaveHandlers[:i], p.leaveHandlers[i+1:]...) + break + } + } +} + +func (p *Surface) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.enterHandlers) > 0 { + ev := SurfaceEnterEvent{} + ev.EventContext = ctx + ev.Output = event.Proxy(p.Context()).(*Output) + p.mu.RLock() + for _, h := range p.enterHandlers { + h.HandleSurfaceEnter(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.leaveHandlers) > 0 { + ev := SurfaceLeaveEvent{} + ev.EventContext = ctx + ev.Output = event.Proxy(p.Context()).(*Output) + p.mu.RLock() + for _, h := range p.leaveHandlers { + h.HandleSurfaceLeave(ev) + } + p.mu.RUnlock() + } + } +} + +type Surface struct { + BaseProxy + mu sync.RWMutex + enterHandlers []SurfaceEnterHandler + leaveHandlers []SurfaceLeaveHandler +} + +func NewSurface(ctx *Context) *Surface { + ret := new(Surface) + ctx.Register(ret) + return ret +} + +// Destroy will delete surface. +// +// +// Deletes the surface and invalidates its object ID. +// +func (p *Surface) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// Attach will set the surface contents. +// +// +// Set a buffer as the content of this surface. +// +// The new size of the surface is calculated based on the buffer +// size transformed by the inverse buffer_transform and the +// inverse buffer_scale. This means that the supplied buffer +// must be an integer multiple of the buffer_scale. +// +// The x and y arguments specify the location of the new pending +// buffer's upper left corner, relative to the current buffer's upper +// left corner, in surface-local coordinates. In other words, the +// x and y, combined with the new surface size define in which +// directions the surface's size changes. +// +// Surface contents are double-buffered state, see wl_surface.commit. +// +// The initial surface contents are void; there is no content. +// wl_surface.attach assigns the given wl_buffer as the pending +// wl_buffer. wl_surface.commit makes the pending wl_buffer the new +// surface contents, and the size of the surface becomes the size +// calculated from the wl_buffer, as described above. After commit, +// there is no pending buffer until the next attach. +// +// Committing a pending wl_buffer allows the compositor to read the +// pixels in the wl_buffer. The compositor may access the pixels at +// any time after the wl_surface.commit request. When the compositor +// will not access the pixels anymore, it will send the +// wl_buffer.release event. Only after receiving wl_buffer.release, +// the client may reuse the wl_buffer. A wl_buffer that has been +// attached and then replaced by another attach instead of committed +// will not receive a release event, and is not used by the +// compositor. +// +// Destroying the wl_buffer after wl_buffer.release does not change +// the surface contents. However, if the client destroys the +// wl_buffer before receiving the wl_buffer.release event, the surface +// contents become undefined immediately. +// +// If wl_surface.attach is sent with a NULL wl_buffer, the +// following wl_surface.commit will remove the surface content. +// +func (p *Surface) Attach(buffer *Buffer, x int32, y int32) error { + return p.Context().SendRequest(p, 1, buffer, x, y) +} + +// Damage will mark part of the surface damaged. +// +// +// This request is used to describe the regions where the pending +// buffer is different from the current surface contents, and where +// the surface therefore needs to be repainted. The compositor +// ignores the parts of the damage that fall outside of the surface. +// +// Damage is double-buffered state, see wl_surface.commit. +// +// The damage rectangle is specified in surface-local coordinates, +// where x and y specify the upper left corner of the damage rectangle. +// +// The initial value for pending damage is empty: no damage. +// wl_surface.damage adds pending damage: the new pending damage +// is the union of old pending damage and the given rectangle. +// +// wl_surface.commit assigns pending damage as the current damage, +// and clears pending damage. The server will clear the current +// damage as it repaints the surface. +// +// Alternatively, damage can be posted with wl_surface.damage_buffer +// which uses buffer coordinates instead of surface coordinates, +// and is probably the preferred and intuitive way of doing this. +// +func (p *Surface) Damage(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 2, x, y, width, height) +} + +// Frame will request a frame throttling hint. +// +// +// Request a notification when it is a good time to start drawing a new +// frame, by creating a frame callback. This is useful for throttling +// redrawing operations, and driving animations. +// +// When a client is animating on a wl_surface, it can use the 'frame' +// request to get notified when it is a good time to draw and commit the +// next frame of animation. If the client commits an update earlier than +// that, it is likely that some updates will not make it to the display, +// and the client is wasting resources by drawing too often. +// +// The frame request will take effect on the next wl_surface.commit. +// The notification will only be posted for one frame unless +// requested again. For a wl_surface, the notifications are posted in +// the order the frame requests were committed. +// +// The server must send the notifications so that a client +// will not send excessive updates, while still allowing +// the highest possible update rate for clients that wait for the reply +// before drawing again. The server should give some time for the client +// to draw and commit after sending the frame callback events to let it +// hit the next output refresh. +// +// A server should avoid signaling the frame callbacks if the +// surface is not visible in any way, e.g. the surface is off-screen, +// or completely obscured by other opaque surfaces. +// +// The object returned by this request will be destroyed by the +// compositor after the callback is fired and as such the client must not +// attempt to use it after that point. +// +// The callback_data passed in the callback is the current time, in +// milliseconds, with an undefined base. +// +func (p *Surface) Frame() (*Callback, error) { + ret := NewCallback(p.Context()) + return ret, p.Context().SendRequest(p, 3, Proxy(ret)) +} + +// SetOpaqueRegion will set opaque region. +// +// +// This request sets the region of the surface that contains +// opaque content. +// +// The opaque region is an optimization hint for the compositor +// that lets it optimize the redrawing of content behind opaque +// regions. Setting an opaque region is not required for correct +// behaviour, but marking transparent content as opaque will result +// in repaint artifacts. +// +// The opaque region is specified in surface-local coordinates. +// +// The compositor ignores the parts of the opaque region that fall +// outside of the surface. +// +// Opaque region is double-buffered state, see wl_surface.commit. +// +// wl_surface.set_opaque_region changes the pending opaque region. +// wl_surface.commit copies the pending region to the current region. +// Otherwise, the pending and current regions are never changed. +// +// The initial value for an opaque region is empty. Setting the pending +// opaque region has copy semantics, and the wl_region object can be +// destroyed immediately. A NULL wl_region causes the pending opaque +// region to be set to empty. +// +func (p *Surface) SetOpaqueRegion(region *Region) error { + return p.Context().SendRequest(p, 4, region) +} + +// SetInputRegion will set input region. +// +// +// This request sets the region of the surface that can receive +// pointer and touch events. +// +// Input events happening outside of this region will try the next +// surface in the server surface stack. The compositor ignores the +// parts of the input region that fall outside of the surface. +// +// The input region is specified in surface-local coordinates. +// +// Input region is double-buffered state, see wl_surface.commit. +// +// wl_surface.set_input_region changes the pending input region. +// wl_surface.commit copies the pending region to the current region. +// Otherwise the pending and current regions are never changed, +// except cursor and icon surfaces are special cases, see +// wl_pointer.set_cursor and wl_data_device.start_drag. +// +// The initial value for an input region is infinite. That means the +// whole surface will accept input. Setting the pending input region +// has copy semantics, and the wl_region object can be destroyed +// immediately. A NULL wl_region causes the input region to be set +// to infinite. +// +func (p *Surface) SetInputRegion(region *Region) error { + return p.Context().SendRequest(p, 5, region) +} + +// Commit will commit pending surface state. +// +// +// Surface state (input, opaque, and damage regions, attached buffers, +// etc.) is double-buffered. Protocol requests modify the pending state, +// as opposed to the current state in use by the compositor. A commit +// request atomically applies all pending state, replacing the current +// state. After commit, the new pending state is as documented for each +// related request. +// +// On commit, a pending wl_buffer is applied first, and all other state +// second. This means that all coordinates in double-buffered state are +// relative to the new wl_buffer coming into use, except for +// wl_surface.attach itself. If there is no pending wl_buffer, the +// coordinates are relative to the current surface contents. +// +// All requests that need a commit to become effective are documented +// to affect double-buffered state. +// +// Other interfaces may add further double-buffered surface state. +// +func (p *Surface) Commit() error { + return p.Context().SendRequest(p, 6) +} + +// SetBufferTransform will sets the buffer transformation. +// +// +// This request sets an optional transformation on how the compositor +// interprets the contents of the buffer attached to the surface. The +// accepted values for the transform parameter are the values for +// wl_output.transform. +// +// Buffer transform is double-buffered state, see wl_surface.commit. +// +// A newly created surface has its buffer transformation set to normal. +// +// wl_surface.set_buffer_transform changes the pending buffer +// transformation. wl_surface.commit copies the pending buffer +// transformation to the current one. Otherwise, the pending and current +// values are never changed. +// +// The purpose of this request is to allow clients to render content +// according to the output transform, thus permitting the compositor to +// use certain optimizations even if the display is rotated. Using +// hardware overlays and scanning out a client buffer for fullscreen +// surfaces are examples of such optimizations. Those optimizations are +// highly dependent on the compositor implementation, so the use of this +// request should be considered on a case-by-case basis. +// +// Note that if the transform value includes 90 or 270 degree rotation, +// the width of the buffer will become the surface height and the height +// of the buffer will become the surface width. +// +// If transform is not one of the values from the +// wl_output.transform enum the invalid_transform protocol error +// is raised. +// +func (p *Surface) SetBufferTransform(transform int32) error { + return p.Context().SendRequest(p, 7, transform) +} + +// SetBufferScale will sets the buffer scaling factor. +// +// +// This request sets an optional scaling factor on how the compositor +// interprets the contents of the buffer attached to the window. +// +// Buffer scale is double-buffered state, see wl_surface.commit. +// +// A newly created surface has its buffer scale set to 1. +// +// wl_surface.set_buffer_scale changes the pending buffer scale. +// wl_surface.commit copies the pending buffer scale to the current one. +// Otherwise, the pending and current values are never changed. +// +// The purpose of this request is to allow clients to supply higher +// resolution buffer data for use on high resolution outputs. It is +// intended that you pick the same buffer scale as the scale of the +// output that the surface is displayed on. This means the compositor +// can avoid scaling when rendering the surface on that output. +// +// Note that if the scale is larger than 1, then you have to attach +// a buffer that is larger (by a factor of scale in each dimension) +// than the desired surface size. +// +// If scale is not positive the invalid_scale protocol error is +// raised. +// +func (p *Surface) SetBufferScale(scale int32) error { + return p.Context().SendRequest(p, 8, scale) +} + +// DamageBuffer will mark part of the surface damaged using buffer coordinates. +// +// +// This request is used to describe the regions where the pending +// buffer is different from the current surface contents, and where +// the surface therefore needs to be repainted. The compositor +// ignores the parts of the damage that fall outside of the surface. +// +// Damage is double-buffered state, see wl_surface.commit. +// +// The damage rectangle is specified in buffer coordinates, +// where x and y specify the upper left corner of the damage rectangle. +// +// The initial value for pending damage is empty: no damage. +// wl_surface.damage_buffer adds pending damage: the new pending +// damage is the union of old pending damage and the given rectangle. +// +// wl_surface.commit assigns pending damage as the current damage, +// and clears pending damage. The server will clear the current +// damage as it repaints the surface. +// +// This request differs from wl_surface.damage in only one way - it +// takes damage in buffer coordinates instead of surface-local +// coordinates. While this generally is more intuitive than surface +// coordinates, it is especially desirable when using wp_viewport +// or when a drawing library (like EGL) is unaware of buffer scale +// and buffer transform. +// +// Note: Because buffer transformation changes and damage requests may +// be interleaved in the protocol stream, it is impossible to determine +// the actual mapping between surface and buffer damage until +// wl_surface.commit time. Therefore, compositors wishing to take both +// kinds of damage into account will have to accumulate damage from the +// two requests separately and only transform from one to the other +// after receiving the wl_surface.commit. +// +func (p *Surface) DamageBuffer(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 9, x, y, width, height) +} + +const ( + SurfaceErrorInvalidScale = 0 + SurfaceErrorInvalidTransform = 1 +) + +type SeatCapabilitiesEvent struct { + EventContext context.Context + Capabilities uint32 +} + +type SeatCapabilitiesHandler interface { + HandleSeatCapabilities(SeatCapabilitiesEvent) +} + +func (p *Seat) AddCapabilitiesHandler(h SeatCapabilitiesHandler) { + if h != nil { + p.mu.Lock() + p.capabilitiesHandlers = append(p.capabilitiesHandlers, h) + p.mu.Unlock() + } +} + +func (p *Seat) RemoveCapabilitiesHandler(h SeatCapabilitiesHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.capabilitiesHandlers { + if e == h { + p.capabilitiesHandlers = append(p.capabilitiesHandlers[:i], p.capabilitiesHandlers[i+1:]...) + break + } + } +} + +type SeatNameEvent struct { + EventContext context.Context + Name string +} + +type SeatNameHandler interface { + HandleSeatName(SeatNameEvent) +} + +func (p *Seat) AddNameHandler(h SeatNameHandler) { + if h != nil { + p.mu.Lock() + p.nameHandlers = append(p.nameHandlers, h) + p.mu.Unlock() + } +} + +func (p *Seat) RemoveNameHandler(h SeatNameHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.nameHandlers { + if e == h { + p.nameHandlers = append(p.nameHandlers[:i], p.nameHandlers[i+1:]...) + break + } + } +} + +func (p *Seat) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.capabilitiesHandlers) > 0 { + ev := SeatCapabilitiesEvent{} + ev.EventContext = ctx + ev.Capabilities = event.Uint32() + p.mu.RLock() + for _, h := range p.capabilitiesHandlers { + h.HandleSeatCapabilities(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.nameHandlers) > 0 { + ev := SeatNameEvent{} + ev.EventContext = ctx + ev.Name = event.String() + p.mu.RLock() + for _, h := range p.nameHandlers { + h.HandleSeatName(ev) + } + p.mu.RUnlock() + } + } +} + +type Seat struct { + BaseProxy + mu sync.RWMutex + capabilitiesHandlers []SeatCapabilitiesHandler + nameHandlers []SeatNameHandler +} + +func NewSeat(ctx *Context) *Seat { + ret := new(Seat) + ctx.Register(ret) + return ret +} + +// GetPointer will return pointer object. +// +// +// The ID provided will be initialized to the wl_pointer interface +// for this seat. +// +// This request only takes effect if the seat has the pointer +// capability, or has had the pointer capability in the past. +// It is a protocol violation to issue this request on a seat that has +// never had the pointer capability. +// +func (p *Seat) GetPointer() (*Pointer, error) { + ret := NewPointer(p.Context()) + return ret, p.Context().SendRequest(p, 0, Proxy(ret)) +} + +// GetKeyboard will return keyboard object. +// +// +// The ID provided will be initialized to the wl_keyboard interface +// for this seat. +// +// This request only takes effect if the seat has the keyboard +// capability, or has had the keyboard capability in the past. +// It is a protocol violation to issue this request on a seat that has +// never had the keyboard capability. +// +func (p *Seat) GetKeyboard() (*Keyboard, error) { + ret := NewKeyboard(p.Context()) + return ret, p.Context().SendRequest(p, 1, Proxy(ret)) +} + +// GetTouch will return touch object. +// +// +// The ID provided will be initialized to the wl_touch interface +// for this seat. +// +// This request only takes effect if the seat has the touch +// capability, or has had the touch capability in the past. +// It is a protocol violation to issue this request on a seat that has +// never had the touch capability. +// +func (p *Seat) GetTouch() (*Touch, error) { + ret := NewTouch(p.Context()) + return ret, p.Context().SendRequest(p, 2, Proxy(ret)) +} + +// Release will release the seat object. +// +// +// Using this request a client can tell the server that it is not going to +// use the seat object anymore. +// +func (p *Seat) Release() error { + return p.Context().SendRequest(p, 3) +} + +const ( + SeatCapabilityPointer = 1 + SeatCapabilityKeyboard = 2 + SeatCapabilityTouch = 4 +) + +type PointerEnterEvent struct { + EventContext context.Context + Serial uint32 + Surface *Surface + SurfaceX float32 + SurfaceY float32 +} + +type PointerEnterHandler interface { + HandlePointerEnter(PointerEnterEvent) +} + +func (p *Pointer) AddEnterHandler(h PointerEnterHandler) { + if h != nil { + p.mu.Lock() + p.enterHandlers = append(p.enterHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveEnterHandler(h PointerEnterHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.enterHandlers { + if e == h { + p.enterHandlers = append(p.enterHandlers[:i], p.enterHandlers[i+1:]...) + break + } + } +} + +type PointerLeaveEvent struct { + EventContext context.Context + Serial uint32 + Surface *Surface +} + +type PointerLeaveHandler interface { + HandlePointerLeave(PointerLeaveEvent) +} + +func (p *Pointer) AddLeaveHandler(h PointerLeaveHandler) { + if h != nil { + p.mu.Lock() + p.leaveHandlers = append(p.leaveHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveLeaveHandler(h PointerLeaveHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.leaveHandlers { + if e == h { + p.leaveHandlers = append(p.leaveHandlers[:i], p.leaveHandlers[i+1:]...) + break + } + } +} + +type PointerMotionEvent struct { + EventContext context.Context + Time uint32 + SurfaceX float32 + SurfaceY float32 +} + +type PointerMotionHandler interface { + HandlePointerMotion(PointerMotionEvent) +} + +func (p *Pointer) AddMotionHandler(h PointerMotionHandler) { + if h != nil { + p.mu.Lock() + p.motionHandlers = append(p.motionHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveMotionHandler(h PointerMotionHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.motionHandlers { + if e == h { + p.motionHandlers = append(p.motionHandlers[:i], p.motionHandlers[i+1:]...) + break + } + } +} + +type PointerButtonEvent struct { + EventContext context.Context + Serial uint32 + Time uint32 + Button uint32 + State uint32 +} + +type PointerButtonHandler interface { + HandlePointerButton(PointerButtonEvent) +} + +func (p *Pointer) AddButtonHandler(h PointerButtonHandler) { + if h != nil { + p.mu.Lock() + p.buttonHandlers = append(p.buttonHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveButtonHandler(h PointerButtonHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.buttonHandlers { + if e == h { + p.buttonHandlers = append(p.buttonHandlers[:i], p.buttonHandlers[i+1:]...) + break + } + } +} + +type PointerAxisEvent struct { + EventContext context.Context + Time uint32 + Axis uint32 + Value float32 +} + +type PointerAxisHandler interface { + HandlePointerAxis(PointerAxisEvent) +} + +func (p *Pointer) AddAxisHandler(h PointerAxisHandler) { + if h != nil { + p.mu.Lock() + p.axisHandlers = append(p.axisHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveAxisHandler(h PointerAxisHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.axisHandlers { + if e == h { + p.axisHandlers = append(p.axisHandlers[:i], p.axisHandlers[i+1:]...) + break + } + } +} + +type PointerFrameEvent struct { + EventContext context.Context +} + +type PointerFrameHandler interface { + HandlePointerFrame(PointerFrameEvent) +} + +func (p *Pointer) AddFrameHandler(h PointerFrameHandler) { + if h != nil { + p.mu.Lock() + p.frameHandlers = append(p.frameHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveFrameHandler(h PointerFrameHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.frameHandlers { + if e == h { + p.frameHandlers = append(p.frameHandlers[:i], p.frameHandlers[i+1:]...) + break + } + } +} + +type PointerAxisSourceEvent struct { + EventContext context.Context + AxisSource uint32 +} + +type PointerAxisSourceHandler interface { + HandlePointerAxisSource(PointerAxisSourceEvent) +} + +func (p *Pointer) AddAxisSourceHandler(h PointerAxisSourceHandler) { + if h != nil { + p.mu.Lock() + p.axisSourceHandlers = append(p.axisSourceHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveAxisSourceHandler(h PointerAxisSourceHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.axisSourceHandlers { + if e == h { + p.axisSourceHandlers = append(p.axisSourceHandlers[:i], p.axisSourceHandlers[i+1:]...) + break + } + } +} + +type PointerAxisStopEvent struct { + EventContext context.Context + Time uint32 + Axis uint32 +} + +type PointerAxisStopHandler interface { + HandlePointerAxisStop(PointerAxisStopEvent) +} + +func (p *Pointer) AddAxisStopHandler(h PointerAxisStopHandler) { + if h != nil { + p.mu.Lock() + p.axisStopHandlers = append(p.axisStopHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveAxisStopHandler(h PointerAxisStopHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.axisStopHandlers { + if e == h { + p.axisStopHandlers = append(p.axisStopHandlers[:i], p.axisStopHandlers[i+1:]...) + break + } + } +} + +type PointerAxisDiscreteEvent struct { + EventContext context.Context + Axis uint32 + Discrete int32 +} + +type PointerAxisDiscreteHandler interface { + HandlePointerAxisDiscrete(PointerAxisDiscreteEvent) +} + +func (p *Pointer) AddAxisDiscreteHandler(h PointerAxisDiscreteHandler) { + if h != nil { + p.mu.Lock() + p.axisDiscreteHandlers = append(p.axisDiscreteHandlers, h) + p.mu.Unlock() + } +} + +func (p *Pointer) RemoveAxisDiscreteHandler(h PointerAxisDiscreteHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.axisDiscreteHandlers { + if e == h { + p.axisDiscreteHandlers = append(p.axisDiscreteHandlers[:i], p.axisDiscreteHandlers[i+1:]...) + break + } + } +} + +func (p *Pointer) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.enterHandlers) > 0 { + ev := PointerEnterEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Surface = event.Proxy(p.Context()).(*Surface) + ev.SurfaceX = event.Float32() + ev.SurfaceY = event.Float32() + p.mu.RLock() + for _, h := range p.enterHandlers { + h.HandlePointerEnter(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.leaveHandlers) > 0 { + ev := PointerLeaveEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Surface = event.Proxy(p.Context()).(*Surface) + p.mu.RLock() + for _, h := range p.leaveHandlers { + h.HandlePointerLeave(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.motionHandlers) > 0 { + ev := PointerMotionEvent{} + ev.EventContext = ctx + ev.Time = event.Uint32() + ev.SurfaceX = event.Float32() + ev.SurfaceY = event.Float32() + p.mu.RLock() + for _, h := range p.motionHandlers { + h.HandlePointerMotion(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.buttonHandlers) > 0 { + ev := PointerButtonEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Time = event.Uint32() + ev.Button = event.Uint32() + ev.State = event.Uint32() + p.mu.RLock() + for _, h := range p.buttonHandlers { + h.HandlePointerButton(ev) + } + p.mu.RUnlock() + } + case 4: + if len(p.axisHandlers) > 0 { + ev := PointerAxisEvent{} + ev.EventContext = ctx + ev.Time = event.Uint32() + ev.Axis = event.Uint32() + ev.Value = event.Float32() + p.mu.RLock() + for _, h := range p.axisHandlers { + h.HandlePointerAxis(ev) + } + p.mu.RUnlock() + } + case 5: + if len(p.frameHandlers) > 0 { + ev := PointerFrameEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.frameHandlers { + h.HandlePointerFrame(ev) + } + p.mu.RUnlock() + } + case 6: + if len(p.axisSourceHandlers) > 0 { + ev := PointerAxisSourceEvent{} + ev.EventContext = ctx + ev.AxisSource = event.Uint32() + p.mu.RLock() + for _, h := range p.axisSourceHandlers { + h.HandlePointerAxisSource(ev) + } + p.mu.RUnlock() + } + case 7: + if len(p.axisStopHandlers) > 0 { + ev := PointerAxisStopEvent{} + ev.EventContext = ctx + ev.Time = event.Uint32() + ev.Axis = event.Uint32() + p.mu.RLock() + for _, h := range p.axisStopHandlers { + h.HandlePointerAxisStop(ev) + } + p.mu.RUnlock() + } + case 8: + if len(p.axisDiscreteHandlers) > 0 { + ev := PointerAxisDiscreteEvent{} + ev.EventContext = ctx + ev.Axis = event.Uint32() + ev.Discrete = event.Int32() + p.mu.RLock() + for _, h := range p.axisDiscreteHandlers { + h.HandlePointerAxisDiscrete(ev) + } + p.mu.RUnlock() + } + } +} + +type Pointer struct { + BaseProxy + mu sync.RWMutex + enterHandlers []PointerEnterHandler + leaveHandlers []PointerLeaveHandler + motionHandlers []PointerMotionHandler + buttonHandlers []PointerButtonHandler + axisHandlers []PointerAxisHandler + frameHandlers []PointerFrameHandler + axisSourceHandlers []PointerAxisSourceHandler + axisStopHandlers []PointerAxisStopHandler + axisDiscreteHandlers []PointerAxisDiscreteHandler +} + +func NewPointer(ctx *Context) *Pointer { + ret := new(Pointer) + ctx.Register(ret) + return ret +} + +// SetCursor will set the pointer surface. +// +// +// Set the pointer surface, i.e., the surface that contains the +// pointer image (cursor). This request gives the surface the role +// of a cursor. If the surface already has another role, it raises +// a protocol error. +// +// The cursor actually changes only if the pointer +// focus for this device is one of the requesting client's surfaces +// or the surface parameter is the current pointer surface. If +// there was a previous surface set with this request it is +// replaced. If surface is NULL, the pointer image is hidden. +// +// The parameters hotspot_x and hotspot_y define the position of +// the pointer surface relative to the pointer location. Its +// top-left corner is always at (x, y) - (hotspot_x, hotspot_y), +// where (x, y) are the coordinates of the pointer location, in +// surface-local coordinates. +// +// On surface.attach requests to the pointer surface, hotspot_x +// and hotspot_y are decremented by the x and y parameters +// passed to the request. Attach must be confirmed by +// wl_surface.commit as usual. +// +// The hotspot can also be updated by passing the currently set +// pointer surface to this request with new values for hotspot_x +// and hotspot_y. +// +// The current and pending input regions of the wl_surface are +// cleared, and wl_surface.set_input_region is ignored until the +// wl_surface is no longer used as the cursor. When the use as a +// cursor ends, the current and pending input regions become +// undefined, and the wl_surface is unmapped. +// +func (p *Pointer) SetCursor(serial uint32, surface *Surface, hotspot_x int32, hotspot_y int32) error { + return p.Context().SendRequest(p, 0, serial, surface, hotspot_x, hotspot_y) +} + +// Release will release the pointer object. +// +// +// Using this request a client can tell the server that it is not going to +// use the pointer object anymore. +// +// This request destroys the pointer proxy object, so clients must not call +// wl_pointer_destroy() after using this request. +// +func (p *Pointer) Release() error { + return p.Context().SendRequest(p, 1) +} + +const ( + PointerErrorRole = 0 +) + +const ( + PointerButtonStateReleased = 0 + PointerButtonStatePressed = 1 +) + +const ( + PointerAxisVerticalScroll = 0 + PointerAxisHorizontalScroll = 1 +) + +const ( + PointerAxisSourceWheel = 0 + PointerAxisSourceFinger = 1 + PointerAxisSourceContinuous = 2 + PointerAxisSourceWheelTilt = 3 +) + +type KeyboardKeymapEvent struct { + EventContext context.Context + Format uint32 + Fd uintptr + Size uint32 +} + +type KeyboardKeymapHandler interface { + HandleKeyboardKeymap(KeyboardKeymapEvent) +} + +func (p *Keyboard) AddKeymapHandler(h KeyboardKeymapHandler) { + if h != nil { + p.mu.Lock() + p.keymapHandlers = append(p.keymapHandlers, h) + p.mu.Unlock() + } +} + +func (p *Keyboard) RemoveKeymapHandler(h KeyboardKeymapHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.keymapHandlers { + if e == h { + p.keymapHandlers = append(p.keymapHandlers[:i], p.keymapHandlers[i+1:]...) + break + } + } +} + +type KeyboardEnterEvent struct { + EventContext context.Context + Serial uint32 + Surface *Surface + Keys []int32 +} + +type KeyboardEnterHandler interface { + HandleKeyboardEnter(KeyboardEnterEvent) +} + +func (p *Keyboard) AddEnterHandler(h KeyboardEnterHandler) { + if h != nil { + p.mu.Lock() + p.enterHandlers = append(p.enterHandlers, h) + p.mu.Unlock() + } +} + +func (p *Keyboard) RemoveEnterHandler(h KeyboardEnterHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.enterHandlers { + if e == h { + p.enterHandlers = append(p.enterHandlers[:i], p.enterHandlers[i+1:]...) + break + } + } +} + +type KeyboardLeaveEvent struct { + EventContext context.Context + Serial uint32 + Surface *Surface +} + +type KeyboardLeaveHandler interface { + HandleKeyboardLeave(KeyboardLeaveEvent) +} + +func (p *Keyboard) AddLeaveHandler(h KeyboardLeaveHandler) { + if h != nil { + p.mu.Lock() + p.leaveHandlers = append(p.leaveHandlers, h) + p.mu.Unlock() + } +} + +func (p *Keyboard) RemoveLeaveHandler(h KeyboardLeaveHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.leaveHandlers { + if e == h { + p.leaveHandlers = append(p.leaveHandlers[:i], p.leaveHandlers[i+1:]...) + break + } + } +} + +type KeyboardKeyEvent struct { + EventContext context.Context + Serial uint32 + Time uint32 + Key uint32 + State uint32 +} + +type KeyboardKeyHandler interface { + HandleKeyboardKey(KeyboardKeyEvent) +} + +func (p *Keyboard) AddKeyHandler(h KeyboardKeyHandler) { + if h != nil { + p.mu.Lock() + p.keyHandlers = append(p.keyHandlers, h) + p.mu.Unlock() + } +} + +func (p *Keyboard) RemoveKeyHandler(h KeyboardKeyHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.keyHandlers { + if e == h { + p.keyHandlers = append(p.keyHandlers[:i], p.keyHandlers[i+1:]...) + break + } + } +} + +type KeyboardModifiersEvent struct { + EventContext context.Context + Serial uint32 + ModsDepressed uint32 + ModsLatched uint32 + ModsLocked uint32 + Group uint32 +} + +type KeyboardModifiersHandler interface { + HandleKeyboardModifiers(KeyboardModifiersEvent) +} + +func (p *Keyboard) AddModifiersHandler(h KeyboardModifiersHandler) { + if h != nil { + p.mu.Lock() + p.modifiersHandlers = append(p.modifiersHandlers, h) + p.mu.Unlock() + } +} + +func (p *Keyboard) RemoveModifiersHandler(h KeyboardModifiersHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.modifiersHandlers { + if e == h { + p.modifiersHandlers = append(p.modifiersHandlers[:i], p.modifiersHandlers[i+1:]...) + break + } + } +} + +type KeyboardRepeatInfoEvent struct { + EventContext context.Context + Rate int32 + Delay int32 +} + +type KeyboardRepeatInfoHandler interface { + HandleKeyboardRepeatInfo(KeyboardRepeatInfoEvent) +} + +func (p *Keyboard) AddRepeatInfoHandler(h KeyboardRepeatInfoHandler) { + if h != nil { + p.mu.Lock() + p.repeatInfoHandlers = append(p.repeatInfoHandlers, h) + p.mu.Unlock() + } +} + +func (p *Keyboard) RemoveRepeatInfoHandler(h KeyboardRepeatInfoHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.repeatInfoHandlers { + if e == h { + p.repeatInfoHandlers = append(p.repeatInfoHandlers[:i], p.repeatInfoHandlers[i+1:]...) + break + } + } +} + +func (p *Keyboard) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.keymapHandlers) > 0 { + ev := KeyboardKeymapEvent{} + ev.EventContext = ctx + ev.Format = event.Uint32() + ev.Fd = event.FD() + ev.Size = event.Uint32() + p.mu.RLock() + for _, h := range p.keymapHandlers { + h.HandleKeyboardKeymap(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.enterHandlers) > 0 { + ev := KeyboardEnterEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Surface = event.Proxy(p.Context()).(*Surface) + ev.Keys = event.Array() + p.mu.RLock() + for _, h := range p.enterHandlers { + h.HandleKeyboardEnter(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.leaveHandlers) > 0 { + ev := KeyboardLeaveEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Surface = event.Proxy(p.Context()).(*Surface) + p.mu.RLock() + for _, h := range p.leaveHandlers { + h.HandleKeyboardLeave(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.keyHandlers) > 0 { + ev := KeyboardKeyEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Time = event.Uint32() + ev.Key = event.Uint32() + ev.State = event.Uint32() + p.mu.RLock() + for _, h := range p.keyHandlers { + h.HandleKeyboardKey(ev) + } + p.mu.RUnlock() + } + case 4: + if len(p.modifiersHandlers) > 0 { + ev := KeyboardModifiersEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.ModsDepressed = event.Uint32() + ev.ModsLatched = event.Uint32() + ev.ModsLocked = event.Uint32() + ev.Group = event.Uint32() + p.mu.RLock() + for _, h := range p.modifiersHandlers { + h.HandleKeyboardModifiers(ev) + } + p.mu.RUnlock() + } + case 5: + if len(p.repeatInfoHandlers) > 0 { + ev := KeyboardRepeatInfoEvent{} + ev.EventContext = ctx + ev.Rate = event.Int32() + ev.Delay = event.Int32() + p.mu.RLock() + for _, h := range p.repeatInfoHandlers { + h.HandleKeyboardRepeatInfo(ev) + } + p.mu.RUnlock() + } + } +} + +type Keyboard struct { + BaseProxy + mu sync.RWMutex + keymapHandlers []KeyboardKeymapHandler + enterHandlers []KeyboardEnterHandler + leaveHandlers []KeyboardLeaveHandler + keyHandlers []KeyboardKeyHandler + modifiersHandlers []KeyboardModifiersHandler + repeatInfoHandlers []KeyboardRepeatInfoHandler +} + +func NewKeyboard(ctx *Context) *Keyboard { + ret := new(Keyboard) + ctx.Register(ret) + return ret +} + +// Release will release the keyboard object. +// +// +func (p *Keyboard) Release() error { + return p.Context().SendRequest(p, 0) +} + +const ( + KeyboardKeymapFormatNoKeymap = 0 + KeyboardKeymapFormatXkbV1 = 1 +) + +const ( + KeyboardKeyStateReleased = 0 + KeyboardKeyStatePressed = 1 +) + +type TouchDownEvent struct { + EventContext context.Context + Serial uint32 + Time uint32 + Surface *Surface + Id int32 + X float32 + Y float32 +} + +type TouchDownHandler interface { + HandleTouchDown(TouchDownEvent) +} + +func (p *Touch) AddDownHandler(h TouchDownHandler) { + if h != nil { + p.mu.Lock() + p.downHandlers = append(p.downHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveDownHandler(h TouchDownHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.downHandlers { + if e == h { + p.downHandlers = append(p.downHandlers[:i], p.downHandlers[i+1:]...) + break + } + } +} + +type TouchUpEvent struct { + EventContext context.Context + Serial uint32 + Time uint32 + Id int32 +} + +type TouchUpHandler interface { + HandleTouchUp(TouchUpEvent) +} + +func (p *Touch) AddUpHandler(h TouchUpHandler) { + if h != nil { + p.mu.Lock() + p.upHandlers = append(p.upHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveUpHandler(h TouchUpHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.upHandlers { + if e == h { + p.upHandlers = append(p.upHandlers[:i], p.upHandlers[i+1:]...) + break + } + } +} + +type TouchMotionEvent struct { + EventContext context.Context + Time uint32 + Id int32 + X float32 + Y float32 +} + +type TouchMotionHandler interface { + HandleTouchMotion(TouchMotionEvent) +} + +func (p *Touch) AddMotionHandler(h TouchMotionHandler) { + if h != nil { + p.mu.Lock() + p.motionHandlers = append(p.motionHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveMotionHandler(h TouchMotionHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.motionHandlers { + if e == h { + p.motionHandlers = append(p.motionHandlers[:i], p.motionHandlers[i+1:]...) + break + } + } +} + +type TouchFrameEvent struct { + EventContext context.Context +} + +type TouchFrameHandler interface { + HandleTouchFrame(TouchFrameEvent) +} + +func (p *Touch) AddFrameHandler(h TouchFrameHandler) { + if h != nil { + p.mu.Lock() + p.frameHandlers = append(p.frameHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveFrameHandler(h TouchFrameHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.frameHandlers { + if e == h { + p.frameHandlers = append(p.frameHandlers[:i], p.frameHandlers[i+1:]...) + break + } + } +} + +type TouchCancelEvent struct { + EventContext context.Context +} + +type TouchCancelHandler interface { + HandleTouchCancel(TouchCancelEvent) +} + +func (p *Touch) AddCancelHandler(h TouchCancelHandler) { + if h != nil { + p.mu.Lock() + p.cancelHandlers = append(p.cancelHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveCancelHandler(h TouchCancelHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.cancelHandlers { + if e == h { + p.cancelHandlers = append(p.cancelHandlers[:i], p.cancelHandlers[i+1:]...) + break + } + } +} + +type TouchShapeEvent struct { + EventContext context.Context + Id int32 + Major float32 + Minor float32 +} + +type TouchShapeHandler interface { + HandleTouchShape(TouchShapeEvent) +} + +func (p *Touch) AddShapeHandler(h TouchShapeHandler) { + if h != nil { + p.mu.Lock() + p.shapeHandlers = append(p.shapeHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveShapeHandler(h TouchShapeHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.shapeHandlers { + if e == h { + p.shapeHandlers = append(p.shapeHandlers[:i], p.shapeHandlers[i+1:]...) + break + } + } +} + +type TouchOrientationEvent struct { + EventContext context.Context + Id int32 + Orientation float32 +} + +type TouchOrientationHandler interface { + HandleTouchOrientation(TouchOrientationEvent) +} + +func (p *Touch) AddOrientationHandler(h TouchOrientationHandler) { + if h != nil { + p.mu.Lock() + p.orientationHandlers = append(p.orientationHandlers, h) + p.mu.Unlock() + } +} + +func (p *Touch) RemoveOrientationHandler(h TouchOrientationHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.orientationHandlers { + if e == h { + p.orientationHandlers = append(p.orientationHandlers[:i], p.orientationHandlers[i+1:]...) + break + } + } +} + +func (p *Touch) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.downHandlers) > 0 { + ev := TouchDownEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Time = event.Uint32() + ev.Surface = event.Proxy(p.Context()).(*Surface) + ev.Id = event.Int32() + ev.X = event.Float32() + ev.Y = event.Float32() + p.mu.RLock() + for _, h := range p.downHandlers { + h.HandleTouchDown(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.upHandlers) > 0 { + ev := TouchUpEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + ev.Time = event.Uint32() + ev.Id = event.Int32() + p.mu.RLock() + for _, h := range p.upHandlers { + h.HandleTouchUp(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.motionHandlers) > 0 { + ev := TouchMotionEvent{} + ev.EventContext = ctx + ev.Time = event.Uint32() + ev.Id = event.Int32() + ev.X = event.Float32() + ev.Y = event.Float32() + p.mu.RLock() + for _, h := range p.motionHandlers { + h.HandleTouchMotion(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.frameHandlers) > 0 { + ev := TouchFrameEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.frameHandlers { + h.HandleTouchFrame(ev) + } + p.mu.RUnlock() + } + case 4: + if len(p.cancelHandlers) > 0 { + ev := TouchCancelEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.cancelHandlers { + h.HandleTouchCancel(ev) + } + p.mu.RUnlock() + } + case 5: + if len(p.shapeHandlers) > 0 { + ev := TouchShapeEvent{} + ev.EventContext = ctx + ev.Id = event.Int32() + ev.Major = event.Float32() + ev.Minor = event.Float32() + p.mu.RLock() + for _, h := range p.shapeHandlers { + h.HandleTouchShape(ev) + } + p.mu.RUnlock() + } + case 6: + if len(p.orientationHandlers) > 0 { + ev := TouchOrientationEvent{} + ev.EventContext = ctx + ev.Id = event.Int32() + ev.Orientation = event.Float32() + p.mu.RLock() + for _, h := range p.orientationHandlers { + h.HandleTouchOrientation(ev) + } + p.mu.RUnlock() + } + } +} + +type Touch struct { + BaseProxy + mu sync.RWMutex + downHandlers []TouchDownHandler + upHandlers []TouchUpHandler + motionHandlers []TouchMotionHandler + frameHandlers []TouchFrameHandler + cancelHandlers []TouchCancelHandler + shapeHandlers []TouchShapeHandler + orientationHandlers []TouchOrientationHandler +} + +func NewTouch(ctx *Context) *Touch { + ret := new(Touch) + ctx.Register(ret) + return ret +} + +// Release will release the touch object. +// +// +func (p *Touch) Release() error { + return p.Context().SendRequest(p, 0) +} + +type OutputGeometryEvent struct { + EventContext context.Context + X int32 + Y int32 + PhysicalWidth int32 + PhysicalHeight int32 + Subpixel int32 + Make string + Model string + Transform int32 +} + +type OutputGeometryHandler interface { + HandleOutputGeometry(OutputGeometryEvent) +} + +func (p *Output) AddGeometryHandler(h OutputGeometryHandler) { + if h != nil { + p.mu.Lock() + p.geometryHandlers = append(p.geometryHandlers, h) + p.mu.Unlock() + } +} + +func (p *Output) RemoveGeometryHandler(h OutputGeometryHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.geometryHandlers { + if e == h { + p.geometryHandlers = append(p.geometryHandlers[:i], p.geometryHandlers[i+1:]...) + break + } + } +} + +type OutputModeEvent struct { + EventContext context.Context + Flags uint32 + Width int32 + Height int32 + Refresh int32 +} + +type OutputModeHandler interface { + HandleOutputMode(OutputModeEvent) +} + +func (p *Output) AddModeHandler(h OutputModeHandler) { + if h != nil { + p.mu.Lock() + p.modeHandlers = append(p.modeHandlers, h) + p.mu.Unlock() + } +} + +func (p *Output) RemoveModeHandler(h OutputModeHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.modeHandlers { + if e == h { + p.modeHandlers = append(p.modeHandlers[:i], p.modeHandlers[i+1:]...) + break + } + } +} + +type OutputDoneEvent struct { + EventContext context.Context +} + +type OutputDoneHandler interface { + HandleOutputDone(OutputDoneEvent) +} + +func (p *Output) AddDoneHandler(h OutputDoneHandler) { + if h != nil { + p.mu.Lock() + p.doneHandlers = append(p.doneHandlers, h) + p.mu.Unlock() + } +} + +func (p *Output) RemoveDoneHandler(h OutputDoneHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.doneHandlers { + if e == h { + p.doneHandlers = append(p.doneHandlers[:i], p.doneHandlers[i+1:]...) + break + } + } +} + +type OutputScaleEvent struct { + EventContext context.Context + Factor int32 +} + +type OutputScaleHandler interface { + HandleOutputScale(OutputScaleEvent) +} + +func (p *Output) AddScaleHandler(h OutputScaleHandler) { + if h != nil { + p.mu.Lock() + p.scaleHandlers = append(p.scaleHandlers, h) + p.mu.Unlock() + } +} + +func (p *Output) RemoveScaleHandler(h OutputScaleHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.scaleHandlers { + if e == h { + p.scaleHandlers = append(p.scaleHandlers[:i], p.scaleHandlers[i+1:]...) + break + } + } +} + +func (p *Output) Dispatch(ctx context.Context, event *Event) { + switch event.Opcode { + case 0: + if len(p.geometryHandlers) > 0 { + ev := OutputGeometryEvent{} + ev.EventContext = ctx + ev.X = event.Int32() + ev.Y = event.Int32() + ev.PhysicalWidth = event.Int32() + ev.PhysicalHeight = event.Int32() + ev.Subpixel = event.Int32() + ev.Make = event.String() + ev.Model = event.String() + ev.Transform = event.Int32() + p.mu.RLock() + for _, h := range p.geometryHandlers { + h.HandleOutputGeometry(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.modeHandlers) > 0 { + ev := OutputModeEvent{} + ev.EventContext = ctx + ev.Flags = event.Uint32() + ev.Width = event.Int32() + ev.Height = event.Int32() + ev.Refresh = event.Int32() + p.mu.RLock() + for _, h := range p.modeHandlers { + h.HandleOutputMode(ev) + } + p.mu.RUnlock() + } + case 2: + if len(p.doneHandlers) > 0 { + ev := OutputDoneEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.doneHandlers { + h.HandleOutputDone(ev) + } + p.mu.RUnlock() + } + case 3: + if len(p.scaleHandlers) > 0 { + ev := OutputScaleEvent{} + ev.EventContext = ctx + ev.Factor = event.Int32() + p.mu.RLock() + for _, h := range p.scaleHandlers { + h.HandleOutputScale(ev) + } + p.mu.RUnlock() + } + } +} + +type Output struct { + BaseProxy + mu sync.RWMutex + geometryHandlers []OutputGeometryHandler + modeHandlers []OutputModeHandler + doneHandlers []OutputDoneHandler + scaleHandlers []OutputScaleHandler +} + +func NewOutput(ctx *Context) *Output { + ret := new(Output) + ctx.Register(ret) + return ret +} + +// Release will release the output object. +// +// +// Using this request a client can tell the server that it is not going to +// use the output object anymore. +// +func (p *Output) Release() error { + return p.Context().SendRequest(p, 0) +} + +const ( + OutputSubpixelUnknown = 0 + OutputSubpixelNone = 1 + OutputSubpixelHorizontalRgb = 2 + OutputSubpixelHorizontalBgr = 3 + OutputSubpixelVerticalRgb = 4 + OutputSubpixelVerticalBgr = 5 +) + +const ( + OutputTransformNormal = 0 + OutputTransform90 = 1 + OutputTransform180 = 2 + OutputTransform270 = 3 + OutputTransformFlipped = 4 + OutputTransformFlipped90 = 5 + OutputTransformFlipped180 = 6 + OutputTransformFlipped270 = 7 +) + +const ( + OutputModeCurrent = 0x1 + OutputModePreferred = 0x2 +) + +type Region struct { + BaseProxy +} + +func NewRegion(ctx *Context) *Region { + ret := new(Region) + ctx.Register(ret) + return ret +} + +// Destroy will destroy region. +// +// +// Destroy the region. This will invalidate the object ID. +// +func (p *Region) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// Add will add rectangle to region. +// +// +// Add the specified rectangle to the region. +// +func (p *Region) Add(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 1, x, y, width, height) +} + +// Subtract will subtract rectangle from region. +// +// +// Subtract the specified rectangle from the region. +// +func (p *Region) Subtract(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 2, x, y, width, height) +} + +type Subcompositor struct { + BaseProxy +} + +func NewSubcompositor(ctx *Context) *Subcompositor { + ret := new(Subcompositor) + ctx.Register(ret) + return ret +} + +// Destroy will unbind from the subcompositor interface. +// +// +// Informs the server that the client will not be using this +// protocol object anymore. This does not affect any other +// objects, wl_subsurface objects included. +// +func (p *Subcompositor) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// GetSubsurface will give a surface the role sub-surface. +// +// +// Create a sub-surface interface for the given surface, and +// associate it with the given parent surface. This turns a +// plain wl_surface into a sub-surface. +// +// The to-be sub-surface must not already have another role, and it +// must not have an existing wl_subsurface object. Otherwise a protocol +// error is raised. +// +// Adding sub-surfaces to a parent is a double-buffered operation on the +// parent (see wl_surface.commit). The effect of adding a sub-surface +// becomes visible on the next time the state of the parent surface is +// applied. +// +// This request modifies the behaviour of wl_surface.commit request on +// the sub-surface, see the documentation on wl_subsurface interface. +// +func (p *Subcompositor) GetSubsurface(surface *Surface, parent *Surface) (*Subsurface, error) { + ret := NewSubsurface(p.Context()) + return ret, p.Context().SendRequest(p, 1, Proxy(ret), surface, parent) +} + +const ( + SubcompositorErrorBadSurface = 0 +) + +type Subsurface struct { + BaseProxy +} + +func NewSubsurface(ctx *Context) *Subsurface { + ret := new(Subsurface) + ctx.Register(ret) + return ret +} + +// Destroy will remove sub-surface interface. +// +// +// The sub-surface interface is removed from the wl_surface object +// that was turned into a sub-surface with a +// wl_subcompositor.get_subsurface request. The wl_surface's association +// to the parent is deleted, and the wl_surface loses its role as +// a sub-surface. The wl_surface is unmapped immediately. +// +func (p *Subsurface) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// SetPosition will reposition the sub-surface. +// +// +// This schedules a sub-surface position change. +// The sub-surface will be moved so that its origin (top left +// corner pixel) will be at the location x, y of the parent surface +// coordinate system. The coordinates are not restricted to the parent +// surface area. Negative values are allowed. +// +// The scheduled coordinates will take effect whenever the state of the +// parent surface is applied. When this happens depends on whether the +// parent surface is in synchronized mode or not. See +// wl_subsurface.set_sync and wl_subsurface.set_desync for details. +// +// If more than one set_position request is invoked by the client before +// the commit of the parent surface, the position of a new request always +// replaces the scheduled position from any previous request. +// +// The initial position is 0, 0. +// +func (p *Subsurface) SetPosition(x int32, y int32) error { + return p.Context().SendRequest(p, 1, x, y) +} + +// PlaceAbove will restack the sub-surface. +// +// +// This sub-surface is taken from the stack, and put back just +// above the reference surface, changing the z-order of the sub-surfaces. +// The reference surface must be one of the sibling surfaces, or the +// parent surface. Using any other surface, including this sub-surface, +// will cause a protocol error. +// +// The z-order is double-buffered. Requests are handled in order and +// applied immediately to a pending state. The final pending state is +// copied to the active state the next time the state of the parent +// surface is applied. When this happens depends on whether the parent +// surface is in synchronized mode or not. See wl_subsurface.set_sync and +// wl_subsurface.set_desync for details. +// +// A new sub-surface is initially added as the top-most in the stack +// of its siblings and parent. +// +func (p *Subsurface) PlaceAbove(sibling *Surface) error { + return p.Context().SendRequest(p, 2, sibling) +} + +// PlaceBelow will restack the sub-surface. +// +// +// The sub-surface is placed just below the reference surface. +// See wl_subsurface.place_above. +// +func (p *Subsurface) PlaceBelow(sibling *Surface) error { + return p.Context().SendRequest(p, 3, sibling) +} + +// SetSync will set sub-surface to synchronized mode. +// +// +// Change the commit behaviour of the sub-surface to synchronized +// mode, also described as the parent dependent mode. +// +// In synchronized mode, wl_surface.commit on a sub-surface will +// accumulate the committed state in a cache, but the state will +// not be applied and hence will not change the compositor output. +// The cached state is applied to the sub-surface immediately after +// the parent surface's state is applied. This ensures atomic +// updates of the parent and all its synchronized sub-surfaces. +// Applying the cached state will invalidate the cache, so further +// parent surface commits do not (re-)apply old state. +// +// See wl_subsurface for the recursive effect of this mode. +// +func (p *Subsurface) SetSync() error { + return p.Context().SendRequest(p, 4) +} + +// SetDesync will set sub-surface to desynchronized mode. +// +// +// Change the commit behaviour of the sub-surface to desynchronized +// mode, also described as independent or freely running mode. +// +// In desynchronized mode, wl_surface.commit on a sub-surface will +// apply the pending state directly, without caching, as happens +// normally with a wl_surface. Calling wl_surface.commit on the +// parent surface has no effect on the sub-surface's wl_surface +// state. This mode allows a sub-surface to be updated on its own. +// +// If cached state exists when wl_surface.commit is called in +// desynchronized mode, the pending state is added to the cached +// state, and applied as a whole. This invalidates the cache. +// +// Note: even if a sub-surface is set to desynchronized, a parent +// sub-surface may override it to behave as synchronized. For details, +// see wl_subsurface. +// +// If a surface's parent surface behaves as desynchronized, then +// the cached state is applied on set_desync. +// +func (p *Subsurface) SetDesync() error { + return p.Context().SendRequest(p, 5) +} + +const ( + SubsurfaceErrorBadSurface = 0 +) diff --git a/src/github.com/dkolbly/wl/common.go b/src/github.com/dkolbly/wl/common.go new file mode 100644 index 00000000..308014d3 --- /dev/null +++ b/src/github.com/dkolbly/wl/common.go @@ -0,0 +1,55 @@ +package wl + +import ( + "context" +) + +type ProxyId uint32 + +type Dispatcher interface { + Dispatch(context.Context, *Event) +} + +type Proxy interface { + Context() *Context + SetContext(c *Context) + Id() ProxyId + SetId(id ProxyId) +} + +type BaseProxy struct { + id ProxyId + ctx *Context +} + +func (p *BaseProxy) Id() ProxyId { + return p.id +} + +func (p *BaseProxy) SetId(id ProxyId) { + p.id = id +} + +func (p *BaseProxy) Context() *Context { + return p.ctx +} + +func (p *BaseProxy) SetContext(c *Context) { + p.ctx = c +} + +type Handler interface { + Handle(ev interface{}) +} + +type eventHandler struct { + f func(interface{}) +} + +func HandlerFunc(f func(interface{})) Handler { + return &eventHandler{f} +} + +func (h *eventHandler) Handle(ev interface{}) { + h.f(ev) +} diff --git a/src/github.com/dkolbly/wl/context.go b/src/github.com/dkolbly/wl/context.go new file mode 100644 index 00000000..3ab3852c --- /dev/null +++ b/src/github.com/dkolbly/wl/context.go @@ -0,0 +1,129 @@ +package wl + +import ( + "context" + "errors" + "io" + "log" + "net" + "os" + "sync" + "time" +) + +func init() { + log.SetFlags(0) +} + +type Context struct { + mu sync.RWMutex + conn *net.UnixConn + currentId ProxyId + objects map[ProxyId]Proxy + dispatchChan chan struct{} + exitChan chan struct{} +} + +func (ctx *Context) Register(proxy Proxy) { + ctx.mu.Lock() + defer ctx.mu.Unlock() + ctx.currentId += 1 + proxy.SetId(ctx.currentId) + proxy.SetContext(ctx) + ctx.objects[ctx.currentId] = proxy +} + +func (ctx *Context) lookupProxy(id ProxyId) Proxy { + ctx.mu.RLock() + defer ctx.mu.RUnlock() + proxy, ok := ctx.objects[id] + if !ok { + return nil + } + return proxy +} + +func (ctx *Context) unregister(proxy Proxy) { + ctx.mu.Lock() + defer ctx.mu.Unlock() + delete(ctx.objects, proxy.Id()) +} + +func (c *Context) Close() { + c.conn.Close() + c.exitChan <- struct{}{} + close(c.dispatchChan) + +} + +func (c *Context) Dispatch() chan<- struct{} { + return c.dispatchChan +} + +func Connect(addr string) (ret *Display, err error) { + runtime_dir := os.Getenv("XDG_RUNTIME_DIR") + if runtime_dir == "" { + return nil, errors.New("XDG_RUNTIME_DIR not set in the environment.") + } + if addr == "" { + addr = os.Getenv("WAYLAND_DISPLAY") + } + if addr == "" { + addr = "wayland-0" + } + addr = runtime_dir + "/" + addr + c := new(Context) + c.objects = make(map[ProxyId]Proxy) + c.currentId = 0 + c.dispatchChan = make(chan struct{}) + c.exitChan = make(chan struct{}) + c.conn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: addr, Net: "unix"}) + if err != nil { + return nil, err + } + c.conn.SetReadDeadline(time.Time{}) + //dispatch events in separate gorutine + go c.run() + return NewDisplay(c), nil +} + +func (c *Context) run() { + ctx := context.Background() + +loop: + for { + select { + case <-c.dispatchChan: + ev, err := c.readEvent() + if err != nil { + if err == io.EOF { + // connection closed + break loop + + } + + if neterr, ok := err.(net.Error); ok && neterr.Timeout() { + log.Print("Timeout Error") + continue + } + + log.Fatal(err) + } + + proxy := c.lookupProxy(ev.pid) + if proxy != nil { + if dispatcher, ok := proxy.(Dispatcher); ok { + dispatcher.Dispatch(ctx, ev) + bytePool.Give(ev.data) + } else { + log.Print("Not dispatched") + } + } else { + log.Print("Proxy NULL") + } + + case <-c.exitChan: + break loop + } + } +} diff --git a/src/github.com/dkolbly/wl/event.go b/src/github.com/dkolbly/wl/event.go new file mode 100644 index 00000000..f65e59bc --- /dev/null +++ b/src/github.com/dkolbly/wl/event.go @@ -0,0 +1,121 @@ +package wl + +import ( + "bytes" + "fmt" + "syscall" +) + +type Event struct { + pid ProxyId + Opcode uint32 + data []byte + scms []syscall.SocketControlMessage + off int +} + +func (c *Context) readEvent() (*Event, error) { + buf := bytePool.Take(8) + control := bytePool.Take(24) + + n, oobn, _, _, err := c.conn.ReadMsgUnix(buf[:], control) + if err != nil { + return nil, err + } + if n != 8 { + return nil, fmt.Errorf("Unable to read message header.") + } + ev := new(Event) + if oobn > 0 { + if oobn > len(control) { + return nil, fmt.Errorf("Unsufficient control msg buffer") + } + scms, err := syscall.ParseSocketControlMessage(control) + if err != nil { + return nil, fmt.Errorf("Control message parse error: %s", err) + } + ev.scms = scms + } + + ev.pid = ProxyId(order.Uint32(buf[0:4])) + ev.Opcode = uint32(order.Uint16(buf[4:6])) + size := uint32(order.Uint16(buf[6:8])) + + // subtract 8 bytes from header + data := bytePool.Take(int(size) - 8) + n, err = c.conn.Read(data) + if err != nil { + return nil, err + } + if n != int(size)-8 { + return nil, fmt.Errorf("Invalid message size.") + } + ev.data = data + + bytePool.Give(buf) + bytePool.Give(control) + + return ev, nil +} + +func (ev *Event) FD() uintptr { + if ev.scms == nil { + return 0 + } + fds, err := syscall.ParseUnixRights(&ev.scms[0]) + if err != nil { + panic("Unable to parse unix rights") + } + //TODO is this required + ev.scms = append(ev.scms, ev.scms[1:]...) + return uintptr(fds[0]) +} + +func (ev *Event) Uint32() uint32 { + buf := ev.next(4) + if len(buf) != 4 { + panic("Unable to read unsigned int") + } + return order.Uint32(buf) +} + +func (ev *Event) Proxy(c *Context) Proxy { + return c.lookupProxy(ProxyId(ev.Uint32())) +} + +func (ev *Event) String() string { + l := int(ev.Uint32()) + buf := ev.next(l) + if len(buf) != l { + panic("Unable to read string") + } + ret := string(bytes.TrimRight(buf, "\x00")) + //padding to 32 bit boundary + if (l & 0x3) != 0 { + ev.next(4 - (l & 0x3)) + } + return ret +} + +func (ev *Event) Int32() int32 { + return int32(ev.Uint32()) +} + +func (ev *Event) Float32() float32 { + return float32(fixedToFloat64(ev.Int32())) +} + +func (ev *Event) Array() []int32 { + l := int(ev.Uint32()) + arr := make([]int32, l/4) + for i := range arr { + arr[i] = ev.Int32() + } + return arr +} + +func (ev *Event) next(n int) []byte { + ret := ev.data[ev.off : ev.off+n] + ev.off += n + return ret +} diff --git a/src/github.com/dkolbly/wl/request.go b/src/github.com/dkolbly/wl/request.go new file mode 100644 index 00000000..95bde526 --- /dev/null +++ b/src/github.com/dkolbly/wl/request.go @@ -0,0 +1,112 @@ +package wl + +import ( + "errors" + "net" + "syscall" +) + +type Request struct { + pid ProxyId + opcode uint32 + data []byte + oob []byte +} + +func (context *Context) SendRequest(proxy Proxy, opcode uint32, args ...interface{}) (err error) { + req := Request{ + pid: proxy.Id(), + opcode: opcode, + } + + for _, arg := range args { + req.Write(arg) + } + + return writeRequest(context.conn, req) +} + +func (r *Request) Write(arg interface{}) { + switch t := arg.(type) { + case Proxy: + r.PutProxy(t) + case uint32: + r.PutUint32(t) + case int32: + r.PutInt32(t) + case float32: + r.PutFloat32(t) + case string: + r.PutString(t) + case []int32: + r.PutArray(t) + case uintptr: + r.PutFd(t) + default: + panic("Invalid Wayland request parameter type.") + } +} + +func (r *Request) PutUint32(u uint32) { + buf := bytePool.Take(4) + order.PutUint32(buf, u) + r.data = append(r.data, buf...) +} + +func (r *Request) PutProxy(p Proxy) { + r.PutUint32(uint32(p.Id())) +} + +func (r *Request) PutInt32(i int32) { + r.PutUint32(uint32(i)) +} + +func (r *Request) PutFloat32(f float32) { + fx := float64ToFixed(float64(f)) + r.PutUint32(uint32(fx)) +} + +func (r *Request) PutString(s string) { + tail := 4 - (len(s) & 0x3) + r.PutUint32(uint32(len(s) + tail)) + r.data = append(r.data, []byte(s)...) + // if padding required + if tail > 0 { + padding := make([]byte, tail) + r.data = append(r.data, padding...) + } +} + +func (r *Request) PutArray(a []int32) { + r.PutUint32(uint32(len(a))) + for _, e := range a { + r.PutUint32(uint32(e)) + } +} + +func (r *Request) PutFd(fd uintptr) { + rights := syscall.UnixRights(int(fd)) + r.oob = append(r.oob, rights...) +} + +func writeRequest(conn *net.UnixConn, r Request) error { + var header []byte + // calculate message total size + size := uint32(len(r.data) + 8) + buf := make([]byte, 4) + order.PutUint32(buf, uint32(r.pid)) + header = append(header, buf...) + order.PutUint32(buf, uint32(size<<16|r.opcode&0x0000ffff)) + header = append(header, buf...) + + d, c, err := conn.WriteMsgUnix(append(header, r.data...), r.oob, nil) + if err != nil { + return err + } + if c != len(r.oob) || d != (len(header)+len(r.data)) { + return errors.New("WriteMsgUnix failed") + } + bytePool.Give(r.data) + + return nil +} diff --git a/src/github.com/dkolbly/wl/screenshooter.go b/src/github.com/dkolbly/wl/screenshooter.go new file mode 100644 index 00000000..99c1bbe7 --- /dev/null +++ b/src/github.com/dkolbly/wl/screenshooter.go @@ -0,0 +1,24 @@ +package wl + +const ( + _WESTON_SCREENSHOOTER_SHOOT = 0 +) + +type WestonScreenshooter struct { + BaseProxy + DoneChan chan WestonScreenshooterDoneEvent +} + +type WestonScreenshooterDoneEvent struct { +} + +func NewWestonScreenshooter(conn *Context) *WestonScreenshooter { + ret := new(WestonScreenshooter) + ret.DoneChan = make(chan WestonScreenshooterDoneEvent) + conn.Register(ret) + return ret +} + +func (p *WestonScreenshooter) Shoot(output *Output, buffer *Buffer) error { + return p.Context().SendRequest(p, _WESTON_SCREENSHOOTER_SHOOT, output, buffer) +} diff --git a/src/github.com/dkolbly/wl/text_cursor_position.go b/src/github.com/dkolbly/wl/text_cursor_position.go new file mode 100644 index 00000000..0634af6f --- /dev/null +++ b/src/github.com/dkolbly/wl/text_cursor_position.go @@ -0,0 +1,19 @@ +package wl + +const ( + _TEXT_CURSOR_POSITION_NOTIFY = 0 +) + +type TextCursorPosition struct { + BaseProxy +} + +func NewTextCursorPosition(conn *Context) *TextCursorPosition { + ret := new(TextCursorPosition) + conn.Register(ret) + return ret +} + +func (p *TextCursorPosition) Notify(surface *Surface, x float32, y float32) error { + return p.Context().SendRequest(p, _TEXT_CURSOR_POSITION_NOTIFY, surface, x, y) +} diff --git a/src/github.com/dkolbly/wl/ui/bgra.go b/src/github.com/dkolbly/wl/ui/bgra.go new file mode 100644 index 00000000..87cf0fe2 --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/bgra.go @@ -0,0 +1,180 @@ +package ui + +import ( + "github.com/golang/freetype/raster" + "image" + "image/color" + "image/draw" +) + +// BGRA is like RGBA but in wayland's byte order +type BGRA struct { + Pix []uint8 + Stride int + Rect image.Rectangle +} + +// NewBGRA returns a new BGRA image with the given bounds. +func NewBGRA(r image.Rectangle) *BGRA { + w, h := r.Dx(), r.Dy() + buf := make([]uint8, 4*w*h) + return &BGRA{buf, 4 * w, r} +} + +func NewBGRAWithData(r image.Rectangle, data []uint8) *BGRA { + w, h := r.Dx(), r.Dy() + if len(data) < 4*w*h { + panic("not enough data supplied") + } + return &BGRA{data, 4 * w, r} +} + +func (p *BGRA) ColorModel() color.Model { return color.RGBAModel } + +func (p *BGRA) Bounds() image.Rectangle { return p.Rect } + +func (p *BGRA) At(x, y int) color.Color { + return p.RGBAAt(x, y) +} + +func (p *BGRA) RGBAAt(x, y int) color.RGBA { + if !(image.Point{x, y}.In(p.Rect)) { + return color.RGBA{} + } + i := p.PixOffset(x, y) + return color.RGBA{p.Pix[i+2], p.Pix[i+1], p.Pix[i+0], p.Pix[i+3]} +} + +// PixOffset returns the index of the first element of Pix that corresponds to +// the pixel at (x, y). +func (p *BGRA) PixOffset(x, y int) int { + return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 +} + +func (p *BGRA) Set(x, y int, c color.Color) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + c1 := color.RGBAModel.Convert(c).(color.RGBA) + p.Pix[i+0] = c1.B + p.Pix[i+1] = c1.G + p.Pix[i+2] = c1.R + p.Pix[i+3] = c1.A +} + +func (p *BGRA) SetRGBA(x, y int, c color.RGBA) { + if !(image.Point{x, y}.In(p.Rect)) { + return + } + i := p.PixOffset(x, y) + p.Pix[i+0] = c.B + p.Pix[i+1] = c.G + p.Pix[i+2] = c.R + p.Pix[i+3] = c.A +} + +// SubImage returns an image representing the portion of the image p visible +// through r. The returned value shares pixels with the original image. +func (p *BGRA) SubImage(r image.Rectangle) image.Image { + r = r.Intersect(p.Rect) + // If r1 and r2 are Rectangles, r1.Intersect(r2) is not + // guaranteed to be inside either r1 or r2 if the intersection + // is empty. Without explicitly checking for this, the Pix[i:] + // expression below can panic. + if r.Empty() { + return &BGRA{} + } + i := p.PixOffset(r.Min.X, r.Min.Y) + return &BGRA{ + Pix: p.Pix[i:], + Stride: p.Stride, + Rect: r, + } +} + +// Opaque scans the entire image and reports whether it is fully opaque. +func (p *BGRA) Opaque() bool { + if p.Rect.Empty() { + return true + } + i0 := 0 + i1 := p.Rect.Dx() * 4 + for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ { + for i := i0; i < i1; i += 4 { + if p.Pix[i+3] != 0xff { + return false + } + } + i0 += p.Stride + i1 += p.Stride + } + return true +} + +// a BGRAPainter is used to interface with the freetype renderer and +// raster library +type BGRAPainter struct { + image *BGRA + r, g, b, a uint32 + op draw.Op +} + +func (p *BGRA) Painter() *BGRAPainter { + return &BGRAPainter{ + image: p, + } +} + +func (p *BGRAPainter) SetColor(c color.Color) { + p.r, p.g, p.b, p.a = c.RGBA() +} + +func (p *BGRAPainter) Paint(spans []raster.Span, done bool) { + b := p.image.Rect + pix := p.image.Pix + for _, s := range spans { + if s.Y < b.Min.Y { + continue + } + if s.Y >= b.Max.Y { + // since spans are ordered, we know we're done + // at this point + return + } + if s.X0 < b.Min.X { + s.X0 = b.Min.X + } + if s.X1 > b.Max.X { + s.X1 = b.Max.X + } + if s.X0 >= s.X1 { + continue + } + // This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go. + ma := s.Alpha + const m = 1<<16 - 1 + i0 := (s.Y-b.Min.Y)*p.image.Stride + (s.X0-b.Min.X)*4 + i1 := i0 + (s.X1-s.X0)*4 + if p.op == draw.Over { + for i := i0; i < i1; i += 4 { + dr := uint32(pix[i+0]) + dg := uint32(pix[i+1]) + db := uint32(pix[i+2]) + da := uint32(pix[i+3]) + a := (m - (p.a * ma / m)) * 0x101 + pix[i+0] = uint8((dr*a + p.r*ma) / m >> 8) + pix[i+1] = uint8((dg*a + p.g*ma) / m >> 8) + pix[i+2] = uint8((db*a + p.b*ma) / m >> 8) + pix[i+3] = uint8((da*a + p.a*ma) / m >> 8) + } + } else { + for i := i0; i < i1; i += 4 { + pix[i+0] = uint8(p.r * ma / m >> 8) + pix[i+1] = uint8(p.g * ma / m >> 8) + pix[i+2] = uint8(p.b * ma / m >> 8) + pix[i+3] = uint8(p.a * ma / m >> 8) + } + } + } +} diff --git a/src/github.com/dkolbly/wl/ui/configure.go b/src/github.com/dkolbly/wl/ui/configure.go new file mode 100644 index 00000000..e0d27f27 --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/configure.go @@ -0,0 +1,112 @@ +package ui + +import ( + "fmt" + //"github.com/dkolbly/wl" + "github.com/dkolbly/wl/xdg" +) + +type Config struct { + Width int + Height int + Active bool +} + +// see description of xdg_surface.configure +// +// Basically, we will receive a series of events on our role +// object (e.g., a xdg.Toplevel) which are accumulating latchable +// state. When the xdg.Surface get a configure event, we "latch" +// those changes, do whatever we need to do, and then respond with +// an AckConfigure request. + +// the compositor wants the surface to be closed, based on user action +func (w *Window) HandleToplevelClose(ev xdg.ToplevelCloseEvent) { + fmt.Printf("toplevel close request: %v\n", ev) +} + +func (w *Window) HandleToplevelConfigure(ev xdg.ToplevelConfigureEvent) { + fmt.Printf("toplevel configured: %v\n", ev) + + pend := Config{ + Width: int(ev.Width), + Height: int(ev.Height), + } + for _, state := range ev.States { + if state == xdg.ToplevelStateActivated { + pend.Active = true + } + } + w.pending = pend +} + +func (w *Window) HandleSurfaceConfigure(ev xdg.SurfaceConfigureEvent) { + fmt.Printf("surface configured; committing %#v\n", w.pending) + // and ack it + w.xdgSurface.AckConfigure(ev.Serial) + + // apply the changes + w.current = w.pending +} + +func (w *Window) setupXDGTopLevel() error { + + d := w.display + + /*fmt.Printf("creating xdg_wm_base\n") + wm := xdg.NewXdgWmBase(d.Context()) + fmt.Printf("==> %#v\n", wm) + */ + s, err := d.wmBase.GetXdgSurface(w.surface) + if err != nil { + fmt.Printf("failed to get surface: %s", err) + } else { + fmt.Printf("surface is: %p\n", s) + } + + w.xdgSurface = s + /*ping := wl.HandlerFunc(func(x interface{}) { + if ev, ok := x.(xdg.WmBasePingEvent); ok { + fmt.Printf("ping <%d>\n", ev.Serial) + d.wmBase.Pong(ev.Serial) + } else { + fmt.Printf("umm, what %#v\n", x) + } + }) + foo := wl.HandlerFunc(func(x interface{}) { + fmt.Printf("surface configured: %#v\n", x) + })*/ + + s.AddConfigureHandler(w) + + top, err := s.GetToplevel() + if err != nil { + panic(err) + } + fmt.Printf("top level is: %p\n", top) + + top.SetTitle("Hello!") + top.SetAppId("go.hello") + /*bar := wl.HandlerFunc(func(x interface{}) { + fmt.Printf("toplevel configured: %#v\n", x) + }) + barc := wl.HandlerFunc(func(x interface{}) { + fmt.Printf("toplevel closed: %#v\n", x) + })*/ + top.AddConfigureHandler(w) + top.AddCloseHandler(w) + + err = s.SetWindowGeometry(10, 10, 300, 300) + if err != nil { + panic(err) + } + // we need to commit the underlying wl_surface before + // doing much else (see description of xdg_surface) + err = w.surface.Commit() + if err != nil { + return fmt.Errorf("Surface.Commit failed: %s", err) + } + + fmt.Printf("ok...\n") + return nil +} diff --git a/src/github.com/dkolbly/wl/ui/display.go b/src/github.com/dkolbly/wl/ui/display.go new file mode 100644 index 00000000..5707d469 --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/display.go @@ -0,0 +1,360 @@ +package ui + +import ( + "fmt" + "log" + "sync" + "syscall" +) + +import ( + "github.com/dkolbly/wl" + "github.com/dkolbly/wl/xdg" +) + +type Display struct { + mu sync.RWMutex + display *wl.Display + registry *wl.Registry + compositor *wl.Compositor + subCompositor *wl.Subcompositor + shell *wl.Shell + shm *wl.Shm + seat *wl.Seat + dataDeviceManager *wl.DataDeviceManager + pointer *wl.Pointer + keyboard *wl.Keyboard + touch *wl.Touch + wmBase *xdg.WmBase + windows []*Window +} + +func Connect(addr string) (*Display, error) { + d := new(Display) + display, err := wl.Connect(addr) + if err != nil { + return nil, fmt.Errorf("Connect to Wayland server failed %s", err) + } + + d.display = display + display.AddErrorHandler(d) + + err = d.registerGlobals() + if err != nil { + return nil, err + } + + err = d.checkGlobalsRegistered() + if err != nil { + return nil, err + } + + err = d.registerInputs() + if err != nil { + return nil, err + } + + err = d.checkInputsRegistered() + if err != nil { + return nil, err + } + return d, nil +} + +func (d *Display) Disconnect() { + d.keyboard.Release() + d.pointer.Release() + if d.touch != nil { + d.touch.Release() + } + + d.seat.Release() + d.display.Context().Close() +} + +func (d *Display) Dispatch() chan<- struct{} { + return d.Context().Dispatch() +} + +func (d *Display) Context() *wl.Context { + return d.display.Context() +} + +type registrar struct { + ch chan wl.RegistryGlobalEvent +} + +func (r registrar) HandleRegistryGlobal(ev wl.RegistryGlobalEvent) { + r.ch <- ev +} + +type doner struct { + ch chan wl.CallbackDoneEvent +} + +func (d doner) HandleCallbackDone(ev wl.CallbackDoneEvent) { + d.ch <- ev +} + +func (d *Display) registerGlobals() error { + registry, err := d.display.GetRegistry() + if err != nil { + return fmt.Errorf("Display.GetRegistry failed : %s", err) + } + d.registry = registry + + callback, err := d.display.Sync() + if err != nil { + return fmt.Errorf("Display.Sync failed %s", err) + } + + rgeChan := make(chan wl.RegistryGlobalEvent) + rgeHandler := registrar{rgeChan} + registry.AddGlobalHandler(rgeHandler) + + cdeChan := make(chan wl.CallbackDoneEvent) + cdeHandler := doner{cdeChan} + + callback.AddDoneHandler(cdeHandler) +loop: + for { + select { + case ev := <-rgeChan: + if err := d.registerInterface(registry, ev); err != nil { + return err + } + case d.Dispatch() <- struct{}{}: + case <-cdeChan: + break loop + } + } + + registry.RemoveGlobalHandler(rgeHandler) + callback.RemoveDoneHandler(cdeHandler) + return nil +} + +type seatcap struct { + ch chan wl.SeatCapabilitiesEvent +} + +func (sce seatcap) HandleSeatCapabilities(ev wl.SeatCapabilitiesEvent) { + sce.ch <- ev +} + +func (d *Display) registerInputs() error { + callback, err := d.display.Sync() + if err != nil { + return fmt.Errorf("Display.Sync failed %s", err) + } + + cdeChan := make(chan wl.CallbackDoneEvent) + cdeHandler := doner{cdeChan} + callback.AddDoneHandler(cdeHandler) + + sceChan := make(chan wl.SeatCapabilitiesEvent) + sceHandler := seatcap{sceChan} + d.seat.AddCapabilitiesHandler(sceHandler) + +loop: + for { + select { + case ev := <-sceChan: + if (ev.Capabilities & wl.SeatCapabilityPointer) != 0 { + pointer, err := d.seat.GetPointer() + if err != nil { + return fmt.Errorf("Unable to get Pointer object: %s", err) + } + d.pointer = pointer + } + if (ev.Capabilities & wl.SeatCapabilityKeyboard) != 0 { + keyboard, err := d.seat.GetKeyboard() + if err != nil { + return fmt.Errorf("Unable to get Keyboard object: %s", err) + } + d.keyboard = keyboard + } + if (ev.Capabilities & wl.SeatCapabilityTouch) != 0 { + touch, err := d.seat.GetTouch() + if err != nil { + return fmt.Errorf("Unable to get Touch object: %s", err) + } + d.touch = touch + } + case d.Dispatch() <- struct{}{}: + case <-cdeChan: + break loop + } + } + + d.seat.RemoveCapabilitiesHandler(sceHandler) + callback.RemoveDoneHandler(cdeHandler) + + return nil +} + +func (d *Display) registerInterface(registry *wl.Registry, ev wl.RegistryGlobalEvent) error { + fmt.Printf("we discovered an interface: %q\n", ev.Interface) + switch ev.Interface { + case "wl_shm": + ret := wl.NewShm(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind Shm interface: %s", err) + } + d.shm = ret + case "wl_compositor": + ret := wl.NewCompositor(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind Compositor interface: %s", err) + } + d.compositor = ret + case "wl_shell": + ret := wl.NewShell(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind Shell interface: %s", err) + } + d.shell = ret + case "wl_seat": + ret := wl.NewSeat(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind Seat interface: %s", err) + } + d.seat = ret + case "wl_data_device_manager": + ret := wl.NewDataDeviceManager(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind DataDeviceManager interface: %s", err) + } + d.dataDeviceManager = ret + case "wl_subcompositor": + ret := wl.NewSubcompositor(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind Subcompositor interface: %s", err) + } + d.subCompositor = ret + case "zxdg_shell_v6": + ret := xdg.NewWmBase(d.Context()) + err := registry.Bind(ev.Name, ev.Interface, ev.Version, ret) + if err != nil { + return fmt.Errorf("Unable to bind Subcompositor interface: %s", err) + } + d.wmBase = ret + d.wmBase.AddPingHandler(d) + } + return nil +} + +func (d *Display) HandleDisplayError(ev wl.DisplayErrorEvent) { + log.Fatalf("Display Error Event: %d - %s - %d", ev.ObjectId.Id(), ev.Message, ev.Code) +} + +func (d *Display) newBuffer(width, height, stride int32) (*wl.Buffer, []byte, error) { + size := stride * height + + file, err := TempFile(int64(size)) + if err != nil { + return nil, nil, fmt.Errorf("TempFile failed: %s", err) + } + defer file.Close() + + data, err := syscall.Mmap(int(file.Fd()), 0, int(size), syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return nil, nil, fmt.Errorf("syscall.Mmap failed: %s", err) + } + + pool, err := d.shm.CreatePool(file.Fd(), size) + if err != nil { + return nil, nil, fmt.Errorf("Shm.CreatePool failed: %s", err) + } + defer pool.Destroy() + + buf, err := pool.CreateBuffer(0, width, height, stride, wl.ShmFormatArgb8888) + if err != nil { + return nil, nil, fmt.Errorf("Pool.CreateBuffer failed : %s", err) + } + + return buf, data, nil +} + +func (d *Display) registerWindow(w *Window) { + d.mu.Lock() + defer d.mu.Unlock() + + d.windows = append(d.windows, w) +} + +func (d *Display) unregisterWindow(w *Window) { + d.mu.Lock() + defer d.mu.Unlock() + + for i, _w := range d.windows { + if _w == w { + d.windows = append(d.windows[:i], d.windows[i+1:]...) + break + } + } +} + +// TODO +func (d *Display) FindWindow() *Window { + return nil +} + +func (d *Display) checkGlobalsRegistered() error { + if d.seat == nil { + return fmt.Errorf("Seat is not registered") + } + + if d.compositor == nil { + return fmt.Errorf("Compositor is not registered") + } + + if d.shm == nil { + return fmt.Errorf("Shm is not registered") + } + + if d.shell == nil { + return fmt.Errorf("Shell is not registered") + } + + if d.dataDeviceManager == nil { + return fmt.Errorf("DataDeviceManager is not registered") + } + + return nil +} + +func (d *Display) Keyboard() *wl.Keyboard { + return d.keyboard +} + +func (d *Display) Pointer() *wl.Pointer { + return d.pointer +} + +func (d *Display) Touch() *wl.Touch { + return d.touch +} + +func (d *Display) checkInputsRegistered() error { + if d.keyboard == nil { + return fmt.Errorf("Keyboard is not registered") + } + + if d.pointer == nil { + return fmt.Errorf("Pointer is not registered") + } + + return nil +} + +func (d *Display) HandleWmBasePing(ev xdg.WmBasePingEvent) { + fmt.Printf("ping <%d>\n", ev.Serial) + d.wmBase.Pong(ev.Serial) +} diff --git a/src/github.com/dkolbly/wl/ui/examples/img/bsd_daemon.jpg b/src/github.com/dkolbly/wl/ui/examples/img/bsd_daemon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..55bc02c4edc066b4d31ea8496d62a993a36f0d1c GIT binary patch literal 26609 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⁣h}htL7;~;e^hmlC3?nz)54*tb(jZ0=HoA+ax?G^iDNLG?N~Vi6geuf|*uykB1nW*nZx z^#lRa%?+}5k`=Ae%gqGim%Gh`Ci1E#vy)s@Otr`TsuVspP;{&=_D)pDhZGM`=y zws2t3zJ1YF$(#cyc&t$&9{*PRRrq2~(w`f8kg z6cE<^K*>_>E6!aTzZAG(;3rITb2ZT{I8JJO*OD-Ei5QqU4B6?8_(p3VX#|OLN4QGN zSeF8+k+*Cg%M644FRRxg65O)&ZCNi`CJS3Wa0g6J`sO%DDpj`0dNOOZncmS5fzN#v zV^s-=TDbPZ4 z>olzS8YLx%Q9j4S-JIaZ9-#F)1190`KZWM ztxHycy8q9NzTm@&D~^dEeVtPC>-#9j&mVz5s(wHYeFO|~A9Bm}L&lp~H{3_fe%}7S zZ0`TRQUGHn3L5siQogJ#T6KeGHR#)k4GSkY*V#{tTz%V`iX#k9&pUG}QcM|{(btcN z+uiayOz*PMFK}l>WnXYaxY1M7#KBA0*k>KWJ6De?2uOkqm#67s+}#%C&hr4scHu&H zxy=L@h_aErnxD1vN83}C|a_FB=?PROE;IIl(EG% zD2=|0Lzhc4mhJ(l+Lx*3VN)8KRar}~a}~+d>UUB;vBj4$lmveeM~3^T)i&^Xnrm97 z_wn;}QXF|8mFj%XmxxYfKP*=M>D9^2nO!Zo$(` z&*gUT2PE5KFG1kt_NaFIP3lV1IQk^jA`Njls<|==@!OJt3 z-|z`NCsZ?k1}$y+sy7eM1$^*7nwux+2W}Z?#4`ed?9yBpagfrc9In|c4~GCyuTpB>#xu!?JW4-yYS92=uo3-pts2* z43O%9)SCbL%EdXRrA?2;c&Bkbck|;i;X=x9Ol_Z}0#$bdqUAmgt}?&i#jZba2Qhnz zTsb0CJd2quPSlG}^fe7u(@Ik(YPCW-^xm&+V0j=F`S1saKO|5oYOUi-5GtX-Du%?_x)JI zfK)zh-+E%}&G?4qVxj~tylSBDV$*Y}39qczyuNWpIcts=d*ZQ+c?<7Nl3U-P#AtH( z%|K?cdD}!oF{6qVG}Qn;=lH#CD)XsI>3n&&d;EKf8p%E8>6{x$6Cpm&$xa))r5rMq z+8=EgXQ4vaJK+4&s1R4sHNrWE=blPGgzzN)x7+>T^o3b5=TVL>W1#DN;=aOZ?n(A7 z+s+2I1*6~MFKAiqho{)uraV9rNXdPM(wIw8o%CpU<&nPGaA*ONt`%K>cC|spp*k38 zWo5O#b#Wxe(gF-#KiEP6cva(K4z?jPk??RK4%su>}`r6w;yrfJ%Am68)(4s!oma zis@Bk%~F4wLk(qkQ_Ifb{WGnbZ+mnvJ7B)qBK*cX8h2)o(uZNhh4;VK2uD zUz?w@_q|-n+j@5sy=<(%`CnZLu(X*}o@yRq#%vI3R~Mv%08!bJDecm#d6jIFcE@0M zK@NlwldwA^F5@zO{q!H`TX@GlY~l9|ockK)G0pU)nIGb+!-?nDOUY@4INCK=OVJM% zr?^9=cD_BfXj~5KNl5OjbyETzXJKqL8UQF+5qYu@W5;aWQq1t|o(MFT3i-LT7YY>- z==(vt_-MFR-tMQb2HxjX!RWsB|Sp(sBju^oTIZe}$-@4SF%VI-!Q6sx&k_pUl%caRMCzBmeBwFe*Gl6nAj3 zX@~a<*}*G)1EBd^*11ajp?cZb<}UTLmhe>px|QhDsng0dut*CAXbP5uL_qobY(->m zR0%(#XkUrCqmwAZ_D-ugw_GxgU2Q$Q;f%gMiur@1$&-fz#858e0%5cuT#$5Iqcla!O+##V1_b=ljSI%U27yRsN(#TWcW zxEnkBgDWJ&E2R9meY}#_=p?Tl(aQ0t2&9ej*s2b8k6Ne)#C&5{yj?xN**Na_i|;-P z-_sN-fjwg+QhC%Uua@92RdY{FHfrLJ1u2K_&xP{7lf_a$8#+Q4zJ++`Y+yj;@4N1= zO;wamTvYD}X^RgbK)+*TIlQ)u$hJ zm6v2L3#-(0J5nUcqkZso!^sB~<4|b$7;bXG&)vSb^=A|v&jw0zPm3Sg42AO)4$sg3 z;(Gy-on3#86;3luzTp$1fU z;%b0E6Elj-?gCYTzmEJsqGdx~FcXgV1ri0!)BX|URP zak9JKojV~9WS%5$M}=N<{nX^mTH(D#kChDCn9)@5#xZIj0dV5e(7Ag-dgHAQnO4uB zl;;aWgw)xi+&E9! zFAmUGrR5harqMTxQIb?-x6NQMRjuUL z!`7q#Q&w)Pi?fqc6K8A8Vbm{t7dw>sH|7eefSfPPP$P}-m_eexC^@?@F?~NA6HEj7WcwIhs_=Ia?V@ z{3&)0N^;3B)Nh*nnZ&CBlGbfRQm!v~j~&sV7S}_$7YCO9S!8>U4(|xi;Qd8*x%R+t zb#*uB%p^2Vqp!+b5HfF0S;d5Hfppi~?$t1{T+sp{6v>pq{ZO`wJ=>5lA^`8?9~ZMr zI-?iI7@(f~r$G=(&baGtat`lP=~kj61zSlu7IWvF)Z?P4;B@O!n6OPFunmxB?+-rL z(WKZwJ?T9bL>=08$uP^(Hts8r&Tz_^E=(#yAIDWweU z1gh=sf86GIhY*rDF2x*O_C10a7+&L`ZcEfC`ko!t{4pgu!^%TBgl?0IQs{02wFdgz z%OR`x*6U7pi3$BkfukjX! zJ6o-sbIxp0wYivM#uhxh$}wQ8gdu+~uJX$O*Y|E3OMVQZxy*lEOQCL*QS;x-4Hh40@n z@Nq^E29liZlc<`d<(uC!@P;?RXXqHkG_O&ke8bWa2iw6XKc(j2eayxo;@Z7(bXqMGTZqV{b>vDPfQLb;9| z^ZPHp0*X7@hKCv*^QlBDvsZcKGu@)8fpWHE;^JicwWn|djn{7%d8cI*4SfT7OV&Ji z+&mlH!``|+ZzHAb2VIBwKlX30IAMFkl!~?axNM*~oHBLAAaNrc=RjbB1FS_`GM2Uz8ifVeK8ssX^l&*#;x?3#VDW}RL6vD- zWaaE;5Qm+L>a&++I?s%VxCs8!?LZn^sV&~BnOtyaEA438oM5$t9{*>seh|-}_24M_ zx8UMU)ohEKx`V0D)Ks?0xH7FlM%&y!EO=`z9%`yc zPYeP*og1C(%gWKp%cWA6)Gt|y!D*Oxr{cxorbhUKWG0e_H}c+fhBt!29Da5olVUYf z#lokM7)`%`|Z)YZg^ zSC4uK`2h9Rp|^X(jKyc>xm|>&>5bOIAdd8Crxs@DAgL**ZT1FI7OgS8i}*KCNOYcM zXL-I@%RkM|T6`q8wJYah(em=LD{~PB5KY|HT&vfHr>*v z#p$b!?E6=SgjL5f+!WYKp__7t&gc+@CdrM}a?}mlUN`tSM~>0VgUf%@oe$@p-B$S8 z#Te)YiyGM$trPy>+^WKo#jDp28n9<;5oM!|el-HhE%DNx6ZD^SELLsk{h*v-i|d@K zW3J{>sPcjlOvB+WGM!?sWvMM1zl^ZoR}#LVGq6sF+vcCt$ybp3eY06P)emyR%}K4; zCNVFaTAf07p_z4ANuf2FiT6%1d*bNZ&%vVf4-=YNo#)Y^jF?#TG1`s84%r@EN6s}> zf@irE?NCd{$=cm|)N5*Yywyot&P}cZ&d?WN-gzn<9SA$$zh&oVY6DSC^+q$4PD81~Qh*kB&--?*yU(3_K=`z+N;L z8P{`h+BNvHotMd;I(=pHbr*d2A$$12Ktgc_()UZX(6Jx79jEHjc|6?AB`#L!Ti~4p zQogE9d3^T~Ox(7bZuo4y3jIEb`)ETKnUbA164jU|501fH7~`0Pm(RPv_nQu$?jVrm ziE~}yOFvWMG&3AtK745|R7L=iP{KVy*B+l?wivk#q(2J3y+gqaK>~LgW3yQ z$H;q;#QNX%uG!sBHMeF374}!BWt(43v5eCa-r@-PK=mA7S9_^H&@kKR7lFE+^V}*u z(O?}9G3`@MPg$QMx;G(7U|IMSjMpw1aR?oH`u3JA1MWP%(Z@_B>Q}yBeA}ASBQNeZukr4ujjwZ!|k(y92`A z(}(q0(WBbG_5KuXxmE7FihY zboFrF6jhVCeS`0sdlVIgj9;JnR*bVNS%5)jN-<+N_sDA*p2?(E7;3OUSY{zxl-LTm zUeP+y#$6-4+C8~Z!KkN+rzBt$(Fpb{YAE|@IQ4A2D#&#X(HTxy2SC(T-<^mLRZ3{xMa?W9N=STL( zH^RNbY)u7@2w}0ABhEA8hz`Pwp)&TVFy84FMRG}{nvstHx0|PF4Gq&5lJ?w>qkfQZ zU*SL4#nk2c${8njh+xffK?Se<=!%<@TbE+XOYz25PQa=H!xX?aP~r+CF%7|D+26~XQ1?+L&1@r;nL={dH!O=Pff_eQHx$TluCluV7gvpLd1&WUSzI@Cz8~_PaHVeT-zQmvh7;dqHxMEXjYfhw|8Vp_gmiW9)=# zloBm;W0gvXRuJh_bVi--c1Fec(>x@;UR18EFufL-otVmWkLKM2VeI#U6xalYCk-5jkwC*3d$~>Lg}cGW z{H!0z>x~C}$jJ%+Fe2pUYNVg>j5kC0+RX>J6_d%DM!EBAx5pq__x>_-C+~2OB8%@2}~`fMsTJx}er)`SZ5 zJjr#Na4~nSUy9GXZajYAdz466q~T@R_ZG|;R-2=Yt^Hm@S1aU0>?82=PT3%)tlpb* z2q;ah3+j;VX5%pnn zOa)6i&{Ak%QBc%OlZ$CBoQf408>(Yk44ibftDxbWDd}D+G5Df@G+#~4N%|?Mh+5!5 zJX_}Q7W(W_DMt{_R9jRa1NhNS43lp)kBx3F)&z_=H8pgmfH0X=b3CE1|L46N+wQ5* zS2u%CKlv_*$%!U=yi4{yu#W3WOuek7b-|KBt`Nec)FxRm z?fYb;@2X|q?VhJ?@T$_Fap!9@eu0C>NA6+TKcCD%yRzjNNc#4RV1;97f3_1Z3#KhR z-}8g!$eHmTEP_@kWRpyI{=#mVSB;ZDc!i6`y}X}38?ox-2Hn&uu>ZdAu2>8ta^Ay@ zIY%&Cp7fuV!jUQErDSC0Cea-Qpic9eMs`?#^gH)5B&}al>!3&zpd_=TXX~?T{`9nWY9$(u;c=gZX6;{Kj)3vFTUAZciic`HLw`H<^?|ldcIRt%{psu2W)P?QaBV!DPNDRd zs_d7w*A&udEjOax!FXU%tFY39jL z=UBT8iBjFh_NmXSlxs(+YEy{6U@EwfpmEHN@2|4j6Hj>U^mkfa*i=vSaPv_d)H1&h+`9$7*M9BL z7&Gz8te$tgH8c~4?pFy@ct#FB?VnY%jh<6{SS!3Q#OHeTem~~ezkm4|Y$<>-pp=&P z&fsU{P$x9}~QVg?uxjr)il9Mlhzs{Icjsksq=Q6-X4T zd}-3S5QUcWDt7jh&k{_OiVXOz!h4>0{=Bz__C(;FePySzhK0^6m$UAW_M6b(A_-4p1*s3F9*D>Io% zbXJC9GwP!5tGdyMU0osMu9X~62V0&#oQx7>|y+)u4vx{`iX?`gUle^^U zlA?^hOLp;ZsML#(zFj8RNqJ}0bHcL2{U=(W}p7YLiT?XxeC8#{vXPff%E_X literal 0 HcmV?d00001 diff --git a/src/github.com/dkolbly/wl/ui/examples/img/image.go b/src/github.com/dkolbly/wl/ui/examples/img/image.go new file mode 100644 index 00000000..decd2897 --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/examples/img/image.go @@ -0,0 +1,38 @@ +package main + +import ( + "bufio" + "image" + "image/draw" + "os" +) + +import ( + _ "golang.org/x/image/bmp" + _ "golang.org/x/image/tiff" + _ "golang.org/x/image/webp" + _ "image/gif" + _ "image/jpeg" + _ "image/png" +) + +func ImageFromFile(filePath string) (image.Image, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer f.Close() + br := bufio.NewReader(f) + img, _, err := image.Decode(br) + if err != nil { + return nil, err + } + return img, nil +} + +func RGBAFromImage(img image.Image) (*image.RGBA, error) { + b := img.Bounds() + m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) + draw.Draw(m, m.Bounds(), img, b.Min, draw.Src) + return m, nil +} diff --git a/src/github.com/dkolbly/wl/ui/examples/img/img.go b/src/github.com/dkolbly/wl/ui/examples/img/img.go new file mode 100644 index 00000000..42b76a3c --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/examples/img/img.go @@ -0,0 +1,84 @@ +package main + +import ( + "flag" + "log" + "net/http" + _ "net/http/pprof" + "os" + "runtime/debug" +) + +import ( + "github.com/dkolbly/wl" + "github.com/dkolbly/wl/ui" +) + +func init() { + flag.Parse() + log.SetFlags(0) +} + +func main() { + defer func() { + if r := recover(); r != nil { + debug.PrintStack() + } + }() + + go func() { + log.Println(http.ListenAndServe("localhost:6060", nil)) + }() + + exitChan := make(chan bool, 10) + + if flag.NArg() == 0 { + log.Fatalf("usage: %s imagefile", os.Args[0]) + } + + img, err := ImageFromFile(flag.Arg(0)) + if err != nil { + log.Fatal(err) + } + + display, err := ui.Connect("") + if err != nil { + log.Fatal(err) + } + + b := img.Bounds() + w := int32(b.Dx()) + h := int32(b.Dy()) + + window, err := display.NewWindow(w, h) + if err != nil { + log.Fatal(err) + } + + display.Keyboard().AddKeyHandler(quitter{exitChan}) + + window.Draw(img) + +loop: + for { + select { + case <-exitChan: + break loop + case display.Dispatch() <- struct{}{}: + } + } + + log.Print("Loop finished") + window.Dispose() + display.Disconnect() +} + +type quitter struct { + ch chan bool +} + +func (q quitter) HandleKeyboardKey(ev wl.KeyboardKeyEvent) { + if ev.Key == 16 { + q.ch <- true + } +} diff --git a/src/github.com/dkolbly/wl/ui/utils.go b/src/github.com/dkolbly/wl/ui/utils.go new file mode 100644 index 00000000..ff74cdea --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/utils.go @@ -0,0 +1,27 @@ +package ui + +import ( + "errors" + "io/ioutil" + "os" +) + +func TempFile(size int64) (*os.File, error) { + dir := os.Getenv("XDG_RUNTIME_DIR") + if dir == "" { + return nil, errors.New("XDG_RUNTIME_DIR is not defined in env") + } + file, err := ioutil.TempFile(dir, "go-wayland-shared") + if err != nil { + return nil, err + } + err = file.Truncate(size) + if err != nil { + return nil, err + } + err = os.Remove(file.Name()) + if err != nil { + return nil, err + } + return file, nil +} diff --git a/src/github.com/dkolbly/wl/ui/window.go b/src/github.com/dkolbly/wl/ui/window.go new file mode 100644 index 00000000..d03233cc --- /dev/null +++ b/src/github.com/dkolbly/wl/ui/window.go @@ -0,0 +1,112 @@ +package ui + +import ( + "fmt" + "image" + "image/draw" + //"log" + "syscall" + + "github.com/dkolbly/wl" + "github.com/dkolbly/wl/xdg" +) + +type Window struct { + display *Display + surface *wl.Surface + shSurface *wl.ShellSurface + xdgSurface *xdg.Surface + buffer *wl.Buffer + data []byte + image *BGRA + title string + pending Config + current Config +} + +func (d *Display) NewWindow(width, height int32) (*Window, error) { + var err error + stride := width * 4 + + w := new(Window) + pend := Config{ + Width: int(width), + Height: int(height), + } + + w.pending = pend + w.current = pend + + w.display = d + + w.surface, err = d.compositor.CreateSurface() + if err != nil { + return nil, fmt.Errorf("Surface creation failed: %s", err) + } + + w.buffer, w.data, err = d.newBuffer(width, height, stride) + if err != nil { + return nil, err + } + + if d.wmBase != nil { + // New XDG shell + w.setupXDGTopLevel() + } else { + // older plain-jane wl_shell + w.shSurface, err = d.shell.GetShellSurface(w.surface) + if err != nil { + return nil, fmt.Errorf("Shell.GetShellSurface failed: %s", err) + } + + w.shSurface.AddPingHandler(w) + w.shSurface.SetToplevel() + } + + err = w.surface.Attach(w.buffer, width, height) + if err != nil { + return nil, fmt.Errorf("Surface.Attach failed: %s", err) + } + + err = w.surface.Damage(0, 0, width, height) + if err != nil { + return nil, fmt.Errorf("Surface.Damage failed: %s", err) + } + + if true { + err = w.surface.Commit() + if err != nil { + return nil, fmt.Errorf("Surface.Commit failed: %s", err) + } + + w.image = NewBGRAWithData( + image.Rect(0, 0, int(width), int(height)), + w.data) + + d.registerWindow(w) + } + + return w, nil +} + +func (w *Window) DrawUsingFunc(fn func(*BGRA)) { + fn(w.image) +} + +func (w *Window) Draw(img image.Image) { + draw.Draw(w.image, img.Bounds(), img, img.Bounds().Min, draw.Src) +} + +func (w *Window) Dispose() { + if w.shSurface != nil { + w.shSurface.RemovePingHandler(w) + } + w.surface.Destroy() + w.buffer.Destroy() + syscall.Munmap(w.data) + w.display.unregisterWindow(w) +} + +func (w *Window) HandleShellSurfacePing(ev wl.ShellSurfacePingEvent) { + w.shSurface.Pong(ev.Serial) +} diff --git a/src/github.com/dkolbly/wl/utils.go b/src/github.com/dkolbly/wl/utils.go new file mode 100644 index 00000000..60e3fbd5 --- /dev/null +++ b/src/github.com/dkolbly/wl/utils.go @@ -0,0 +1,59 @@ +package wl + +import ( + "encoding/binary" + "sync" + "unsafe" +) + +type BytePool struct { + sync.Pool +} + +var ( + order binary.ByteOrder + bytePool = &BytePool{ + sync.Pool{ + New: func() interface{} { + return make([]byte, 16) + }, + }, + } +) + +func (bp *BytePool) Take(n int) []byte { + buf := bp.Get().([]byte) + if cap(buf) < n { + t := make([]byte, len(buf), n) + copy(t, buf) + buf = t + } + return buf[:n] +} + +func (bp *BytePool) Give(b []byte) { + bp.Put(b) +} + +func init() { + var x uint32 = 0x01020304 + if *(*byte)(unsafe.Pointer(&x)) == 0x01 { + order = binary.BigEndian + } else { + order = binary.LittleEndian + } +} + +// from https://golang.org/src/math/unsafe.go +func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) } +func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) } + +func fixedToFloat64(fixed int32) float64 { + dat := ((int64(1023 + 44)) << 52) + (1 << 51) + int64(fixed) + return Float64frombits(uint64(dat)) - float64(3<<43) +} + +func float64ToFixed(v float64) int32 { + dat := v + float64(int64(3)<<(51-8)) + return int32(Float64bits(dat)) +} diff --git a/src/github.com/dkolbly/wl/utils_test.go b/src/github.com/dkolbly/wl/utils_test.go new file mode 100644 index 00000000..63c20129 --- /dev/null +++ b/src/github.com/dkolbly/wl/utils_test.go @@ -0,0 +1,35 @@ +package wl + +import ( + "math" + "testing" +) + +func TestFixedToFloat64(t *testing.T) { + var f int32 + var d float64 + + f = 0x012030 + d = fixedToFloat64(f) + if d != 288.1875 { + t.Fail() + } + + f = -0x012030 + d = fixedToFloat64(f) + if d != -288.1875 { + t.Fail() + } +} + +func TestReverse(t *testing.T) { + var d float64 + var f int32 + + d = 3.1415 + f = float64ToFixed(d) + dd := d - fixedToFloat64(f) + if math.Abs(dd) > 0.001 { + t.Fail() + } +} diff --git a/src/github.com/dkolbly/wl/xdg-unstable-v6/shell.go b/src/github.com/dkolbly/wl/xdg-unstable-v6/shell.go new file mode 100644 index 00000000..d0294399 --- /dev/null +++ b/src/github.com/dkolbly/wl/xdg-unstable-v6/shell.go @@ -0,0 +1,1060 @@ +// package zxdg acts as a client for the xdg_shell_unstable_v6 wayland protocol. + +// generated by wl-scanner +// https://github.com/dkolbly/wl-scanner +// from: https://raw.githubusercontent.com/wayland-project/wayland-protocols/master/unstable/xdg-shell/xdg-shell-unstable-v6.xml +// on 2018-02-19 14:50:40 -0600 +package zxdg + +import ( + "context" + "sync" + + "github.com/dkolbly/wl" +) + +type ShellPingEvent struct { + EventContext context.Context + Serial uint32 +} + +type ShellPingHandler interface { + HandleShellPing(ShellPingEvent) +} + +func (p *Shell) AddPingHandler(h ShellPingHandler) { + if h != nil { + p.mu.Lock() + p.pingHandlers = append(p.pingHandlers, h) + p.mu.Unlock() + } +} + +func (p *Shell) RemovePingHandler(h ShellPingHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.pingHandlers { + if e == h { + p.pingHandlers = append(p.pingHandlers[:i], p.pingHandlers[i+1:]...) + break + } + } +} + +func (p *Shell) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.pingHandlers) > 0 { + ev := ShellPingEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + p.mu.RLock() + for _, h := range p.pingHandlers { + h.HandleShellPing(ev) + } + p.mu.RUnlock() + } + } +} + +type Shell struct { + wl.BaseProxy + mu sync.RWMutex + pingHandlers []ShellPingHandler +} + +func NewShell(ctx *wl.Context) *Shell { + ret := new(Shell) + ctx.Register(ret) + return ret +} + +// Destroy will destroy xdg_shell. +// +// +// Destroy this xdg_shell object. +// +// Destroying a bound xdg_shell object while there are surfaces +// still alive created by this xdg_shell object instance is illegal +// and will result in a protocol error. +// +func (p *Shell) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// CreatePositioner will create a positioner object. +// +// +// Create a positioner object. A positioner object is used to position +// surfaces relative to some parent surface. See the interface description +// and xdg_surface.get_popup for details. +// +func (p *Shell) CreatePositioner() (*Positioner, error) { + ret := NewPositioner(p.Context()) + return ret, p.Context().SendRequest(p, 1, wl.Proxy(ret)) +} + +// GetXdgSurface will create a shell surface from a surface. +// +// +// This creates an xdg_surface for the given surface. While xdg_surface +// itself is not a role, the corresponding surface may only be assigned +// a role extending xdg_surface, such as xdg_toplevel or xdg_popup. +// +// This creates an xdg_surface for the given surface. An xdg_surface is +// used as basis to define a role to a given surface, such as xdg_toplevel +// or xdg_popup. It also manages functionality shared between xdg_surface +// based surface roles. +// +// See the documentation of xdg_surface for more details about what an +// xdg_surface is and how it is used. +// +func (p *Shell) GetXdgSurface(surface *wl.Surface) (*Surface, error) { + ret := NewSurface(p.Context()) + return ret, p.Context().SendRequest(p, 2, wl.Proxy(ret), surface) +} + +// Pong will respond to a ping event. +// +// +// A client must respond to a ping event with a pong request or +// the client may be deemed unresponsive. See xdg_shell.ping. +// +func (p *Shell) Pong(serial uint32) error { + return p.Context().SendRequest(p, 3, serial) +} + +const ( + ShellErrorRole = 0 + ShellErrorDefunctSurfaces = 1 + ShellErrorNotTheTopmostPopup = 2 + ShellErrorInvalidPopupParent = 3 + ShellErrorInvalidSurfaceState = 4 + ShellErrorInvalidPositioner = 5 +) + +type Positioner struct { + wl.BaseProxy +} + +func NewPositioner(ctx *wl.Context) *Positioner { + ret := new(Positioner) + ctx.Register(ret) + return ret +} + +// Destroy will destroy the xdg_positioner object. +// +// +// Notify the compositor that the xdg_positioner will no longer be used. +// +func (p *Positioner) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// SetSize will set the size of the to-be positioned rectangle. +// +// +// Set the size of the surface that is to be positioned with the positioner +// object. The size is in surface-local coordinates and corresponds to the +// window geometry. See xdg_surface.set_window_geometry. +// +// If a zero or negative size is set the invalid_input error is raised. +// +func (p *Positioner) SetSize(width int32, height int32) error { + return p.Context().SendRequest(p, 1, width, height) +} + +// SetAnchorRect will set the anchor rectangle within the parent surface. +// +// +// Specify the anchor rectangle within the parent surface that the child +// surface will be placed relative to. The rectangle is relative to the +// window geometry as defined by xdg_surface.set_window_geometry of the +// parent surface. The rectangle must be at least 1x1 large. +// +// When the xdg_positioner object is used to position a child surface, the +// anchor rectangle may not extend outside the window geometry of the +// positioned child's parent surface. +// +// If a zero or negative size is set the invalid_input error is raised. +// +func (p *Positioner) SetAnchorRect(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 2, x, y, width, height) +} + +// SetAnchor will set anchor rectangle anchor edges. +// +// +// Defines a set of edges for the anchor rectangle. These are used to +// derive an anchor point that the child surface will be positioned +// relative to. If two orthogonal edges are specified (e.g. 'top' and +// 'left'), then the anchor point will be the intersection of the edges +// (e.g. the top left position of the rectangle); otherwise, the derived +// anchor point will be centered on the specified edge, or in the center of +// the anchor rectangle if no edge is specified. +// +// If two parallel anchor edges are specified (e.g. 'left' and 'right'), +// the invalid_input error is raised. +// +func (p *Positioner) SetAnchor(anchor uint32) error { + return p.Context().SendRequest(p, 3, anchor) +} + +// SetGravity will set child surface gravity. +// +// +// Defines in what direction a surface should be positioned, relative to +// the anchor point of the parent surface. If two orthogonal gravities are +// specified (e.g. 'bottom' and 'right'), then the child surface will be +// placed in the specified direction; otherwise, the child surface will be +// centered over the anchor point on any axis that had no gravity +// specified. +// +// If two parallel gravities are specified (e.g. 'left' and 'right'), the +// invalid_input error is raised. +// +func (p *Positioner) SetGravity(gravity uint32) error { + return p.Context().SendRequest(p, 4, gravity) +} + +// SetConstraintAdjustment will set the adjustment to be done when constrained. +// +// +// Specify how the window should be positioned if the originally intended +// position caused the surface to be constrained, meaning at least +// partially outside positioning boundaries set by the compositor. The +// adjustment is set by constructing a bitmask describing the adjustment to +// be made when the surface is constrained on that axis. +// +// If no bit for one axis is set, the compositor will assume that the child +// surface should not change its position on that axis when constrained. +// +// If more than one bit for one axis is set, the order of how adjustments +// are applied is specified in the corresponding adjustment descriptions. +// +// The default adjustment is none. +// +func (p *Positioner) SetConstraintAdjustment(constraint_adjustment uint32) error { + return p.Context().SendRequest(p, 5, constraint_adjustment) +} + +// SetOffset will set surface position offset. +// +// +// Specify the surface position offset relative to the position of the +// anchor on the anchor rectangle and the anchor on the surface. For +// example if the anchor of the anchor rectangle is at (x, y), the surface +// has the gravity bottom|right, and the offset is (ox, oy), the calculated +// surface position will be (x + ox, y + oy). The offset position of the +// surface is the one used for constraint testing. See +// set_constraint_adjustment. +// +// An example use case is placing a popup menu on top of a user interface +// element, while aligning the user interface element of the parent surface +// with some user interface element placed somewhere in the popup surface. +// +func (p *Positioner) SetOffset(x int32, y int32) error { + return p.Context().SendRequest(p, 6, x, y) +} + +const ( + PositionerErrorInvalidInput = 0 +) + +const ( + PositionerAnchorNone = 0 + PositionerAnchorTop = 1 + PositionerAnchorBottom = 2 + PositionerAnchorLeft = 4 + PositionerAnchorRight = 8 +) + +const ( + PositionerGravityNone = 0 + PositionerGravityTop = 1 + PositionerGravityBottom = 2 + PositionerGravityLeft = 4 + PositionerGravityRight = 8 +) + +const ( + PositionerConstraintAdjustmentNone = 0 + PositionerConstraintAdjustmentSlideX = 1 + PositionerConstraintAdjustmentSlideY = 2 + PositionerConstraintAdjustmentFlipX = 4 + PositionerConstraintAdjustmentFlipY = 8 + PositionerConstraintAdjustmentResizeX = 16 + PositionerConstraintAdjustmentResizeY = 32 +) + +type SurfaceConfigureEvent struct { + EventContext context.Context + Serial uint32 +} + +type SurfaceConfigureHandler interface { + HandleSurfaceConfigure(SurfaceConfigureEvent) +} + +func (p *Surface) AddConfigureHandler(h SurfaceConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *Surface) RemoveConfigureHandler(h SurfaceConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +func (p *Surface) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.configureHandlers) > 0 { + ev := SurfaceConfigureEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandleSurfaceConfigure(ev) + } + p.mu.RUnlock() + } + } +} + +type Surface struct { + wl.BaseProxy + mu sync.RWMutex + configureHandlers []SurfaceConfigureHandler +} + +func NewSurface(ctx *wl.Context) *Surface { + ret := new(Surface) + ctx.Register(ret) + return ret +} + +// Destroy will destroy the xdg_surface. +// +// +// Destroy the xdg_surface object. An xdg_surface must only be destroyed +// after its role object has been destroyed. +// +func (p *Surface) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// GetToplevel will assign the xdg_toplevel surface role. +// +// +// This creates an xdg_toplevel object for the given xdg_surface and gives +// the associated wl_surface the xdg_toplevel role. +// +// See the documentation of xdg_toplevel for more details about what an +// xdg_toplevel is and how it is used. +// +func (p *Surface) GetToplevel() (*Toplevel, error) { + ret := NewToplevel(p.Context()) + return ret, p.Context().SendRequest(p, 1, wl.Proxy(ret)) +} + +// GetPopup will assign the xdg_popup surface role. +// +// +// This creates an xdg_popup object for the given xdg_surface and gives the +// associated wl_surface the xdg_popup role. +// +// See the documentation of xdg_popup for more details about what an +// xdg_popup is and how it is used. +// +func (p *Surface) GetPopup(parent *Surface, positioner *Positioner) (*Popup, error) { + ret := NewPopup(p.Context()) + return ret, p.Context().SendRequest(p, 2, wl.Proxy(ret), parent, positioner) +} + +// SetWindowGeometry will set the new window geometry. +// +// +// The window geometry of a surface is its "visible bounds" from the +// user's perspective. Client-side decorations often have invisible +// portions like drop-shadows which should be ignored for the +// purposes of aligning, placing and constraining windows. +// +// The window geometry is double buffered, and will be applied at the +// time wl_surface.commit of the corresponding wl_surface is called. +// +// Once the window geometry of the surface is set, it is not possible to +// unset it, and it will remain the same until set_window_geometry is +// called again, even if a new subsurface or buffer is attached. +// +// If never set, the value is the full bounds of the surface, +// including any subsurfaces. This updates dynamically on every +// commit. This unset is meant for extremely simple clients. +// +// The arguments are given in the surface-local coordinate space of +// the wl_surface associated with this xdg_surface. +// +// The width and height must be greater than zero. Setting an invalid size +// will raise an error. When applied, the effective window geometry will be +// the set window geometry clamped to the bounding rectangle of the +// combined geometry of the surface of the xdg_surface and the associated +// subsurfaces. +// +func (p *Surface) SetWindowGeometry(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 3, x, y, width, height) +} + +// AckConfigure will ack a configure event. +// +// +// When a configure event is received, if a client commits the +// surface in response to the configure event, then the client +// must make an ack_configure request sometime before the commit +// request, passing along the serial of the configure event. +// +// For instance, for toplevel surfaces the compositor might use this +// information to move a surface to the top left only when the client has +// drawn itself for the maximized or fullscreen state. +// +// If the client receives multiple configure events before it +// can respond to one, it only has to ack the last configure event. +// +// A client is not required to commit immediately after sending +// an ack_configure request - it may even ack_configure several times +// before its next surface commit. +// +// A client may send multiple ack_configure requests before committing, but +// only the last request sent before a commit indicates which configure +// event the client really is responding to. +// +func (p *Surface) AckConfigure(serial uint32) error { + return p.Context().SendRequest(p, 4, serial) +} + +const ( + SurfaceErrorNotConstructed = 1 + SurfaceErrorAlreadyConstructed = 2 + SurfaceErrorUnconfiguredBuffer = 3 +) + +type ToplevelConfigureEvent struct { + EventContext context.Context + Width int32 + Height int32 + States []int32 +} + +type ToplevelConfigureHandler interface { + HandleToplevelConfigure(ToplevelConfigureEvent) +} + +func (p *Toplevel) AddConfigureHandler(h ToplevelConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *Toplevel) RemoveConfigureHandler(h ToplevelConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +type ToplevelCloseEvent struct { + EventContext context.Context +} + +type ToplevelCloseHandler interface { + HandleToplevelClose(ToplevelCloseEvent) +} + +func (p *Toplevel) AddCloseHandler(h ToplevelCloseHandler) { + if h != nil { + p.mu.Lock() + p.closeHandlers = append(p.closeHandlers, h) + p.mu.Unlock() + } +} + +func (p *Toplevel) RemoveCloseHandler(h ToplevelCloseHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.closeHandlers { + if e == h { + p.closeHandlers = append(p.closeHandlers[:i], p.closeHandlers[i+1:]...) + break + } + } +} + +func (p *Toplevel) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.configureHandlers) > 0 { + ev := ToplevelConfigureEvent{} + ev.EventContext = ctx + ev.Width = event.Int32() + ev.Height = event.Int32() + ev.States = event.Array() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandleToplevelConfigure(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.closeHandlers) > 0 { + ev := ToplevelCloseEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.closeHandlers { + h.HandleToplevelClose(ev) + } + p.mu.RUnlock() + } + } +} + +type Toplevel struct { + wl.BaseProxy + mu sync.RWMutex + configureHandlers []ToplevelConfigureHandler + closeHandlers []ToplevelCloseHandler +} + +func NewToplevel(ctx *wl.Context) *Toplevel { + ret := new(Toplevel) + ctx.Register(ret) + return ret +} + +// Destroy will destroy the xdg_toplevel. +// +// +// Unmap and destroy the window. The window will be effectively +// hidden from the user's point of view, and all state like +// maximization, fullscreen, and so on, will be lost. +// +func (p *Toplevel) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// SetParent will set the parent of this surface. +// +// +// Set the "parent" of this surface. This window should be stacked +// above a parent. The parent surface must be mapped as long as this +// surface is mapped. +// +// Parent windows should be set on dialogs, toolboxes, or other +// "auxiliary" surfaces, so that the parent is raised when the dialog +// is raised. +// +func (p *Toplevel) SetParent(parent *Toplevel) error { + return p.Context().SendRequest(p, 1, parent) +} + +// SetTitle will set surface title. +// +// +// Set a short title for the surface. +// +// This string may be used to identify the surface in a task bar, +// window list, or other user interface elements provided by the +// compositor. +// +// The string must be encoded in UTF-8. +// +func (p *Toplevel) SetTitle(title string) error { + return p.Context().SendRequest(p, 2, title) +} + +// SetAppId will set application ID. +// +// +// Set an application identifier for the surface. +// +// The app ID identifies the general class of applications to which +// the surface belongs. The compositor can use this to group multiple +// surfaces together, or to determine how to launch a new application. +// +// For D-Bus activatable applications, the app ID is used as the D-Bus +// service name. +// +// The compositor shell will try to group application surfaces together +// by their app ID. As a best practice, it is suggested to select app +// ID's that match the basename of the application's .desktop file. +// For example, "org.freedesktop.FooViewer" where the .desktop file is +// "org.freedesktop.FooViewer.desktop". +// +// See the desktop-entry specification [0] for more details on +// application identifiers and how they relate to well-known D-Bus +// names and .desktop files. +// +// [0] http://standards.freedesktop.org/desktop-entry-spec/ +// +func (p *Toplevel) SetAppId(app_id string) error { + return p.Context().SendRequest(p, 3, app_id) +} + +// ShowWindowMenu will show the window menu. +// +// +// Clients implementing client-side decorations might want to show +// a context menu when right-clicking on the decorations, giving the +// user a menu that they can use to maximize or minimize the window. +// +// This request asks the compositor to pop up such a window menu at +// the given position, relative to the local surface coordinates of +// the parent surface. There are no guarantees as to what menu items +// the window menu contains. +// +// This request must be used in response to some sort of user action +// like a button press, key press, or touch down event. +// +func (p *Toplevel) ShowWindowMenu(seat *wl.Seat, serial uint32, x int32, y int32) error { + return p.Context().SendRequest(p, 4, seat, serial, x, y) +} + +// Move will start an interactive move. +// +// +// Start an interactive, user-driven move of the surface. +// +// This request must be used in response to some sort of user action +// like a button press, key press, or touch down event. The passed +// serial is used to determine the type of interactive move (touch, +// pointer, etc). +// +// The server may ignore move requests depending on the state of +// the surface (e.g. fullscreen or maximized), or if the passed serial +// is no longer valid. +// +// If triggered, the surface will lose the focus of the device +// (wl_pointer, wl_touch, etc) used for the move. It is up to the +// compositor to visually indicate that the move is taking place, such as +// updating a pointer cursor, during the move. There is no guarantee +// that the device focus will return when the move is completed. +// +func (p *Toplevel) Move(seat *wl.Seat, serial uint32) error { + return p.Context().SendRequest(p, 5, seat, serial) +} + +// Resize will start an interactive resize. +// +// +// Start a user-driven, interactive resize of the surface. +// +// This request must be used in response to some sort of user action +// like a button press, key press, or touch down event. The passed +// serial is used to determine the type of interactive resize (touch, +// pointer, etc). +// +// The server may ignore resize requests depending on the state of +// the surface (e.g. fullscreen or maximized). +// +// If triggered, the client will receive configure events with the +// "resize" state enum value and the expected sizes. See the "resize" +// enum value for more details about what is required. The client +// must also acknowledge configure events using "ack_configure". After +// the resize is completed, the client will receive another "configure" +// event without the resize state. +// +// If triggered, the surface also will lose the focus of the device +// (wl_pointer, wl_touch, etc) used for the resize. It is up to the +// compositor to visually indicate that the resize is taking place, +// such as updating a pointer cursor, during the resize. There is no +// guarantee that the device focus will return when the resize is +// completed. +// +// The edges parameter specifies how the surface should be resized, +// and is one of the values of the resize_edge enum. The compositor +// may use this information to update the surface position for +// example when dragging the top left corner. The compositor may also +// use this information to adapt its behavior, e.g. choose an +// appropriate cursor image. +// +func (p *Toplevel) Resize(seat *wl.Seat, serial uint32, edges uint32) error { + return p.Context().SendRequest(p, 6, seat, serial, edges) +} + +// SetMaxSize will set the maximum size. +// +// +// Set a maximum size for the window. +// +// The client can specify a maximum size so that the compositor does +// not try to configure the window beyond this size. +// +// The width and height arguments are in window geometry coordinates. +// See xdg_surface.set_window_geometry. +// +// Values set in this way are double-buffered. They will get applied +// on the next commit. +// +// The compositor can use this information to allow or disallow +// different states like maximize or fullscreen and draw accurate +// animations. +// +// Similarly, a tiling window manager may use this information to +// place and resize client windows in a more effective way. +// +// The client should not rely on the compositor to obey the maximum +// size. The compositor may decide to ignore the values set by the +// client and request a larger size. +// +// If never set, or a value of zero in the request, means that the +// client has no expected maximum size in the given dimension. +// As a result, a client wishing to reset the maximum size +// to an unspecified state can use zero for width and height in the +// request. +// +// Requesting a maximum size to be smaller than the minimum size of +// a surface is illegal and will result in a protocol error. +// +// The width and height must be greater than or equal to zero. Using +// strictly negative values for width and height will result in a +// protocol error. +// +func (p *Toplevel) SetMaxSize(width int32, height int32) error { + return p.Context().SendRequest(p, 7, width, height) +} + +// SetMinSize will set the minimum size. +// +// +// Set a minimum size for the window. +// +// The client can specify a minimum size so that the compositor does +// not try to configure the window below this size. +// +// The width and height arguments are in window geometry coordinates. +// See xdg_surface.set_window_geometry. +// +// Values set in this way are double-buffered. They will get applied +// on the next commit. +// +// The compositor can use this information to allow or disallow +// different states like maximize or fullscreen and draw accurate +// animations. +// +// Similarly, a tiling window manager may use this information to +// place and resize client windows in a more effective way. +// +// The client should not rely on the compositor to obey the minimum +// size. The compositor may decide to ignore the values set by the +// client and request a smaller size. +// +// If never set, or a value of zero in the request, means that the +// client has no expected minimum size in the given dimension. +// As a result, a client wishing to reset the minimum size +// to an unspecified state can use zero for width and height in the +// request. +// +// Requesting a minimum size to be larger than the maximum size of +// a surface is illegal and will result in a protocol error. +// +// The width and height must be greater than or equal to zero. Using +// strictly negative values for width and height will result in a +// protocol error. +// +func (p *Toplevel) SetMinSize(width int32, height int32) error { + return p.Context().SendRequest(p, 8, width, height) +} + +// SetMaximized will maximize the window. +// +// +// Maximize the surface. +// +// After requesting that the surface should be maximized, the compositor +// will respond by emitting a configure event with the "maximized" state +// and the required window geometry. The client should then update its +// content, drawing it in a maximized state, i.e. without shadow or other +// decoration outside of the window geometry. The client must also +// acknowledge the configure when committing the new content (see +// ack_configure). +// +// It is up to the compositor to decide how and where to maximize the +// surface, for example which output and what region of the screen should +// be used. +// +// If the surface was already maximized, the compositor will still emit +// a configure event with the "maximized" state. +// +func (p *Toplevel) SetMaximized() error { + return p.Context().SendRequest(p, 9) +} + +// UnsetMaximized will unmaximize the window. +// +// +// Unmaximize the surface. +// +// After requesting that the surface should be unmaximized, the compositor +// will respond by emitting a configure event without the "maximized" +// state. If available, the compositor will include the window geometry +// dimensions the window had prior to being maximized in the configure +// request. The client must then update its content, drawing it in a +// regular state, i.e. potentially with shadow, etc. The client must also +// acknowledge the configure when committing the new content (see +// ack_configure). +// +// It is up to the compositor to position the surface after it was +// unmaximized; usually the position the surface had before maximizing, if +// applicable. +// +// If the surface was already not maximized, the compositor will still +// emit a configure event without the "maximized" state. +// +func (p *Toplevel) UnsetMaximized() error { + return p.Context().SendRequest(p, 10) +} + +// SetFullscreen will set the window as fullscreen on a monitor. +// +// +// Make the surface fullscreen. +// +// You can specify an output that you would prefer to be fullscreen. +// If this value is NULL, it's up to the compositor to choose which +// display will be used to map this surface. +// +// If the surface doesn't cover the whole output, the compositor will +// position the surface in the center of the output and compensate with +// black borders filling the rest of the output. +// +func (p *Toplevel) SetFullscreen(output *wl.Output) error { + return p.Context().SendRequest(p, 11, output) +} + +// UnsetFullscreen will . +// +// +func (p *Toplevel) UnsetFullscreen() error { + return p.Context().SendRequest(p, 12) +} + +// SetMinimized will set the window as minimized. +// +// +// Request that the compositor minimize your surface. There is no +// way to know if the surface is currently minimized, nor is there +// any way to unset minimization on this surface. +// +// If you are looking to throttle redrawing when minimized, please +// instead use the wl_surface.frame event for this, as this will +// also work with live previews on windows in Alt-Tab, Expose or +// similar compositor features. +// +func (p *Toplevel) SetMinimized() error { + return p.Context().SendRequest(p, 13) +} + +const ( + ToplevelResizeEdgeNone = 0 + ToplevelResizeEdgeTop = 1 + ToplevelResizeEdgeBottom = 2 + ToplevelResizeEdgeLeft = 4 + ToplevelResizeEdgeTopLeft = 5 + ToplevelResizeEdgeBottomLeft = 6 + ToplevelResizeEdgeRight = 8 + ToplevelResizeEdgeTopRight = 9 + ToplevelResizeEdgeBottomRight = 10 +) + +const ( + ToplevelStateMaximized = 1 + ToplevelStateFullscreen = 2 + ToplevelStateResizing = 3 + ToplevelStateActivated = 4 +) + +type PopupConfigureEvent struct { + EventContext context.Context + X int32 + Y int32 + Width int32 + Height int32 +} + +type PopupConfigureHandler interface { + HandlePopupConfigure(PopupConfigureEvent) +} + +func (p *Popup) AddConfigureHandler(h PopupConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *Popup) RemoveConfigureHandler(h PopupConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +type PopupPopupDoneEvent struct { + EventContext context.Context +} + +type PopupPopupDoneHandler interface { + HandlePopupPopupDone(PopupPopupDoneEvent) +} + +func (p *Popup) AddPopupDoneHandler(h PopupPopupDoneHandler) { + if h != nil { + p.mu.Lock() + p.popupDoneHandlers = append(p.popupDoneHandlers, h) + p.mu.Unlock() + } +} + +func (p *Popup) RemovePopupDoneHandler(h PopupPopupDoneHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.popupDoneHandlers { + if e == h { + p.popupDoneHandlers = append(p.popupDoneHandlers[:i], p.popupDoneHandlers[i+1:]...) + break + } + } +} + +func (p *Popup) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.configureHandlers) > 0 { + ev := PopupConfigureEvent{} + ev.EventContext = ctx + ev.X = event.Int32() + ev.Y = event.Int32() + ev.Width = event.Int32() + ev.Height = event.Int32() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandlePopupConfigure(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.popupDoneHandlers) > 0 { + ev := PopupPopupDoneEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.popupDoneHandlers { + h.HandlePopupPopupDone(ev) + } + p.mu.RUnlock() + } + } +} + +type Popup struct { + wl.BaseProxy + mu sync.RWMutex + configureHandlers []PopupConfigureHandler + popupDoneHandlers []PopupPopupDoneHandler +} + +func NewPopup(ctx *wl.Context) *Popup { + ret := new(Popup) + ctx.Register(ret) + return ret +} + +// Destroy will remove xdg_popup interface. +// +// +// This destroys the popup. Explicitly destroying the xdg_popup +// object will also dismiss the popup, and unmap the surface. +// +// If this xdg_popup is not the "topmost" popup, a protocol error +// will be sent. +// +func (p *Popup) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// Grab will make the popup take an explicit grab. +// +// +// This request makes the created popup take an explicit grab. An explicit +// grab will be dismissed when the user dismisses the popup, or when the +// client destroys the xdg_popup. This can be done by the user clicking +// outside the surface, using the keyboard, or even locking the screen +// through closing the lid or a timeout. +// +// If the compositor denies the grab, the popup will be immediately +// dismissed. +// +// This request must be used in response to some sort of user action like a +// button press, key press, or touch down event. The serial number of the +// event should be passed as 'serial'. +// +// The parent of a grabbing popup must either be an xdg_toplevel surface or +// another xdg_popup with an explicit grab. If the parent is another +// xdg_popup it means that the popups are nested, with this popup now being +// the topmost popup. +// +// Nested popups must be destroyed in the reverse order they were created +// in, e.g. the only popup you are allowed to destroy at all times is the +// topmost one. +// +// When compositors choose to dismiss a popup, they may dismiss every +// nested grabbing popup as well. When a compositor dismisses popups, it +// will follow the same dismissing order as required from the client. +// +// The parent of a grabbing popup must either be another xdg_popup with an +// active explicit grab, or an xdg_popup or xdg_toplevel, if there are no +// explicit grabs already taken. +// +// If the topmost grabbing popup is destroyed, the grab will be returned to +// the parent of the popup, if that parent previously had an explicit grab. +// +// If the parent is a grabbing popup which has already been dismissed, this +// popup will be immediately dismissed. If the parent is a popup that did +// not take an explicit grab, an error will be raised. +// +// During a popup grab, the client owning the grab will receive pointer +// and touch events for all their surfaces as normal (similar to an +// "owner-events" grab in X11 parlance), while the top most grabbing popup +// will always have keyboard focus. +// +func (p *Popup) Grab(seat *wl.Seat, serial uint32) error { + return p.Context().SendRequest(p, 1, seat, serial) +} + +const ( + PopupErrorInvalidGrab = 0 +) diff --git a/src/github.com/dkolbly/wl/xdg/shell.go b/src/github.com/dkolbly/wl/xdg/shell.go new file mode 100644 index 00000000..0579db06 --- /dev/null +++ b/src/github.com/dkolbly/wl/xdg/shell.go @@ -0,0 +1,1116 @@ +// package xdg acts as a client for the xdg_shell wayland protocol. + +// generated by wl-scanner +// https://github.com/dkolbly/wl-scanner +// from: https://raw.githubusercontent.com/wayland-project/wayland-protocols/master/stable/xdg-shell/xdg-shell.xml +// on 2018-02-19 14:50:40 -0600 +package xdg + +import ( + "context" + "sync" + + "github.com/dkolbly/wl" +) + +type WmBasePingEvent struct { + EventContext context.Context + Serial uint32 +} + +type WmBasePingHandler interface { + HandleWmBasePing(WmBasePingEvent) +} + +func (p *WmBase) AddPingHandler(h WmBasePingHandler) { + if h != nil { + p.mu.Lock() + p.pingHandlers = append(p.pingHandlers, h) + p.mu.Unlock() + } +} + +func (p *WmBase) RemovePingHandler(h WmBasePingHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.pingHandlers { + if e == h { + p.pingHandlers = append(p.pingHandlers[:i], p.pingHandlers[i+1:]...) + break + } + } +} + +func (p *WmBase) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.pingHandlers) > 0 { + ev := WmBasePingEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + p.mu.RLock() + for _, h := range p.pingHandlers { + h.HandleWmBasePing(ev) + } + p.mu.RUnlock() + } + } +} + +type WmBase struct { + wl.BaseProxy + mu sync.RWMutex + pingHandlers []WmBasePingHandler +} + +func NewWmBase(ctx *wl.Context) *WmBase { + ret := new(WmBase) + ctx.Register(ret) + return ret +} + +// Destroy will destroy xdg_wm_base. +// +// +// Destroy this xdg_wm_base object. +// +// Destroying a bound xdg_wm_base object while there are surfaces +// still alive created by this xdg_wm_base object instance is illegal +// and will result in a protocol error. +// +func (p *WmBase) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// CreatePositioner will create a positioner object. +// +// +// Create a positioner object. A positioner object is used to position +// surfaces relative to some parent surface. See the interface description +// and xdg_surface.get_popup for details. +// +func (p *WmBase) CreatePositioner() (*Positioner, error) { + ret := NewPositioner(p.Context()) + return ret, p.Context().SendRequest(p, 1, wl.Proxy(ret)) +} + +// GetXdgSurface will create a shell surface from a surface. +// +// +// This creates an xdg_surface for the given surface. While xdg_surface +// itself is not a role, the corresponding surface may only be assigned +// a role extending xdg_surface, such as xdg_toplevel or xdg_popup. +// +// This creates an xdg_surface for the given surface. An xdg_surface is +// used as basis to define a role to a given surface, such as xdg_toplevel +// or xdg_popup. It also manages functionality shared between xdg_surface +// based surface roles. +// +// See the documentation of xdg_surface for more details about what an +// xdg_surface is and how it is used. +// +func (p *WmBase) GetXdgSurface(surface *wl.Surface) (*Surface, error) { + ret := NewSurface(p.Context()) + return ret, p.Context().SendRequest(p, 2, wl.Proxy(ret), surface) +} + +// Pong will respond to a ping event. +// +// +// A client must respond to a ping event with a pong request or +// the client may be deemed unresponsive. See xdg_wm_base.ping. +// +func (p *WmBase) Pong(serial uint32) error { + return p.Context().SendRequest(p, 3, serial) +} + +const ( + WmBaseErrorRole = 0 + WmBaseErrorDefunctSurfaces = 1 + WmBaseErrorNotTheTopmostPopup = 2 + WmBaseErrorInvalidPopupParent = 3 + WmBaseErrorInvalidSurfaceState = 4 + WmBaseErrorInvalidPositioner = 5 +) + +type Positioner struct { + wl.BaseProxy +} + +func NewPositioner(ctx *wl.Context) *Positioner { + ret := new(Positioner) + ctx.Register(ret) + return ret +} + +// Destroy will destroy the xdg_positioner object. +// +// +// Notify the compositor that the xdg_positioner will no longer be used. +// +func (p *Positioner) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// SetSize will set the size of the to-be positioned rectangle. +// +// +// Set the size of the surface that is to be positioned with the positioner +// object. The size is in surface-local coordinates and corresponds to the +// window geometry. See xdg_surface.set_window_geometry. +// +// If a zero or negative size is set the invalid_input error is raised. +// +func (p *Positioner) SetSize(width int32, height int32) error { + return p.Context().SendRequest(p, 1, width, height) +} + +// SetAnchorRect will set the anchor rectangle within the parent surface. +// +// +// Specify the anchor rectangle within the parent surface that the child +// surface will be placed relative to. The rectangle is relative to the +// window geometry as defined by xdg_surface.set_window_geometry of the +// parent surface. +// +// When the xdg_positioner object is used to position a child surface, the +// anchor rectangle may not extend outside the window geometry of the +// positioned child's parent surface. +// +// If a negative size is set the invalid_input error is raised. +// +func (p *Positioner) SetAnchorRect(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 2, x, y, width, height) +} + +// SetAnchor will set anchor rectangle anchor. +// +// +// Defines the anchor point for the anchor rectangle. The specified anchor +// is used derive an anchor point that the child surface will be +// positioned relative to. If a corner anchor is set (e.g. 'top_left' or +// 'bottom_right'), the anchor point will be at the specified corner; +// otherwise, the derived anchor point will be centered on the specified +// edge, or in the center of the anchor rectangle if no edge is specified. +// +func (p *Positioner) SetAnchor(anchor uint32) error { + return p.Context().SendRequest(p, 3, anchor) +} + +// SetGravity will set child surface gravity. +// +// +// Defines in what direction a surface should be positioned, relative to +// the anchor point of the parent surface. If a corner gravity is +// specified (e.g. 'bottom_right' or 'top_left'), then the child surface +// will be placed towards the specified gravity; otherwise, the child +// surface will be centered over the anchor point on any axis that had no +// gravity specified. +// +func (p *Positioner) SetGravity(gravity uint32) error { + return p.Context().SendRequest(p, 4, gravity) +} + +// SetConstraintAdjustment will set the adjustment to be done when constrained. +// +// +// Specify how the window should be positioned if the originally intended +// position caused the surface to be constrained, meaning at least +// partially outside positioning boundaries set by the compositor. The +// adjustment is set by constructing a bitmask describing the adjustment to +// be made when the surface is constrained on that axis. +// +// If no bit for one axis is set, the compositor will assume that the child +// surface should not change its position on that axis when constrained. +// +// If more than one bit for one axis is set, the order of how adjustments +// are applied is specified in the corresponding adjustment descriptions. +// +// The default adjustment is none. +// +func (p *Positioner) SetConstraintAdjustment(constraint_adjustment uint32) error { + return p.Context().SendRequest(p, 5, constraint_adjustment) +} + +// SetOffset will set surface position offset. +// +// +// Specify the surface position offset relative to the position of the +// anchor on the anchor rectangle and the anchor on the surface. For +// example if the anchor of the anchor rectangle is at (x, y), the surface +// has the gravity bottom|right, and the offset is (ox, oy), the calculated +// surface position will be (x + ox, y + oy). The offset position of the +// surface is the one used for constraint testing. See +// set_constraint_adjustment. +// +// An example use case is placing a popup menu on top of a user interface +// element, while aligning the user interface element of the parent surface +// with some user interface element placed somewhere in the popup surface. +// +func (p *Positioner) SetOffset(x int32, y int32) error { + return p.Context().SendRequest(p, 6, x, y) +} + +const ( + PositionerErrorInvalidInput = 0 +) + +const ( + PositionerAnchorNone = 0 + PositionerAnchorTop = 1 + PositionerAnchorBottom = 2 + PositionerAnchorLeft = 3 + PositionerAnchorRight = 4 + PositionerAnchorTopLeft = 5 + PositionerAnchorBottomLeft = 6 + PositionerAnchorTopRight = 7 + PositionerAnchorBottomRight = 8 +) + +const ( + PositionerGravityNone = 0 + PositionerGravityTop = 1 + PositionerGravityBottom = 2 + PositionerGravityLeft = 3 + PositionerGravityRight = 4 + PositionerGravityTopLeft = 5 + PositionerGravityBottomLeft = 6 + PositionerGravityTopRight = 7 + PositionerGravityBottomRight = 8 +) + +const ( + PositionerConstraintAdjustmentNone = 0 + PositionerConstraintAdjustmentSlideX = 1 + PositionerConstraintAdjustmentSlideY = 2 + PositionerConstraintAdjustmentFlipX = 4 + PositionerConstraintAdjustmentFlipY = 8 + PositionerConstraintAdjustmentResizeX = 16 + PositionerConstraintAdjustmentResizeY = 32 +) + +type SurfaceConfigureEvent struct { + EventContext context.Context + Serial uint32 +} + +type SurfaceConfigureHandler interface { + HandleSurfaceConfigure(SurfaceConfigureEvent) +} + +func (p *Surface) AddConfigureHandler(h SurfaceConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *Surface) RemoveConfigureHandler(h SurfaceConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +func (p *Surface) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.configureHandlers) > 0 { + ev := SurfaceConfigureEvent{} + ev.EventContext = ctx + ev.Serial = event.Uint32() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandleSurfaceConfigure(ev) + } + p.mu.RUnlock() + } + } +} + +type Surface struct { + wl.BaseProxy + mu sync.RWMutex + configureHandlers []SurfaceConfigureHandler +} + +func NewSurface(ctx *wl.Context) *Surface { + ret := new(Surface) + ctx.Register(ret) + return ret +} + +// Destroy will destroy the xdg_surface. +// +// +// Destroy the xdg_surface object. An xdg_surface must only be destroyed +// after its role object has been destroyed. +// +func (p *Surface) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// GetToplevel will assign the xdg_toplevel surface role. +// +// +// This creates an xdg_toplevel object for the given xdg_surface and gives +// the associated wl_surface the xdg_toplevel role. +// +// See the documentation of xdg_toplevel for more details about what an +// xdg_toplevel is and how it is used. +// +func (p *Surface) GetToplevel() (*Toplevel, error) { + ret := NewToplevel(p.Context()) + return ret, p.Context().SendRequest(p, 1, wl.Proxy(ret)) +} + +// GetPopup will assign the xdg_popup surface role. +// +// +// This creates an xdg_popup object for the given xdg_surface and gives +// the associated wl_surface the xdg_popup role. +// +// If null is passed as a parent, a parent surface must be specified using +// some other protocol, before committing the initial state. +// +// See the documentation of xdg_popup for more details about what an +// xdg_popup is and how it is used. +// +func (p *Surface) GetPopup(parent *Surface, positioner *Positioner) (*Popup, error) { + ret := NewPopup(p.Context()) + return ret, p.Context().SendRequest(p, 2, wl.Proxy(ret), parent, positioner) +} + +// SetWindowGeometry will set the new window geometry. +// +// +// The window geometry of a surface is its "visible bounds" from the +// user's perspective. Client-side decorations often have invisible +// portions like drop-shadows which should be ignored for the +// purposes of aligning, placing and constraining windows. +// +// The window geometry is double buffered, and will be applied at the +// time wl_surface.commit of the corresponding wl_surface is called. +// +// When maintaining a position, the compositor should treat the (x, y) +// coordinate of the window geometry as the top left corner of the window. +// A client changing the (x, y) window geometry coordinate should in +// general not alter the position of the window. +// +// Once the window geometry of the surface is set, it is not possible to +// unset it, and it will remain the same until set_window_geometry is +// called again, even if a new subsurface or buffer is attached. +// +// If never set, the value is the full bounds of the surface, +// including any subsurfaces. This updates dynamically on every +// commit. This unset is meant for extremely simple clients. +// +// The arguments are given in the surface-local coordinate space of +// the wl_surface associated with this xdg_surface. +// +// The width and height must be greater than zero. Setting an invalid size +// will raise an error. When applied, the effective window geometry will be +// the set window geometry clamped to the bounding rectangle of the +// combined geometry of the surface of the xdg_surface and the associated +// subsurfaces. +// +func (p *Surface) SetWindowGeometry(x int32, y int32, width int32, height int32) error { + return p.Context().SendRequest(p, 3, x, y, width, height) +} + +// AckConfigure will ack a configure event. +// +// +// When a configure event is received, if a client commits the +// surface in response to the configure event, then the client +// must make an ack_configure request sometime before the commit +// request, passing along the serial of the configure event. +// +// For instance, for toplevel surfaces the compositor might use this +// information to move a surface to the top left only when the client has +// drawn itself for the maximized or fullscreen state. +// +// If the client receives multiple configure events before it +// can respond to one, it only has to ack the last configure event. +// +// A client is not required to commit immediately after sending +// an ack_configure request - it may even ack_configure several times +// before its next surface commit. +// +// A client may send multiple ack_configure requests before committing, but +// only the last request sent before a commit indicates which configure +// event the client really is responding to. +// +func (p *Surface) AckConfigure(serial uint32) error { + return p.Context().SendRequest(p, 4, serial) +} + +const ( + SurfaceErrorNotConstructed = 1 + SurfaceErrorAlreadyConstructed = 2 + SurfaceErrorUnconfiguredBuffer = 3 +) + +type ToplevelConfigureEvent struct { + EventContext context.Context + Width int32 + Height int32 + States []int32 +} + +type ToplevelConfigureHandler interface { + HandleToplevelConfigure(ToplevelConfigureEvent) +} + +func (p *Toplevel) AddConfigureHandler(h ToplevelConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *Toplevel) RemoveConfigureHandler(h ToplevelConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +type ToplevelCloseEvent struct { + EventContext context.Context +} + +type ToplevelCloseHandler interface { + HandleToplevelClose(ToplevelCloseEvent) +} + +func (p *Toplevel) AddCloseHandler(h ToplevelCloseHandler) { + if h != nil { + p.mu.Lock() + p.closeHandlers = append(p.closeHandlers, h) + p.mu.Unlock() + } +} + +func (p *Toplevel) RemoveCloseHandler(h ToplevelCloseHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.closeHandlers { + if e == h { + p.closeHandlers = append(p.closeHandlers[:i], p.closeHandlers[i+1:]...) + break + } + } +} + +func (p *Toplevel) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.configureHandlers) > 0 { + ev := ToplevelConfigureEvent{} + ev.EventContext = ctx + ev.Width = event.Int32() + ev.Height = event.Int32() + ev.States = event.Array() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandleToplevelConfigure(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.closeHandlers) > 0 { + ev := ToplevelCloseEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.closeHandlers { + h.HandleToplevelClose(ev) + } + p.mu.RUnlock() + } + } +} + +type Toplevel struct { + wl.BaseProxy + mu sync.RWMutex + configureHandlers []ToplevelConfigureHandler + closeHandlers []ToplevelCloseHandler +} + +func NewToplevel(ctx *wl.Context) *Toplevel { + ret := new(Toplevel) + ctx.Register(ret) + return ret +} + +// Destroy will destroy the xdg_toplevel. +// +// +// This request destroys the role surface and unmaps the surface; +// see "Unmapping" behavior in interface section for details. +// +func (p *Toplevel) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// SetParent will set the parent of this surface. +// +// +// Set the "parent" of this surface. This surface should be stacked +// above the parent surface and all other ancestor surfaces. +// +// Parent windows should be set on dialogs, toolboxes, or other +// "auxiliary" surfaces, so that the parent is raised when the dialog +// is raised. +// +// Setting a null parent for a child window removes any parent-child +// relationship for the child. Setting a null parent for a window which +// currently has no parent is a no-op. +// +// If the parent is unmapped then its children are managed as +// though the parent of the now-unmapped parent has become the +// parent of this surface. If no parent exists for the now-unmapped +// parent then the children are managed as though they have no +// parent surface. +// +func (p *Toplevel) SetParent(parent *Toplevel) error { + return p.Context().SendRequest(p, 1, parent) +} + +// SetTitle will set surface title. +// +// +// Set a short title for the surface. +// +// This string may be used to identify the surface in a task bar, +// window list, or other user interface elements provided by the +// compositor. +// +// The string must be encoded in UTF-8. +// +func (p *Toplevel) SetTitle(title string) error { + return p.Context().SendRequest(p, 2, title) +} + +// SetAppId will set application ID. +// +// +// Set an application identifier for the surface. +// +// The app ID identifies the general class of applications to which +// the surface belongs. The compositor can use this to group multiple +// surfaces together, or to determine how to launch a new application. +// +// For D-Bus activatable applications, the app ID is used as the D-Bus +// service name. +// +// The compositor shell will try to group application surfaces together +// by their app ID. As a best practice, it is suggested to select app +// ID's that match the basename of the application's .desktop file. +// For example, "org.freedesktop.FooViewer" where the .desktop file is +// "org.freedesktop.FooViewer.desktop". +// +// See the desktop-entry specification [0] for more details on +// application identifiers and how they relate to well-known D-Bus +// names and .desktop files. +// +// [0] http://standards.freedesktop.org/desktop-entry-spec/ +// +func (p *Toplevel) SetAppId(app_id string) error { + return p.Context().SendRequest(p, 3, app_id) +} + +// ShowWindowMenu will show the window menu. +// +// +// Clients implementing client-side decorations might want to show +// a context menu when right-clicking on the decorations, giving the +// user a menu that they can use to maximize or minimize the window. +// +// This request asks the compositor to pop up such a window menu at +// the given position, relative to the local surface coordinates of +// the parent surface. There are no guarantees as to what menu items +// the window menu contains. +// +// This request must be used in response to some sort of user action +// like a button press, key press, or touch down event. +// +func (p *Toplevel) ShowWindowMenu(seat *wl.Seat, serial uint32, x int32, y int32) error { + return p.Context().SendRequest(p, 4, seat, serial, x, y) +} + +// Move will start an interactive move. +// +// +// Start an interactive, user-driven move of the surface. +// +// This request must be used in response to some sort of user action +// like a button press, key press, or touch down event. The passed +// serial is used to determine the type of interactive move (touch, +// pointer, etc). +// +// The server may ignore move requests depending on the state of +// the surface (e.g. fullscreen or maximized), or if the passed serial +// is no longer valid. +// +// If triggered, the surface will lose the focus of the device +// (wl_pointer, wl_touch, etc) used for the move. It is up to the +// compositor to visually indicate that the move is taking place, such as +// updating a pointer cursor, during the move. There is no guarantee +// that the device focus will return when the move is completed. +// +func (p *Toplevel) Move(seat *wl.Seat, serial uint32) error { + return p.Context().SendRequest(p, 5, seat, serial) +} + +// Resize will start an interactive resize. +// +// +// Start a user-driven, interactive resize of the surface. +// +// This request must be used in response to some sort of user action +// like a button press, key press, or touch down event. The passed +// serial is used to determine the type of interactive resize (touch, +// pointer, etc). +// +// The server may ignore resize requests depending on the state of +// the surface (e.g. fullscreen or maximized). +// +// If triggered, the client will receive configure events with the +// "resize" state enum value and the expected sizes. See the "resize" +// enum value for more details about what is required. The client +// must also acknowledge configure events using "ack_configure". After +// the resize is completed, the client will receive another "configure" +// event without the resize state. +// +// If triggered, the surface also will lose the focus of the device +// (wl_pointer, wl_touch, etc) used for the resize. It is up to the +// compositor to visually indicate that the resize is taking place, +// such as updating a pointer cursor, during the resize. There is no +// guarantee that the device focus will return when the resize is +// completed. +// +// The edges parameter specifies how the surface should be resized, +// and is one of the values of the resize_edge enum. The compositor +// may use this information to update the surface position for +// example when dragging the top left corner. The compositor may also +// use this information to adapt its behavior, e.g. choose an +// appropriate cursor image. +// +func (p *Toplevel) Resize(seat *wl.Seat, serial uint32, edges uint32) error { + return p.Context().SendRequest(p, 6, seat, serial, edges) +} + +// SetMaxSize will set the maximum size. +// +// +// Set a maximum size for the window. +// +// The client can specify a maximum size so that the compositor does +// not try to configure the window beyond this size. +// +// The width and height arguments are in window geometry coordinates. +// See xdg_surface.set_window_geometry. +// +// Values set in this way are double-buffered. They will get applied +// on the next commit. +// +// The compositor can use this information to allow or disallow +// different states like maximize or fullscreen and draw accurate +// animations. +// +// Similarly, a tiling window manager may use this information to +// place and resize client windows in a more effective way. +// +// The client should not rely on the compositor to obey the maximum +// size. The compositor may decide to ignore the values set by the +// client and request a larger size. +// +// If never set, or a value of zero in the request, means that the +// client has no expected maximum size in the given dimension. +// As a result, a client wishing to reset the maximum size +// to an unspecified state can use zero for width and height in the +// request. +// +// Requesting a maximum size to be smaller than the minimum size of +// a surface is illegal and will result in a protocol error. +// +// The width and height must be greater than or equal to zero. Using +// strictly negative values for width and height will result in a +// protocol error. +// +func (p *Toplevel) SetMaxSize(width int32, height int32) error { + return p.Context().SendRequest(p, 7, width, height) +} + +// SetMinSize will set the minimum size. +// +// +// Set a minimum size for the window. +// +// The client can specify a minimum size so that the compositor does +// not try to configure the window below this size. +// +// The width and height arguments are in window geometry coordinates. +// See xdg_surface.set_window_geometry. +// +// Values set in this way are double-buffered. They will get applied +// on the next commit. +// +// The compositor can use this information to allow or disallow +// different states like maximize or fullscreen and draw accurate +// animations. +// +// Similarly, a tiling window manager may use this information to +// place and resize client windows in a more effective way. +// +// The client should not rely on the compositor to obey the minimum +// size. The compositor may decide to ignore the values set by the +// client and request a smaller size. +// +// If never set, or a value of zero in the request, means that the +// client has no expected minimum size in the given dimension. +// As a result, a client wishing to reset the minimum size +// to an unspecified state can use zero for width and height in the +// request. +// +// Requesting a minimum size to be larger than the maximum size of +// a surface is illegal and will result in a protocol error. +// +// The width and height must be greater than or equal to zero. Using +// strictly negative values for width and height will result in a +// protocol error. +// +func (p *Toplevel) SetMinSize(width int32, height int32) error { + return p.Context().SendRequest(p, 8, width, height) +} + +// SetMaximized will maximize the window. +// +// +// Maximize the surface. +// +// After requesting that the surface should be maximized, the compositor +// will respond by emitting a configure event with the "maximized" state +// and the required window geometry. The client should then update its +// content, drawing it in a maximized state, i.e. without shadow or other +// decoration outside of the window geometry. The client must also +// acknowledge the configure when committing the new content (see +// ack_configure). +// +// It is up to the compositor to decide how and where to maximize the +// surface, for example which output and what region of the screen should +// be used. +// +// If the surface was already maximized, the compositor will still emit +// a configure event with the "maximized" state. +// +// If the surface is in a fullscreen state, this request has no direct +// effect. It will alter the state the surface is returned to when +// unmaximized if not overridden by the compositor. +// +func (p *Toplevel) SetMaximized() error { + return p.Context().SendRequest(p, 9) +} + +// UnsetMaximized will unmaximize the window. +// +// +// Unmaximize the surface. +// +// After requesting that the surface should be unmaximized, the compositor +// will respond by emitting a configure event without the "maximized" +// state. If available, the compositor will include the window geometry +// dimensions the window had prior to being maximized in the configure +// event. The client must then update its content, drawing it in a +// regular state, i.e. potentially with shadow, etc. The client must also +// acknowledge the configure when committing the new content (see +// ack_configure). +// +// It is up to the compositor to position the surface after it was +// unmaximized; usually the position the surface had before maximizing, if +// applicable. +// +// If the surface was already not maximized, the compositor will still +// emit a configure event without the "maximized" state. +// +// If the surface is in a fullscreen state, this request has no direct +// effect. It will alter the state the surface is returned to when +// unmaximized if not overridden by the compositor. +// +func (p *Toplevel) UnsetMaximized() error { + return p.Context().SendRequest(p, 10) +} + +// SetFullscreen will set the window as fullscreen on an output. +// +// +// Make the surface fullscreen. +// +// After requesting that the surface should be fullscreened, the +// compositor will respond by emitting a configure event with the +// "fullscreen" state and the fullscreen window geometry. The client must +// also acknowledge the configure when committing the new content (see +// ack_configure). +// +// The output passed by the request indicates the client's preference as +// to which display it should be set fullscreen on. If this value is NULL, +// it's up to the compositor to choose which display will be used to map +// this surface. +// +// If the surface doesn't cover the whole output, the compositor will +// position the surface in the center of the output and compensate with +// with border fill covering the rest of the output. The content of the +// border fill is undefined, but should be assumed to be in some way that +// attempts to blend into the surrounding area (e.g. solid black). +// +// If the fullscreened surface is not opaque, the compositor must make +// sure that other screen content not part of the same surface tree (made +// up of subsurfaces, popups or similarly coupled surfaces) are not +// visible below the fullscreened surface. +// +func (p *Toplevel) SetFullscreen(output *wl.Output) error { + return p.Context().SendRequest(p, 11, output) +} + +// UnsetFullscreen will unset the window as fullscreen. +// +// +// Make the surface no longer fullscreen. +// +// After requesting that the surface should be unfullscreened, the +// compositor will respond by emitting a configure event without the +// "fullscreen" state. +// +// Making a surface unfullscreen sets states for the surface based on the following: +// * the state(s) it may have had before becoming fullscreen +// * any state(s) decided by the compositor +// * any state(s) requested by the client while the surface was fullscreen +// +// The compositor may include the previous window geometry dimensions in +// the configure event, if applicable. +// +// The client must also acknowledge the configure when committing the new +// content (see ack_configure). +// +func (p *Toplevel) UnsetFullscreen() error { + return p.Context().SendRequest(p, 12) +} + +// SetMinimized will set the window as minimized. +// +// +// Request that the compositor minimize your surface. There is no +// way to know if the surface is currently minimized, nor is there +// any way to unset minimization on this surface. +// +// If you are looking to throttle redrawing when minimized, please +// instead use the wl_surface.frame event for this, as this will +// also work with live previews on windows in Alt-Tab, Expose or +// similar compositor features. +// +func (p *Toplevel) SetMinimized() error { + return p.Context().SendRequest(p, 13) +} + +const ( + ToplevelResizeEdgeNone = 0 + ToplevelResizeEdgeTop = 1 + ToplevelResizeEdgeBottom = 2 + ToplevelResizeEdgeLeft = 4 + ToplevelResizeEdgeTopLeft = 5 + ToplevelResizeEdgeBottomLeft = 6 + ToplevelResizeEdgeRight = 8 + ToplevelResizeEdgeTopRight = 9 + ToplevelResizeEdgeBottomRight = 10 +) + +const ( + ToplevelStateMaximized = 1 + ToplevelStateFullscreen = 2 + ToplevelStateResizing = 3 + ToplevelStateActivated = 4 +) + +type PopupConfigureEvent struct { + EventContext context.Context + X int32 + Y int32 + Width int32 + Height int32 +} + +type PopupConfigureHandler interface { + HandlePopupConfigure(PopupConfigureEvent) +} + +func (p *Popup) AddConfigureHandler(h PopupConfigureHandler) { + if h != nil { + p.mu.Lock() + p.configureHandlers = append(p.configureHandlers, h) + p.mu.Unlock() + } +} + +func (p *Popup) RemoveConfigureHandler(h PopupConfigureHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.configureHandlers { + if e == h { + p.configureHandlers = append(p.configureHandlers[:i], p.configureHandlers[i+1:]...) + break + } + } +} + +type PopupPopupDoneEvent struct { + EventContext context.Context +} + +type PopupPopupDoneHandler interface { + HandlePopupPopupDone(PopupPopupDoneEvent) +} + +func (p *Popup) AddPopupDoneHandler(h PopupPopupDoneHandler) { + if h != nil { + p.mu.Lock() + p.popupDoneHandlers = append(p.popupDoneHandlers, h) + p.mu.Unlock() + } +} + +func (p *Popup) RemovePopupDoneHandler(h PopupPopupDoneHandler) { + p.mu.Lock() + defer p.mu.Unlock() + + for i, e := range p.popupDoneHandlers { + if e == h { + p.popupDoneHandlers = append(p.popupDoneHandlers[:i], p.popupDoneHandlers[i+1:]...) + break + } + } +} + +func (p *Popup) Dispatch(ctx context.Context, event *wl.Event) { + switch event.Opcode { + case 0: + if len(p.configureHandlers) > 0 { + ev := PopupConfigureEvent{} + ev.EventContext = ctx + ev.X = event.Int32() + ev.Y = event.Int32() + ev.Width = event.Int32() + ev.Height = event.Int32() + p.mu.RLock() + for _, h := range p.configureHandlers { + h.HandlePopupConfigure(ev) + } + p.mu.RUnlock() + } + case 1: + if len(p.popupDoneHandlers) > 0 { + ev := PopupPopupDoneEvent{} + ev.EventContext = ctx + p.mu.RLock() + for _, h := range p.popupDoneHandlers { + h.HandlePopupPopupDone(ev) + } + p.mu.RUnlock() + } + } +} + +type Popup struct { + wl.BaseProxy + mu sync.RWMutex + configureHandlers []PopupConfigureHandler + popupDoneHandlers []PopupPopupDoneHandler +} + +func NewPopup(ctx *wl.Context) *Popup { + ret := new(Popup) + ctx.Register(ret) + return ret +} + +// Destroy will remove xdg_popup interface. +// +// +// This destroys the popup. Explicitly destroying the xdg_popup +// object will also dismiss the popup, and unmap the surface. +// +// If this xdg_popup is not the "topmost" popup, a protocol error +// will be sent. +// +func (p *Popup) Destroy() error { + return p.Context().SendRequest(p, 0) +} + +// Grab will make the popup take an explicit grab. +// +// +// This request makes the created popup take an explicit grab. An explicit +// grab will be dismissed when the user dismisses the popup, or when the +// client destroys the xdg_popup. This can be done by the user clicking +// outside the surface, using the keyboard, or even locking the screen +// through closing the lid or a timeout. +// +// If the compositor denies the grab, the popup will be immediately +// dismissed. +// +// This request must be used in response to some sort of user action like a +// button press, key press, or touch down event. The serial number of the +// event should be passed as 'serial'. +// +// The parent of a grabbing popup must either be an xdg_toplevel surface or +// another xdg_popup with an explicit grab. If the parent is another +// xdg_popup it means that the popups are nested, with this popup now being +// the topmost popup. +// +// Nested popups must be destroyed in the reverse order they were created +// in, e.g. the only popup you are allowed to destroy at all times is the +// topmost one. +// +// When compositors choose to dismiss a popup, they may dismiss every +// nested grabbing popup as well. When a compositor dismisses popups, it +// will follow the same dismissing order as required from the client. +// +// The parent of a grabbing popup must either be another xdg_popup with an +// active explicit grab, or an xdg_popup or xdg_toplevel, if there are no +// explicit grabs already taken. +// +// If the topmost grabbing popup is destroyed, the grab will be returned to +// the parent of the popup, if that parent previously had an explicit grab. +// +// If the parent is a grabbing popup which has already been dismissed, this +// popup will be immediately dismissed. If the parent is a popup that did +// not take an explicit grab, an error will be raised. +// +// During a popup grab, the client owning the grab will receive pointer +// and touch events for all their surfaces as normal (similar to an +// "owner-events" grab in X11 parlance), while the top most grabbing popup +// will always have keyboard focus. +// +func (p *Popup) Grab(seat *wl.Seat, serial uint32) error { + return p.Context().SendRequest(p, 1, seat, serial) +} + +const ( + PopupErrorInvalidGrab = 0 +) From cc6a7589f4481827e62d9ab814b2c7a906d47645 Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Sat, 13 Mar 2021 10:09:20 +0700 Subject: [PATCH 09/11] Fix focus window issue on Gnome Activity --- src/ibus-bamboo/engine.go | 15 +++--- src/ibus-bamboo/engine_utils.go | 14 +++-- src/ibus-bamboo/gnome_introspect.go | 37 ------------- src/ibus-bamboo/gnome_introspector.go | 54 +++++++++++++++++++ .../{wl_introspect.go => wl_introspector.go} | 0 src/ibus-bamboo/x11.go | 12 ++--- .../{x11_introspect.c => x11_introspector.c} | 11 ++-- 7 files changed, 85 insertions(+), 58 deletions(-) delete mode 100644 src/ibus-bamboo/gnome_introspect.go create mode 100644 src/ibus-bamboo/gnome_introspector.go rename src/ibus-bamboo/{wl_introspect.go => wl_introspector.go} (100%) rename src/ibus-bamboo/{x11_introspect.c => x11_introspector.c} (95%) diff --git a/src/ibus-bamboo/engine.go b/src/ibus-bamboo/engine.go index a9156af4..c6240c39 100644 --- a/src/ibus-bamboo/engine.go +++ b/src/ibus-bamboo/engine.go @@ -72,7 +72,6 @@ Return: This function gets called whenever a key is pressed. */ func (e *IBusBambooEngine) ProcessKeyEvent(keyVal uint32, keyCode uint32, state uint32) (bool, *dbus.Error) { - e.checkWmClass() if e.checkInputMode(usIM) { if e.isInputModeLTOpened || keyVal == IBusOpenLookupTable { // return false, nil @@ -119,17 +118,21 @@ func (e *IBusBambooEngine) ProcessKeyEvent(keyVal uint32, keyCode uint32, state func (e *IBusBambooEngine) FocusIn() *dbus.Error { log.Print("FocusIn.") - fmt.Printf("WM_CLASS=(%s)\n", e.getWmClass()) - - e.checkWmClass() + var latestWm string + if isGnome && isGnomeOverviewVisible() { + latestWm = "" + } else { + latestWm = e.getLatestWmClass() + } + e.checkWmClass(latestWm) e.RegisterProperties(e.propList) e.RequireSurroundingText() + fmt.Printf("WM_CLASS=(%s)\n", e.getWmClass()) return nil } func (e *IBusBambooEngine) FocusOut() *dbus.Error { log.Print("FocusOut.") - // e.checkWmClass() return nil } @@ -144,13 +147,11 @@ func (e *IBusBambooEngine) Reset() *dbus.Error { func (e *IBusBambooEngine) Enable() *dbus.Error { fmt.Print("Enable.") e.RequireSurroundingText() - startInputWatching() return nil } func (e *IBusBambooEngine) Disable() *dbus.Error { fmt.Print("Disable.") - stopInputWatching() return nil } diff --git a/src/ibus-bamboo/engine_utils.go b/src/ibus-bamboo/engine_utils.go index aa1f828c..d69cd48d 100644 --- a/src/ibus-bamboo/engine_utils.go +++ b/src/ibus-bamboo/engine_utils.go @@ -131,9 +131,9 @@ func (e *IBusBambooEngine) resetBuffer() { } } -func (e *IBusBambooEngine) checkWmClass() { - if e.wmClasses != e.getWmClass() { - e.wmClasses = e.getWmClass() +func (e *IBusBambooEngine) checkWmClass(newId string) { + if e.wmClasses != newId { + e.wmClasses = newId e.resetBuffer() e.resetFakeBackspace() } @@ -238,7 +238,7 @@ func (e *IBusBambooEngine) openLookupTable() { } func (e *IBusBambooEngine) ltProcessKeyEvent(keyVal uint32, keyCode uint32, state uint32) (bool, *dbus.Error) { - var wmClasses = x11GetFocusWindowClass() + var wmClasses = e.getWmClass() //e.HideLookupTable() fmt.Printf("keyCode 0x%04x keyval 0x%04x | %c\n", keyCode, keyVal, rune(keyVal)) //e.HideAuxiliaryText() @@ -378,10 +378,14 @@ func (e *IBusBambooEngine) inBrowserList() bool { } func (e *IBusBambooEngine) getWmClass() string { + return e.wmClasses +} + +func (e *IBusBambooEngine) getLatestWmClass() string { var wmClass string if isWayland { if isGnome { - wmClass = gnomeGetFocusWindowClass() + wmClass, _ = gnomeGetFocusWindowClass() } else { wmClass = wlAppId } diff --git a/src/ibus-bamboo/gnome_introspect.go b/src/ibus-bamboo/gnome_introspect.go deleted file mode 100644 index f7068214..00000000 --- a/src/ibus-bamboo/gnome_introspect.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "github.com/godbus/dbus" -) - -func gnomeGetFocusWindowClass() string { - conn, err := dbus.SessionBus() - if err != nil { - panic(err) - } - defer conn.Close() - - js_code := ` - global._ib_current_window = () => { - var window_list = global.get_window_actors(); - var active_window_actor = window_list.find(window => window.meta_window.has_focus()); - var active_window = active_window_actor.get_meta_window(); - var vm_class = active_window.get_wm_class(); - var title = active_window.get_title(); - var result = vm_class; - return result; - } - ` - obj := conn.Object("org.gnome.Shell", "/org/gnome/Shell") - call := obj.Call("org.gnome.Shell.Eval", 0, js_code) - if call.Err != nil { - panic(call.Err) - } - var s string - var ok bool - err = obj.Call("org.gnome.Shell.Eval", 0, "global._ib_current_window()").Store(&ok, &s) - if (err != nil) { - panic(err) - } - return s -} diff --git a/src/ibus-bamboo/gnome_introspector.go b/src/ibus-bamboo/gnome_introspector.go new file mode 100644 index 00000000..8adf5b4c --- /dev/null +++ b/src/ibus-bamboo/gnome_introspector.go @@ -0,0 +1,54 @@ +package main + +import ( + "errors" + + "github.com/godbus/dbus" +) + +func gnomeGetFocusWindowClass() ( string, error ) { + conn, err := dbus.SessionBus() + var s string + if err != nil { + return s, err + } + defer func() { + if err = conn.Hello(); err == nil { + conn.Close() + } + }() + + js_code := "global.get_window_actors().find(window => window.meta_window.has_focus()).get_meta_window().get_wm_class()" + obj := conn.Object("org.gnome.Shell", "/org/gnome/Shell") + var ok bool + err = obj.Call("org.gnome.Shell.Eval", 0, js_code).Store(&ok, &s) + if !ok { + err = errors.New(s) + } + if (err != nil) { + return "", err + } + return s, nil +} + +func isGnomeOverviewVisible() ( bool ) { + conn, err := dbus.SessionBus() + if err != nil { + return false + } + defer func() { + if err = conn.Hello(); err == nil { + conn.Close() + } + }() + + js_code := "Main.overview.visible" + obj := conn.Object("org.gnome.Shell", "/org/gnome/Shell") + var visible string + var ok bool + err = obj.Call("org.gnome.Shell.Eval", 0, js_code).Store(&ok, &visible) + if !ok || err != nil { + return false + } + return visible == "true" +} diff --git a/src/ibus-bamboo/wl_introspect.go b/src/ibus-bamboo/wl_introspector.go similarity index 100% rename from src/ibus-bamboo/wl_introspect.go rename to src/ibus-bamboo/wl_introspector.go diff --git a/src/ibus-bamboo/x11.go b/src/ibus-bamboo/x11.go index 99038825..99c7b201 100644 --- a/src/ibus-bamboo/x11.go +++ b/src/ibus-bamboo/x11.go @@ -40,8 +40,8 @@ extern void x11SendShiftR(); extern void x11SendShiftLeft(int n, int r, int timeout); extern void setXIgnoreErrorHandler(); extern char* x11GetFocusWindowClass(); -extern void start_input_watching(); -extern void stop_input_watching(); +extern void x11StartWindowInspector(); +extern void x11StopWindowInspector(); */ import "C" import ( @@ -65,12 +65,12 @@ func mouse_click_handler() { var onMouseMove func() var onMouseClick func() -func startInputWatching() { - C.start_input_watching() +func x11StartWindowInspector() { + C.x11StartWindowInspector() } -func stopInputWatching() { - C.stop_input_watching() +func x11StopWindowInspector() { + C.x11StopWindowInspector() } func startMouseRecording() { diff --git a/src/ibus-bamboo/x11_introspect.c b/src/ibus-bamboo/x11_introspector.c similarity index 95% rename from src/ibus-bamboo/x11_introspect.c rename to src/ibus-bamboo/x11_introspector.c index a9344d91..c6bff7e8 100644 --- a/src/ibus-bamboo/x11_introspect.c +++ b/src/ibus-bamboo/x11_introspector.c @@ -97,7 +97,11 @@ char * x11GetFocusWindowClassByDpy(Display *display) { } char * x11GetFocusWindowClass() { - return text; + Display * dpy; + dpy = XOpenDisplay(NULL); + char * wm = x11GetFocusWindowClassByDpy(dpy); + XCloseDisplay(dpy); + return wm; } static int input_watching = 0; @@ -143,11 +147,12 @@ static void* thread_input_watching(void* data) } input_watching = 0; th_count--; + free(text); XCloseDisplay(dpy); return NULL; } -void start_input_watching() +void x11StartWindowInspector() { setbuf(stdout, NULL); setbuf(stderr, NULL); @@ -161,6 +166,6 @@ void start_input_watching() pthread_detach(th_input_watch); } -void stop_input_watching() { +void x11StopWindowInspector() { input_watching = 0; } From a5f82d7bb035c29af3bcf613d7e083e8e2fa0289 Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Sat, 13 Mar 2021 11:40:28 +0700 Subject: [PATCH 10/11] Fix wayland introspector --- src/github.com/dkolbly/wl/common.go | 4 +- src/github.com/dkolbly/wl/context.go | 11 +++-- src/ibus-bamboo/client.go | 11 +++-- src/ibus-bamboo/version.go | 2 +- src/ibus-bamboo/wl_introspector.go | 66 +++++++++++++++++++++++----- 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/github.com/dkolbly/wl/common.go b/src/github.com/dkolbly/wl/common.go index 308014d3..e741da3a 100644 --- a/src/github.com/dkolbly/wl/common.go +++ b/src/github.com/dkolbly/wl/common.go @@ -1,8 +1,6 @@ package wl -import ( - "context" -) +import "context" type ProxyId uint32 diff --git a/src/github.com/dkolbly/wl/context.go b/src/github.com/dkolbly/wl/context.go index 3ab3852c..c02234d0 100644 --- a/src/github.com/dkolbly/wl/context.go +++ b/src/github.com/dkolbly/wl/context.go @@ -27,10 +27,15 @@ type Context struct { func (ctx *Context) Register(proxy Proxy) { ctx.mu.Lock() defer ctx.mu.Unlock() - ctx.currentId += 1 - proxy.SetId(ctx.currentId) + pid := proxy.Id() proxy.SetContext(ctx) - ctx.objects[ctx.currentId] = proxy + if pid > 0 { + ctx.objects[pid] = proxy + } else { + ctx.currentId += 1 + proxy.SetId(ctx.currentId) + ctx.objects[ctx.currentId] = proxy + } } func (ctx *Context) lookupProxy(id ProxyId) Proxy { diff --git a/src/ibus-bamboo/client.go b/src/ibus-bamboo/client.go index e648d3f5..481d8f5b 100644 --- a/src/ibus-bamboo/client.go +++ b/src/ibus-bamboo/client.go @@ -7,6 +7,7 @@ package main import ( + "context" "sync" wl "github.com/dkolbly/wl" @@ -67,12 +68,13 @@ func (p *ZwlrForeignToplevelManagerV1) RemoveFinishedHandler(h ZwlrForeignToplev } } -func (p *ZwlrForeignToplevelManagerV1) Dispatch(event *wl.Event) { +func (p *ZwlrForeignToplevelManagerV1) Dispatch(c context.Context, event *wl.Event) { switch event.Opcode { case 0: if len(p.toplevelHandlers) > 0 { ev := ZwlrForeignToplevelManagerV1ToplevelEvent{} - ev.Toplevel = event.Proxy(p.Context()).(*ZwlrForeignToplevelHandleV1) + // ev.Toplevel = event.Proxy(p.Context()).(*ZwlrForeignToplevelHandleV1) + ev.Toplevel = NewZwlrForeignToplevelHandleV1(p.Context(), wl.ProxyId(event.Uint32())) p.mu.RLock() for _, h := range p.toplevelHandlers { h.HandleZwlrForeignToplevelManagerV1Toplevel(ev) @@ -339,7 +341,7 @@ func (p *ZwlrForeignToplevelHandleV1) RemoveParentHandler(h ZwlrForeignToplevelH } } -func (p *ZwlrForeignToplevelHandleV1) Dispatch(event *wl.Event) { +func (p *ZwlrForeignToplevelHandleV1) Dispatch(c context.Context, event *wl.Event) { switch event.Opcode { case 0: if len(p.titleHandlers) > 0 { @@ -435,8 +437,9 @@ type ZwlrForeignToplevelHandleV1 struct { parentHandlers []ZwlrForeignToplevelHandleV1ParentHandler } -func NewZwlrForeignToplevelHandleV1(ctx *wl.Context) *ZwlrForeignToplevelHandleV1 { +func NewZwlrForeignToplevelHandleV1(ctx *wl.Context, pid wl.ProxyId) *ZwlrForeignToplevelHandleV1 { ret := new(ZwlrForeignToplevelHandleV1) + ret.SetId(pid) ctx.Register(ret) return ret } diff --git a/src/ibus-bamboo/version.go b/src/ibus-bamboo/version.go index 2ef87c97..5c7a42e3 100644 --- a/src/ibus-bamboo/version.go +++ b/src/ibus-bamboo/version.go @@ -19,4 +19,4 @@ package main -const Version = "v0.6.9" +const Version = "v0.7.0" diff --git a/src/ibus-bamboo/wl_introspector.go b/src/ibus-bamboo/wl_introspector.go index de64d02e..99e25912 100644 --- a/src/ibus-bamboo/wl_introspector.go +++ b/src/ibus-bamboo/wl_introspector.go @@ -13,38 +13,65 @@ func wlGetFocusWindowClass() error { if err != nil { return fmt.Errorf("Connect to Wayland server failed %s", err) } + appIdChan := make(chan string, 10) + err = registerGlobals(display, appIdChan) + if err != nil { + return err + } + for { + select { + case wlAppId = <-appIdChan: + fmt.Println("wlAppId = ", wlAppId) + case display.Context().Dispatch() <- struct{}{}: + } + } + display.Context().Close() + return nil +} + +func registerGlobals(display *wl.Display, appIdChan chan string) error { registry, err := display.GetRegistry() if err != nil { return fmt.Errorf("Display.GetRegistry failed : %s", err) } - _, err = display.Sync() + + callback, err := display.Sync() if err != nil { return fmt.Errorf("Display.Sync failed %s", err) } - appIdChan := make(chan string, 10) + rgeChan := make(chan wl.RegistryGlobalEvent) rgeHandler := registrar{rgeChan} registry.AddGlobalHandler(rgeHandler) + + cdeChan := make(chan wl.CallbackDoneEvent) + cdeHandler := doner{cdeChan} + + callback.AddDoneHandler(cdeHandler) + adHandler := appIdHandler{appIdChan, false} +loop: for { select { case ev := <-rgeChan: - if err := registerInterface(registry, ev, display.Context(), appIdChan); err != nil { + if err := registerInterface(registry, ev, display.Context(), &adHandler); err != nil { return err } - case wlAppId = <-appIdChan: case display.Context().Dispatch() <- struct{}{}: + case <-cdeChan: + break loop } } + registry.RemoveGlobalHandler(rgeHandler) - display.Context().Close() + callback.RemoveDoneHandler(cdeHandler) return nil } -func registerInterface(registry *wl.Registry, ev wl.RegistryGlobalEvent, ctx *wl.Context, appIdChan chan string) error { +func registerInterface(registry *wl.Registry, ev wl.RegistryGlobalEvent, ctx *wl.Context, adHandler *appIdHandler) error { switch ev.Interface { case "zwlr_foreign_toplevel_manager_v1": manager := NewZwlrForeignToplevelManagerV1(ctx) - manager.AddToplevelHandler(toplevelHandlers{appIdChan}) + manager.AddToplevelHandler(toplevelHandlers{adHandler}) err := registry.Bind(ev.Name, ev.Interface, ev.Version, manager) if err != nil { return fmt.Errorf("Unable to bind ZwlrForeignToplevelManagerV1 interface: %s", err) @@ -53,6 +80,14 @@ func registerInterface(registry *wl.Registry, ev wl.RegistryGlobalEvent, ctx *wl return nil } +type doner struct { + ch chan wl.CallbackDoneEvent +} + +func (d doner) HandleCallbackDone(ev wl.CallbackDoneEvent) { + d.ch <- ev +} + type registrar struct { ch chan wl.RegistryGlobalEvent } @@ -61,19 +96,28 @@ func (r registrar) HandleRegistryGlobal(ev wl.RegistryGlobalEvent) { r.ch <- ev } type toplevelHandlers struct { - ch chan string + adHandler *appIdHandler } func (t toplevelHandlers) HandleZwlrForeignToplevelManagerV1Toplevel(ev ZwlrForeignToplevelManagerV1ToplevelEvent) { - ev.Toplevel.AddAppIdHandler(appIdHandler{t.ch}) + ev.Toplevel.AddAppIdHandler(t.adHandler) + ev.Toplevel.AddDoneHandler(t.adHandler) } type appIdHandler struct { ch chan string + isAppIdUpdated bool } -func (a appIdHandler) HandleZwlrForeignToplevelHandleV1AppId(ev ZwlrForeignToplevelHandleV1AppIdEvent) { - print(ev.AppId) +func (a *appIdHandler) HandleZwlrForeignToplevelHandleV1AppId(ev ZwlrForeignToplevelHandleV1AppIdEvent) { a.ch <- ev.AppId + a.isAppIdUpdated = true +} + +func (a *appIdHandler) HandleZwlrForeignToplevelHandleV1Done(ev ZwlrForeignToplevelHandleV1DoneEvent) { + if !a.isAppIdUpdated { + a.ch <- "" + } + a.isAppIdUpdated = false } From ef5e9df553ad5743d4dabbcddab40a635c5ea6ad Mon Sep 17 00:00:00 2001 From: luongthanhlam Date: Sun, 14 Mar 2021 20:24:50 +0700 Subject: [PATCH 11/11] Bump version to v0.7.0 --- Makefile | 2 +- archlinux/PKGBUILD-git | 2 +- archlinux/PKGBUILD-release | 2 +- bamboo.xml | 2 +- debian/changelog | 6 ++++++ debian/control | 2 +- ibus-bamboo.dsc | 4 ++-- ibus-bamboo.spec | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index c41eb042..d37c9411 100755 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ PREFIX=/usr engine_name=bamboo ibus_e_name=ibus-engine-$(engine_name) pkg_name=ibus-$(engine_name) -version=0.6.9 +version=0.7.0 engine_dir=$(PREFIX)/share/$(pkg_name) ibus_dir=$(PREFIX)/share/ibus diff --git a/archlinux/PKGBUILD-git b/archlinux/PKGBUILD-git index c0ad66a1..603cbd53 100644 --- a/archlinux/PKGBUILD-git +++ b/archlinux/PKGBUILD-git @@ -17,7 +17,7 @@ # # Maintainer: Luong Thanh Lam pkgname=ibus-bamboo-git -pkgver=0.6.9 +pkgver=0.7.0 pkgrel=1 pkgdesc='A Vietnamese IME for IBus' arch=(any) diff --git a/archlinux/PKGBUILD-release b/archlinux/PKGBUILD-release index a4d308c0..002f7143 100644 --- a/archlinux/PKGBUILD-release +++ b/archlinux/PKGBUILD-release @@ -17,7 +17,7 @@ # # Maintainer: Luong Thanh Lam pkgname=ibus-bamboo -pkgver=0.6.9 +pkgver=0.7.0 pkgrel=1 pkgdesc='A Vietnamese IME for IBus' arch=(any) diff --git a/bamboo.xml b/bamboo.xml index d6e0cf75..bf627444 100644 --- a/bamboo.xml +++ b/bamboo.xml @@ -22,7 +22,7 @@ org.freedesktop.IBus.bamboo Vietnamese input engine for IBus /usr/lib/ibus-engine-bamboo --ibus - 0.6.9 + 0.7.0 Luong Thanh Lam <ltlam93@gmail.com> GPLv3 https://github.com/BambooEngine/ibus-bamboo/ diff --git a/debian/changelog b/debian/changelog index 6761aaaf..ba52424c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +ibus-bamboo (0.7.0-1) stable; urgency=medium + + * New upstream release + + -- Luong Thanh Lam Sun, 14 Mar 2021 09:21:06 +0700 + ibus-bamboo (0.6.8-2) stable; urgency=medium * New upstream release diff --git a/debian/control b/debian/control index 65c906d9..7e928f66 100644 --- a/debian/control +++ b/debian/control @@ -20,7 +20,7 @@ Section: utils Priority: extra Maintainer: Luong Thanh Lam Build-Depends: debhelper, golang, libx11-dev, libxtst-dev -Standards-Version: 0.6.9 +Standards-Version: 0.7.0 Homepage: https://github.com/BambooEngine/ibus-bamboo Package: ibus-bamboo diff --git a/ibus-bamboo.dsc b/ibus-bamboo.dsc index 1880e354..7c06c473 100644 --- a/ibus-bamboo.dsc +++ b/ibus-bamboo.dsc @@ -4,12 +4,12 @@ Format: 1.0 Source: ibus-bamboo Binary: ibus-bamboo Architecture: any -Version: 0.6.9-0 +Version: 0.7.0-0 Maintainer: Luong Thanh Lam Homepage: https://github.com/BambooEngine/ibus-bamboo Build-Depends: debhelper, golang, libx11-dev, libxt-dev, libxtst-dev Files: - 0 0 ibus-bamboo-0.6.9.tar.gz + 0 0 ibus-bamboo-0.7.0.tar.gz -----BEGIN PGP SIGNATURE----- diff --git a/ibus-bamboo.spec b/ibus-bamboo.spec index 276eb5a6..1a107c94 100644 --- a/ibus-bamboo.spec +++ b/ibus-bamboo.spec @@ -4,7 +4,7 @@ %define ibus_comp_dir /usr/share/ibus/component Name: ibus-bamboo -Version: 0.6.9 +Version: 0.7.0 Release: 1%{?dist} Summary: A Vietnamese input method for IBus