file chooser dialog events implementation.

This commit is contained in:
Miroslav Šedivý 2021-01-19 21:01:31 +01:00
parent 22d31e871c
commit d4e0287eb5
13 changed files with 129 additions and 157 deletions

View File

@ -97,7 +97,7 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
defer r.MultipartForm.RemoveAll()
if !h.desktop.IsFileChooserDialogOpen() {
if !h.desktop.IsFileChooserDialogOpened() {
utils.HttpBadRequest(w, "Open file chooser dialog first.")
return
}
@ -149,15 +149,11 @@ func (h *RoomHandler) uploadDialogPost(w http.ResponseWriter, r *http.Request) {
}
func (h *RoomHandler) uploadDialogClose(w http.ResponseWriter, r *http.Request) {
if !h.desktop.IsFileChooserDialogOpen() {
if !h.desktop.IsFileChooserDialogOpened() {
utils.HttpBadRequest(w, "File chooser dialog is not open.")
return
}
if !h.desktop.CloseFileChooserDialog() {
utils.HttpInternalServerError(w, "Unable to close file chooser dialog.")
return
}
h.desktop.CloseFileChooserDialog()
utils.HttpSuccess(w)
}

View File

@ -1,52 +1,9 @@
package desktop
import (
"time"
"os/exec"
)
var (
file_chooser_dialog_open = false
)
func (manager *DesktopManagerCtx) fileChooserDialogStart() {
if manager.IsFileChooserDialogOpen() {
manager.CloseFileChooserDialog()
}
manager.OnWindowCreated(func(window uint32, name string, role string) {
if role != "GtkFileChooserDialog" {
return
}
// TODO: Implement, call event.
file_chooser_dialog_open = true
manager.logger.Debug().
Uint32("window", window).
Msg("GtkFileChooserDialog has been opened")
})
manager.OnWindowConfigured(func(window uint32, name string, role string) {
if role != "GtkFileChooserDialog" {
return
}
go func(){
// TOOD: Refactor.
manager.PutWindowBelow(window)
// Because first dialog is not put properly to background
time.Sleep(500 * time.Millisecond)
manager.PutWindowBelow(window)
}()
manager.logger.Debug().
Uint32("window", window).
Msg("GtkFileChooserDialog has been put below main window")
})
}
func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
mu.Lock()
defer mu.Unlock()
@ -54,7 +11,7 @@ func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
// TOOD: Use native API.
cmd := exec.Command(
"xdotool",
"search", "--name", "Open", "windowfocus",
"search", "--name", "Open File", "windowfocus",
"sleep", "0.2",
"key", "--clearmodifiers", "ctrl+l",
"type", "--args", "1", uri + "//",
@ -66,43 +23,36 @@ func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
"sleep", "0.3",
)
// TODO: Implement, call event.
file_chooser_dialog_open = false
_, err := cmd.Output()
return err
}
func (manager *DesktopManagerCtx) CloseFileChooserDialog() bool {
func (manager *DesktopManagerCtx) CloseFileChooserDialog() {
for i := 0; i < 5; i++ {
if !manager.IsFileChooserDialogOpened() {
return
}
// TOOD: Use native API.
mu.Lock()
exec.Command(
"xdotool",
"search", "--name", "Open", "windowfocus",
"search", "--name", "Open File", "windowfocus",
"sleep", "0.2",
"key", "--clearmodifiers", "alt+F4",
).Output()
mu.Unlock()
if !manager.IsFileChooserDialogOpen() {
// TODO: Implement, call event.
file_chooser_dialog_open = false
return true
}
}
return false
}
func (manager *DesktopManagerCtx) IsFileChooserDialogOpen() bool {
func (manager *DesktopManagerCtx) IsFileChooserDialogOpened() bool {
mu.Lock()
defer mu.Unlock()
// TOOD: Use native API.
cmd := exec.Command(
"xdotool",
"search", "--name", "Open", "windowfocus",
"search", "--name", "Open File",
)
_, err := cmd.Output()

View File

@ -53,7 +53,8 @@ func (manager *DesktopManagerCtx) Start() {
go xevent.EventLoop(manager.display)
go manager.fileChooserDialogStart()
// In case it was opened
go manager.CloseFileChooserDialog()
manager.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
manager.logger.Warn().

View File

@ -12,12 +12,12 @@ func (manager *DesktopManagerCtx) OnClipboardUpdated(listener func()) {
xevent.OnClipboardUpdated(listener)
}
func (manager *DesktopManagerCtx) OnWindowCreated(listener func(window uint32, name string, role string)) {
xevent.OnWindowCreated(listener)
func (manager *DesktopManagerCtx) OnFileChooserDialogOpened(listener func()) {
xevent.OnFileChooserDialogOpened(listener)
}
func (manager *DesktopManagerCtx) OnWindowConfigured(listener func(window uint32, name string, role string)) {
xevent.OnWindowConfigured(listener)
func (manager *DesktopManagerCtx) OnFileChooserDialogClosed(listener func()) {
xevent.OnFileChooserDialogClosed(listener)
}
func (manager *DesktopManagerCtx) OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8)) {

View File

@ -64,7 +64,7 @@ void XEventLoop(char *name) {
XTextProperty role;
XGetTextProperty(display, window, &role, WM_WINDOW_ROLE);
goXEventWindowCreated(window, name, role.value);
goXEventCreateNotify(window, name, role.value);
XFree(name);
continue;
}
@ -79,11 +79,60 @@ void XEventLoop(char *name) {
XTextProperty role;
XGetTextProperty(display, window, &role, WM_WINDOW_ROLE);
goXEventWindowConfigured(window, name, role.value);
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);
}

View File

@ -8,12 +8,14 @@ package xevent
import "C"
import (
"time"
"unsafe"
"github.com/kataras/go-events"
)
var emmiter events.EventEmmiter
var file_chooser_dialog_window uint32 = 0
func init() {
emmiter = events.New()
@ -38,15 +40,15 @@ func OnClipboardUpdated(listener func()) {
})
}
func OnWindowCreated(listener func(window uint32, name string, role string)) {
emmiter.On("window-created", func(payload ...interface{}) {
listener(payload[0].(uint32), payload[1].(string), payload[2].(string))
func OnFileChooserDialogOpened(listener func()) {
emmiter.On("file-chooser-dialog-opened", func(payload ...interface{}) {
listener()
})
}
func OnWindowConfigured(listener func(window uint32, name string, role string)) {
emmiter.On("window-configured", func(payload ...interface{}) {
listener(payload[0].(uint32), payload[1].(string), payload[2].(string))
func OnFileChooserDialogClosed(listener func()) {
emmiter.On("file-chooser-dialog-closed", func(payload ...interface{}) {
listener()
})
}
@ -66,14 +68,38 @@ func goXEventClipboardUpdated() {
emmiter.Emit("clipboard-updated")
}
//export goXEventWindowCreated
func goXEventWindowCreated(window C.Window, name *C.char, role *C.char) {
emmiter.Emit("window-created", uint32(window), C.GoString(name), C.GoString(role))
//export goXEventCreateNotify
func goXEventCreateNotify(window C.Window, nameUnsafe *C.char, roleUnsafe *C.char) {
role := C.GoString(roleUnsafe)
if role != "GtkFileChooserDialog" {
return
}
file_chooser_dialog_window = uint32(window)
emmiter.Emit("file-chooser-dialog-opened")
}
//export goXEventWindowConfigured
func goXEventWindowConfigured(window C.Window, name *C.char, role *C.char) {
emmiter.Emit("window-configured", uint32(window), C.GoString(name), C.GoString(role))
//export goXEventConfigureNotify
func goXEventConfigureNotify(display *C.Display, window C.Window, nameUnsafe *C.char, roleUnsafe *C.char) {
role := C.GoString(roleUnsafe)
if role != "GtkFileChooserDialog" {
return
}
C.XFileChooserHide(display, window)
// Because first dialog is not put properly to background
time.Sleep(10 * time.Millisecond)
C.XFileChooserHide(display, window)
}
//export goXEventUnmapNotify
func goXEventUnmapNotify(window C.Window) {
if uint32(window) != file_chooser_dialog_window {
return
}
emmiter.Emit("file-chooser-dialog-closed")
}
//export goXEventError

View File

@ -5,13 +5,17 @@
#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 goXEventWindowCreated(Window window, char *name, char *role);
extern void goXEventWindowConfigured(Window window, char *name, char *role);
extern void goXEventCreateNotify(Window window, char *name, char *role);
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);

View File

@ -113,7 +113,3 @@ func (manager *DesktopManagerCtx) GetKeyboardModifiers() types.KeyboardModifiers
func (manager *DesktopManagerCtx) GetCursorImage() *types.CursorImage {
return xorg.GetCursorImage()
}
func (manager *DesktopManagerCtx) PutWindowBelow(window uint32) {
xorg.PutWindowBelow(window)
}

View File

@ -136,46 +136,3 @@ XFixesCursorImage *XGetCursorImage(void) {
Display *display = getXDisplay();
return XFixesGetCursorImage(display);
}
void XPutWindowBelow(Window window) {
Display *display = getXDisplay();
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);
}

View File

@ -244,13 +244,6 @@ func GetCursorImage() *types.CursorImage {
}
}
func PutWindowBelow(window uint32) {
mu.Lock()
defer mu.Unlock()
C.XPutWindowBelow(C.Window(window))
}
//export goCreateScreenSize
func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) {
ScreenConfigurations[int(index)] = types.ScreenConfiguration{

View File

@ -6,7 +6,6 @@
#include <X11/extensions/XTest.h>
#include <X11/extensions/Xfixes.h>
#include <stdlib.h>
#include <string.h>
extern void goCreateScreenSize(int index, int width, int height, int mwidth, int mheight);
extern void goSetScreenRates(int index, int rate_index, short rate);
@ -28,5 +27,3 @@ short XGetScreenRate();
void XSetKeyboardModifier(int mod, int on);
char XGetKeyboardModifiers();
XFixesCursorImage *XGetCursorImage(void);
void XPutWindowBelow(Window window);

View File

@ -57,8 +57,8 @@ type DesktopManager interface {
// xevent
OnCursorChanged(listener func(serial uint64))
OnClipboardUpdated(listener func())
OnWindowCreated(listener func(window uint32, name string, role string))
OnWindowConfigured(listener func(window uint32, name string, role string))
OnFileChooserDialogOpened(listener func())
OnFileChooserDialogClosed(listener func())
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
// clipboard
@ -70,6 +70,6 @@ type DesktopManager interface {
// filechooser
HandleFileChooserDialog(uri string) error
CloseFileChooserDialog() bool
IsFileChooserDialogOpen() bool
CloseFileChooserDialog()
IsFileChooserDialogOpened() bool
}

View File

@ -134,13 +134,16 @@ func (ws *WebSocketManagerCtx) Start() {
}
})
ws.desktop.OnWindowCreated(func(window uint32, name string, role string) {
ws.desktop.OnFileChooserDialogOpened(func() {
// TODO: Implement.
ws.logger.Info().
Uint32("window", window).
Str("name", name).
Str("role", role).
Msg("created new window")
Msg("FileChooserDialog opened")
})
ws.desktop.OnFileChooserDialogClosed(func() {
// TODO: Implement.
ws.logger.Info().
Msg("FileChooserDialog closed")
})
}