diff --git a/internal/api/room/upload.go b/internal/api/room/upload.go index 6e8a098c..01e6a863 100644 --- a/internal/api/room/upload.go +++ b/internal/api/room/upload.go @@ -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) } diff --git a/internal/desktop/filechooser.go b/internal/desktop/filechooser.go index c37c9b27..8c6fc776 100644 --- a/internal/desktop/filechooser.go +++ b/internal/desktop/filechooser.go @@ -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() diff --git a/internal/desktop/manager.go b/internal/desktop/manager.go index 21264741..a4816457 100644 --- a/internal/desktop/manager.go +++ b/internal/desktop/manager.go @@ -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(). diff --git a/internal/desktop/xevent.go b/internal/desktop/xevent.go index 6f605fb1..3e4f5942 100644 --- a/internal/desktop/xevent.go +++ b/internal/desktop/xevent.go @@ -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)) { diff --git a/internal/desktop/xevent/xevent.c b/internal/desktop/xevent/xevent.c index 36aaa40e..238d7756 100644 --- a/internal/desktop/xevent/xevent.c +++ b/internal/desktop/xevent/xevent.c @@ -60,11 +60,11 @@ void XEventLoop(char *name) { char *name; XFetchName(display, window, &name); - + XTextProperty role; XGetTextProperty(display, window, &role, WM_WINDOW_ROLE); - - goXEventWindowCreated(window, name, role.value); + + goXEventCreateNotify(window, name, role.value); XFree(name); continue; } @@ -75,15 +75,64 @@ void XEventLoop(char *name) { char *name; XFetchName(display, window, &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); +} diff --git a/internal/desktop/xevent/xevent.go b/internal/desktop/xevent/xevent.go index d6b32548..ba40d19f 100644 --- a/internal/desktop/xevent/xevent.go +++ b/internal/desktop/xevent/xevent.go @@ -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 diff --git a/internal/desktop/xevent/xevent.h b/internal/desktop/xevent/xevent.h index 9314fc70..bf744e63 100644 --- a/internal/desktop/xevent/xevent.h +++ b/internal/desktop/xevent/xevent.h @@ -5,13 +5,17 @@ #include #include #include +#include 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); diff --git a/internal/desktop/xorg.go b/internal/desktop/xorg.go index 1d1efef9..270f7baf 100644 --- a/internal/desktop/xorg.go +++ b/internal/desktop/xorg.go @@ -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) -} diff --git a/internal/desktop/xorg/xorg.c b/internal/desktop/xorg/xorg.c index bb68a6ae..aded68c8 100644 --- a/internal/desktop/xorg/xorg.c +++ b/internal/desktop/xorg/xorg.c @@ -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); -} diff --git a/internal/desktop/xorg/xorg.go b/internal/desktop/xorg/xorg.go index de92246d..22a745f6 100644 --- a/internal/desktop/xorg/xorg.go +++ b/internal/desktop/xorg/xorg.go @@ -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{ diff --git a/internal/desktop/xorg/xorg.h b/internal/desktop/xorg/xorg.h index ea6fb385..aed4c841 100644 --- a/internal/desktop/xorg/xorg.h +++ b/internal/desktop/xorg/xorg.h @@ -6,7 +6,6 @@ #include #include #include -#include 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); diff --git a/internal/types/desktop.go b/internal/types/desktop.go index 9ec70e8b..3a7b0c24 100644 --- a/internal/types/desktop.go +++ b/internal/types/desktop.go @@ -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 } diff --git a/internal/websocket/manager.go b/internal/websocket/manager.go index b7b18014..12feb094 100644 --- a/internal/websocket/manager.go +++ b/internal/websocket/manager.go @@ -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") }) }