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() defer r.MultipartForm.RemoveAll()
if !h.desktop.IsFileChooserDialogOpen() { if !h.desktop.IsFileChooserDialogOpened() {
utils.HttpBadRequest(w, "Open file chooser dialog first.") utils.HttpBadRequest(w, "Open file chooser dialog first.")
return 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) { 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.") utils.HttpBadRequest(w, "File chooser dialog is not open.")
return return
} }
if !h.desktop.CloseFileChooserDialog() { h.desktop.CloseFileChooserDialog()
utils.HttpInternalServerError(w, "Unable to close file chooser dialog.")
return
}
utils.HttpSuccess(w) utils.HttpSuccess(w)
} }

View File

@ -1,52 +1,9 @@
package desktop package desktop
import ( import (
"time"
"os/exec" "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 { func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
@ -54,7 +11,7 @@ func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
// TOOD: Use native API. // TOOD: Use native API.
cmd := exec.Command( cmd := exec.Command(
"xdotool", "xdotool",
"search", "--name", "Open", "windowfocus", "search", "--name", "Open File", "windowfocus",
"sleep", "0.2", "sleep", "0.2",
"key", "--clearmodifiers", "ctrl+l", "key", "--clearmodifiers", "ctrl+l",
"type", "--args", "1", uri + "//", "type", "--args", "1", uri + "//",
@ -66,43 +23,36 @@ func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
"sleep", "0.3", "sleep", "0.3",
) )
// TODO: Implement, call event.
file_chooser_dialog_open = false
_, err := cmd.Output() _, err := cmd.Output()
return err return err
} }
func (manager *DesktopManagerCtx) CloseFileChooserDialog() bool { func (manager *DesktopManagerCtx) CloseFileChooserDialog() {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
if !manager.IsFileChooserDialogOpened() {
return
}
// TOOD: Use native API. // TOOD: Use native API.
mu.Lock() mu.Lock()
exec.Command( exec.Command(
"xdotool", "xdotool",
"search", "--name", "Open", "windowfocus", "search", "--name", "Open File", "windowfocus",
"sleep", "0.2", "sleep", "0.2",
"key", "--clearmodifiers", "alt+F4", "key", "--clearmodifiers", "alt+F4",
).Output() ).Output()
mu.Unlock() 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() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
// TOOD: Use native API. // TOOD: Use native API.
cmd := exec.Command( cmd := exec.Command(
"xdotool", "xdotool",
"search", "--name", "Open", "windowfocus", "search", "--name", "Open File",
) )
_, err := cmd.Output() _, err := cmd.Output()

View File

@ -53,7 +53,8 @@ func (manager *DesktopManagerCtx) Start() {
go xevent.EventLoop(manager.display) 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.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
manager.logger.Warn(). manager.logger.Warn().

View File

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

View File

@ -60,11 +60,11 @@ void XEventLoop(char *name) {
char *name; char *name;
XFetchName(display, window, &name); XFetchName(display, window, &name);
XTextProperty role; XTextProperty role;
XGetTextProperty(display, window, &role, WM_WINDOW_ROLE); XGetTextProperty(display, window, &role, WM_WINDOW_ROLE);
goXEventWindowCreated(window, name, role.value); goXEventCreateNotify(window, name, role.value);
XFree(name); XFree(name);
continue; continue;
} }
@ -75,15 +75,64 @@ void XEventLoop(char *name) {
char *name; char *name;
XFetchName(display, window, &name); XFetchName(display, window, &name);
XTextProperty role; XTextProperty role;
XGetTextProperty(display, window, &role, WM_WINDOW_ROLE); XGetTextProperty(display, window, &role, WM_WINDOW_ROLE);
goXEventWindowConfigured(window, name, role.value); goXEventConfigureNotify(display, window, name, role.value);
XFree(name); XFree(name);
continue; continue;
} }
// UnmapNotify
if (event.type == UnmapNotify) {
Window window = event.xunmap.window;
goXEventUnmapNotify(window);
continue;
}
} }
XCloseDisplay(display); 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 "C"
import ( import (
"time"
"unsafe" "unsafe"
"github.com/kataras/go-events" "github.com/kataras/go-events"
) )
var emmiter events.EventEmmiter var emmiter events.EventEmmiter
var file_chooser_dialog_window uint32 = 0
func init() { func init() {
emmiter = events.New() emmiter = events.New()
@ -38,15 +40,15 @@ func OnClipboardUpdated(listener func()) {
}) })
} }
func OnWindowCreated(listener func(window uint32, name string, role string)) { func OnFileChooserDialogOpened(listener func()) {
emmiter.On("window-created", func(payload ...interface{}) { emmiter.On("file-chooser-dialog-opened", func(payload ...interface{}) {
listener(payload[0].(uint32), payload[1].(string), payload[2].(string)) listener()
}) })
} }
func OnWindowConfigured(listener func(window uint32, name string, role string)) { func OnFileChooserDialogClosed(listener func()) {
emmiter.On("window-configured", func(payload ...interface{}) { emmiter.On("file-chooser-dialog-closed", func(payload ...interface{}) {
listener(payload[0].(uint32), payload[1].(string), payload[2].(string)) listener()
}) })
} }
@ -66,14 +68,38 @@ func goXEventClipboardUpdated() {
emmiter.Emit("clipboard-updated") emmiter.Emit("clipboard-updated")
} }
//export goXEventWindowCreated //export goXEventCreateNotify
func goXEventWindowCreated(window C.Window, name *C.char, role *C.char) { func goXEventCreateNotify(window C.Window, nameUnsafe *C.char, roleUnsafe *C.char) {
emmiter.Emit("window-created", uint32(window), C.GoString(name), C.GoString(role)) role := C.GoString(roleUnsafe)
if role != "GtkFileChooserDialog" {
return
}
file_chooser_dialog_window = uint32(window)
emmiter.Emit("file-chooser-dialog-opened")
} }
//export goXEventWindowConfigured //export goXEventConfigureNotify
func goXEventWindowConfigured(window C.Window, name *C.char, role *C.char) { func goXEventConfigureNotify(display *C.Display, window C.Window, nameUnsafe *C.char, roleUnsafe *C.char) {
emmiter.Emit("window-configured", uint32(window), C.GoString(name), C.GoString(role)) 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 //export goXEventError

View File

@ -5,13 +5,17 @@
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/extensions/Xfixes.h> #include <X11/extensions/Xfixes.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
extern void goXEventCursorChanged(XFixesCursorNotifyEvent event); extern void goXEventCursorChanged(XFixesCursorNotifyEvent event);
extern void goXEventClipboardUpdated(); extern void goXEventClipboardUpdated();
extern void goXEventWindowCreated(Window window, char *name, char *role); extern void goXEventCreateNotify(Window window, char *name, char *role);
extern void goXEventWindowConfigured(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 void goXEventError(XErrorEvent *event, char *message);
extern int goXEventActive(); extern int goXEventActive();
static int XEventError(Display *display, XErrorEvent *event); static int XEventError(Display *display, XErrorEvent *event);
void XEventLoop(char *display); 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 { func (manager *DesktopManagerCtx) GetCursorImage() *types.CursorImage {
return xorg.GetCursorImage() return xorg.GetCursorImage()
} }
func (manager *DesktopManagerCtx) PutWindowBelow(window uint32) {
xorg.PutWindowBelow(window)
}

View File

@ -136,46 +136,3 @@ XFixesCursorImage *XGetCursorImage(void) {
Display *display = getXDisplay(); Display *display = getXDisplay();
return XFixesGetCursorImage(display); 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 //export goCreateScreenSize
func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) { func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) {
ScreenConfigurations[int(index)] = types.ScreenConfiguration{ ScreenConfigurations[int(index)] = types.ScreenConfiguration{

View File

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

View File

@ -57,8 +57,8 @@ type DesktopManager interface {
// xevent // xevent
OnCursorChanged(listener func(serial uint64)) OnCursorChanged(listener func(serial uint64))
OnClipboardUpdated(listener func()) OnClipboardUpdated(listener func())
OnWindowCreated(listener func(window uint32, name string, role string)) OnFileChooserDialogOpened(listener func())
OnWindowConfigured(listener func(window uint32, name string, role string)) OnFileChooserDialogClosed(listener func())
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8)) OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
// clipboard // clipboard
@ -70,6 +70,6 @@ type DesktopManager interface {
// filechooser // filechooser
HandleFileChooserDialog(uri string) error HandleFileChooserDialog(uri string) error
CloseFileChooserDialog() bool CloseFileChooserDialog()
IsFileChooserDialogOpen() bool 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. // TODO: Implement.
ws.logger.Info(). ws.logger.Info().
Uint32("window", window). Msg("FileChooserDialog opened")
Str("name", name). })
Str("role", role).
Msg("created new window") ws.desktop.OnFileChooserDialogClosed(func() {
// TODO: Implement.
ws.logger.Info().
Msg("FileChooserDialog closed")
}) })
} }