add xevent.
This commit is contained in:
parent
2afc356911
commit
e9912ea87f
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"m1k1o/neko/internal/config"
|
"m1k1o/neko/internal/config"
|
||||||
|
"m1k1o/neko/internal/desktop/xevent"
|
||||||
"m1k1o/neko/internal/desktop/xorg"
|
"m1k1o/neko/internal/desktop/xorg"
|
||||||
"m1k1o/neko/internal/types"
|
"m1k1o/neko/internal/types"
|
||||||
|
|
||||||
@ -40,6 +41,17 @@ func (manager *DesktopManagerCtx) Start() {
|
|||||||
Str("screen_size", fmt.Sprintf("%dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)).
|
Str("screen_size", fmt.Sprintf("%dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)).
|
||||||
Msgf("setting initial screen size")
|
Msgf("setting initial screen size")
|
||||||
|
|
||||||
|
go xevent.EventLoop(manager.config.Display)
|
||||||
|
|
||||||
|
manager.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
|
||||||
|
manager.logger.Warn().
|
||||||
|
Uint8("error_code", error_code).
|
||||||
|
Str("message", message).
|
||||||
|
Uint8("request_code", request_code).
|
||||||
|
Uint8("minor_code", minor_code).
|
||||||
|
Msg("X event error occurred")
|
||||||
|
})
|
||||||
|
|
||||||
manager.wg.Add(1)
|
manager.wg.Add(1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
33
server/internal/desktop/xevent.go
Normal file
33
server/internal/desktop/xevent.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package desktop
|
||||||
|
|
||||||
|
import "m1k1o/neko/internal/desktop/xevent"
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnCursorChanged(listener func(serial uint64)) {
|
||||||
|
xevent.Emmiter.On("cursor-changed", func(payload ...any) {
|
||||||
|
listener(payload[0].(uint64))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnClipboardUpdated(listener func()) {
|
||||||
|
xevent.Emmiter.On("clipboard-updated", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnFileChooserDialogOpened(listener func()) {
|
||||||
|
xevent.Emmiter.On("file-chooser-dialog-opened", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnFileChooserDialogClosed(listener func()) {
|
||||||
|
xevent.Emmiter.On("file-chooser-dialog-closed", func(payload ...any) {
|
||||||
|
listener()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *DesktopManagerCtx) OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8)) {
|
||||||
|
xevent.Emmiter.On("event-error", func(payload ...any) {
|
||||||
|
listener(payload[0].(uint8), payload[1].(string), payload[2].(uint8), payload[3].(uint8))
|
||||||
|
})
|
||||||
|
}
|
123
server/internal/desktop/xevent/xevent.c
Normal file
123
server/internal/desktop/xevent/xevent.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "xevent.h"
|
||||||
|
|
||||||
|
static int XEventError(Display *display, XErrorEvent *event) {
|
||||||
|
char message[100];
|
||||||
|
|
||||||
|
int error;
|
||||||
|
error = XGetErrorText(display, event->error_code, message, sizeof(message));
|
||||||
|
if (error) {
|
||||||
|
goXEventError(event, "Could not get error message.");
|
||||||
|
} else {
|
||||||
|
goXEventError(event, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XEventLoop(char *name) {
|
||||||
|
Display *display = XOpenDisplay(name);
|
||||||
|
Window root = RootWindow(display, 0);
|
||||||
|
|
||||||
|
int xfixes_event_base, xfixes_error_base;
|
||||||
|
if (!XFixesQueryExtension(display, &xfixes_event_base, &xfixes_error_base)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Atom WM_WINDOW_ROLE = XInternAtom(display, "WM_WINDOW_ROLE", 1);
|
||||||
|
Atom XA_CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
|
||||||
|
XFixesSelectSelectionInput(display, root, XA_CLIPBOARD, XFixesSetSelectionOwnerNotifyMask);
|
||||||
|
XFixesSelectCursorInput(display, root, XFixesDisplayCursorNotifyMask);
|
||||||
|
XSelectInput(display, root, SubstructureNotifyMask);
|
||||||
|
|
||||||
|
XSync(display, 0);
|
||||||
|
XSetErrorHandler(XEventError);
|
||||||
|
|
||||||
|
while (goXEventActive()) {
|
||||||
|
XEvent event;
|
||||||
|
XNextEvent(display, &event);
|
||||||
|
|
||||||
|
// XFixesDisplayCursorNotify
|
||||||
|
if (event.type == xfixes_event_base + 1) {
|
||||||
|
XFixesCursorNotifyEvent notifyEvent = *((XFixesCursorNotifyEvent *) &event);
|
||||||
|
if (notifyEvent.subtype == XFixesDisplayCursorNotify) {
|
||||||
|
goXEventCursorChanged(notifyEvent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XFixesSelectionNotifyEvent
|
||||||
|
if (event.type == xfixes_event_base + XFixesSelectionNotify) {
|
||||||
|
XFixesSelectionNotifyEvent notifyEvent = *((XFixesSelectionNotifyEvent *) &event);
|
||||||
|
if (notifyEvent.subtype == XFixesSetSelectionOwnerNotify && notifyEvent.selection == XA_CLIPBOARD) {
|
||||||
|
goXEventClipboardUpdated();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureNotify
|
||||||
|
if (event.type == ConfigureNotify) {
|
||||||
|
Window window = event.xconfigure.window;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
XFetchName(display, window, &name);
|
||||||
|
|
||||||
|
XTextProperty role;
|
||||||
|
XGetTextProperty(display, window, &role, WM_WINDOW_ROLE);
|
||||||
|
|
||||||
|
goXEventConfigureNotify(display, window, name, role.value);
|
||||||
|
XFree(name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmapNotify
|
||||||
|
if (event.type == UnmapNotify) {
|
||||||
|
Window window = event.xunmap.window;
|
||||||
|
goXEventUnmapNotify(window);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XCloseDisplay(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XFileChooserHide(Display *display, Window window) {
|
||||||
|
Window root = RootWindow(display, 0);
|
||||||
|
|
||||||
|
// The WM_TRANSIENT_FOR property is defined by the [ICCCM] for managed windows.
|
||||||
|
// This specification extends the use of the property to override-redirect windows.
|
||||||
|
// If an override-redirect is a pop-up on behalf of another window, then the Client
|
||||||
|
// SHOULD set WM_TRANSIENT_FOR on the override-redirect to this other window.
|
||||||
|
//
|
||||||
|
// As an example, a Client should set WM_TRANSIENT_FOR on dropdown menus to the
|
||||||
|
// toplevel application window that contains the menubar.
|
||||||
|
|
||||||
|
// Remove WM_TRANSIENT_FOR
|
||||||
|
Atom WM_TRANSIENT_FOR = XInternAtom(display, "WM_TRANSIENT_FOR", 0);
|
||||||
|
XDeleteProperty(display, window, WM_TRANSIENT_FOR);
|
||||||
|
|
||||||
|
// Add _NET_WM_STATE_BELOW
|
||||||
|
XClientMessageEvent clientMessageEvent;
|
||||||
|
memset(&clientMessageEvent, 0, sizeof(clientMessageEvent));
|
||||||
|
|
||||||
|
// window = the respective client window
|
||||||
|
// message_type = _NET_WM_STATE
|
||||||
|
// format = 32
|
||||||
|
// data.l[0] = the action, as listed below
|
||||||
|
// _NET_WM_STATE_REMOVE 0 // remove/unset property
|
||||||
|
// _NET_WM_STATE_ADD 1 // add/set property
|
||||||
|
// _NET_WM_STATE_TOGGLE 2 // toggle property
|
||||||
|
// data.l[1] = first property to alter
|
||||||
|
// data.l[2] = second property to alter
|
||||||
|
// data.l[3] = source indication
|
||||||
|
// other data.l[] elements = 0
|
||||||
|
|
||||||
|
clientMessageEvent.type = ClientMessage;
|
||||||
|
clientMessageEvent.window = window;
|
||||||
|
clientMessageEvent.message_type = XInternAtom(display, "_NET_WM_STATE", 0);
|
||||||
|
clientMessageEvent.format = 32;
|
||||||
|
clientMessageEvent.data.l[0] = 1;
|
||||||
|
clientMessageEvent.data.l[1] = XInternAtom(display, "_NET_WM_STATE_BELOW", 0);
|
||||||
|
clientMessageEvent.data.l[3] = 1;
|
||||||
|
|
||||||
|
XSendEvent(display, root, 0, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&clientMessageEvent);
|
||||||
|
}
|
84
server/internal/desktop/xevent/xevent.go
Normal file
84
server/internal/desktop/xevent/xevent.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package xevent
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo LDFLAGS: -lX11 -lXfixes
|
||||||
|
|
||||||
|
#include "xevent.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/kataras/go-events"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Emmiter events.EventEmmiter
|
||||||
|
var file_chooser_dialog_window uint32 = 0
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Emmiter = events.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventLoop(display string) {
|
||||||
|
displayUnsafe := C.CString(display)
|
||||||
|
defer C.free(unsafe.Pointer(displayUnsafe))
|
||||||
|
|
||||||
|
C.XEventLoop(displayUnsafe)
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventCursorChanged
|
||||||
|
func goXEventCursorChanged(event C.XFixesCursorNotifyEvent) {
|
||||||
|
Emmiter.Emit("cursor-changed", uint64(event.cursor_serial))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventClipboardUpdated
|
||||||
|
func goXEventClipboardUpdated() {
|
||||||
|
Emmiter.Emit("clipboard-updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventConfigureNotify
|
||||||
|
func goXEventConfigureNotify(display *C.Display, window C.Window, name *C.char, role *C.char) {
|
||||||
|
if C.GoString(role) != "GtkFileChooserDialog" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Refactor. Right now processing of this dialog relies on identifying
|
||||||
|
// via its name. When that changes to role, this condition should be removed.
|
||||||
|
if !strings.HasPrefix(C.GoString(name), "Open File") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
C.XFileChooserHide(display, window)
|
||||||
|
|
||||||
|
// Because first dialog is not put properly to background
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
C.XFileChooserHide(display, window)
|
||||||
|
|
||||||
|
if file_chooser_dialog_window == 0 {
|
||||||
|
file_chooser_dialog_window = uint32(window)
|
||||||
|
Emmiter.Emit("file-chooser-dialog-opened")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventUnmapNotify
|
||||||
|
func goXEventUnmapNotify(window C.Window) {
|
||||||
|
if uint32(window) != file_chooser_dialog_window {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file_chooser_dialog_window = 0
|
||||||
|
Emmiter.Emit("file-chooser-dialog-closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventError
|
||||||
|
func goXEventError(event *C.XErrorEvent, message *C.char) {
|
||||||
|
Emmiter.Emit("event-error", uint8(event.error_code), C.GoString(message), uint8(event.request_code), uint8(event.minor_code))
|
||||||
|
}
|
||||||
|
|
||||||
|
//export goXEventActive
|
||||||
|
func goXEventActive() C.int {
|
||||||
|
return C.int(1)
|
||||||
|
}
|
20
server/internal/desktop/xevent/xevent.h
Normal file
20
server/internal/desktop/xevent/xevent.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/Xfixes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
extern void goXEventCursorChanged(XFixesCursorNotifyEvent event);
|
||||||
|
extern void goXEventClipboardUpdated();
|
||||||
|
extern void goXEventConfigureNotify(Display *display, Window window, char *name, char *role);
|
||||||
|
extern void goXEventUnmapNotify(Window window);
|
||||||
|
extern void goXEventError(XErrorEvent *event, char *message);
|
||||||
|
extern int goXEventActive();
|
||||||
|
|
||||||
|
static int XEventError(Display *display, XErrorEvent *event);
|
||||||
|
void XEventLoop(char *display);
|
||||||
|
|
||||||
|
void XFileChooserHide(Display *display, Window window);
|
@ -61,4 +61,11 @@ type DesktopManager interface {
|
|||||||
GetKeyboardModifiers() KeyboardModifiers
|
GetKeyboardModifiers() KeyboardModifiers
|
||||||
GetCursorImage() *CursorImage
|
GetCursorImage() *CursorImage
|
||||||
GetScreenshotImage() *image.RGBA
|
GetScreenshotImage() *image.RGBA
|
||||||
|
|
||||||
|
// xevent
|
||||||
|
OnCursorChanged(listener func(serial uint64))
|
||||||
|
OnClipboardUpdated(listener func())
|
||||||
|
OnFileChooserDialogOpened(listener func())
|
||||||
|
OnFileChooserDialogClosed(listener func())
|
||||||
|
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user