diff --git a/v2/pkg/assetserver/webview/request_linux_purego.go b/v2/pkg/assetserver/webview/request_linux_purego.go deleted file mode 100644 index 03e3a5ca0..000000000 --- a/v2/pkg/assetserver/webview/request_linux_purego.go +++ /dev/null @@ -1,96 +0,0 @@ -//go:build linux && purego -// +build linux,purego - -package webview - -import ( - "fmt" - "io" - "net/http" - - "github.com/ebitengine/purego" -) - -// NewRequest creates as new WebViewRequest based on a pointer to an `WebKitURISchemeRequest` -// -// Please make sure to call Release() when finished using the request. -func NewRequest(webKitURISchemeRequest uintptr) Request { - webkitReq := webKitURISchemeRequest - req := &request{req: webkitReq} - req.AddRef() - return req -} - -var _ Request = &request{} - -type request struct { - req uintptr - - header http.Header - body io.ReadCloser - rw *responseWriter -} - -func (r *request) AddRef() error { - var objectRef func(uintptr) - purego.RegisterLibFunc(&objectRef, gtk, "g_object_ref") - objectRef(r.req) - return nil -} - -func (r *request) Release() error { - var objectUnref func(uintptr) - purego.RegisterLibFunc(&objectUnref, gtk, "g_object_unref") - objectUnref(r.req) - return nil -} - -func (r *request) URL() (string, error) { - var getUri func(uintptr) string - purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_uri") - return getUri(r.req), nil -} - -func (r *request) Method() (string, error) { - return webkit_uri_scheme_request_get_http_method(r.req), nil -} - -func (r *request) Header() (http.Header, error) { - if r.header != nil { - return r.header, nil - } - - r.header = webkit_uri_scheme_request_get_http_headers(r.req) - return r.header, nil -} - -func (r *request) Body() (io.ReadCloser, error) { - if r.body != nil { - return r.body, nil - } - - // WebKit2GTK has currently no support for request bodies. - r.body = http.NoBody - - return r.body, nil -} - -func (r *request) Response() ResponseWriter { - fmt.Println("r.Response()") - if r.rw != nil { - return r.rw - } - - r.rw = &responseWriter{req: r.req} - return r.rw -} - -func (r *request) Close() error { - var err error - if r.body != nil { - err = r.body.Close() - } - r.Response().Finish() - r.Release() - return err -} diff --git a/v2/pkg/assetserver/webview/responsewriter_linux_purego.go b/v2/pkg/assetserver/webview/responsewriter_linux_purego.go deleted file mode 100644 index 8f5f1a57f..000000000 --- a/v2/pkg/assetserver/webview/responsewriter_linux_purego.go +++ /dev/null @@ -1,175 +0,0 @@ -//go:build linux && purego -// +build linux,purego - -package webview - -import ( - "fmt" - "io" - "net/http" - "os" - "strconv" - "syscall" - - "github.com/ebitengine/purego" -) - -const ( - gtk3 = "libgtk-3.so" - gtk4 = "libgtk-4.so" -) - -var ( - gtk uintptr - webkit uintptr - version int -) - -func init() { - var err error - // gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) - // if err == nil { - // version = 4 - // return - // } - // log.Println("Failed to open GTK4: Falling back to GTK3") - gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - panic(err) - } - version = 3 - - var webkit4 string = "libwebkit2gtk-4.1.so" - webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - panic(err) - } -} - -type responseWriter struct { - req uintptr - - header http.Header - wroteHeader bool - finished bool - - w io.WriteCloser - wErr error -} - -func (rw *responseWriter) Header() http.Header { - if rw.header == nil { - rw.header = http.Header{} - } - return rw.header -} - -func (rw *responseWriter) Write(buf []byte) (int, error) { - if rw.finished { - return 0, errResponseFinished - } - - rw.WriteHeader(http.StatusOK) - if rw.wErr != nil { - return 0, rw.wErr - } - return rw.w.Write(buf) -} - -func (rw *responseWriter) WriteHeader(code int) { - // TODO? Is this ever called? I don't think so! - if rw.wroteHeader || rw.finished { - return - } - rw.wroteHeader = true - - contentLength := int64(-1) - if sLen := rw.Header().Get(HeaderContentLength); sLen != "" { - if pLen, _ := strconv.ParseInt(sLen, 10, 64); pLen > 0 { - contentLength = pLen - } - } - fmt.Println("content_length", contentLength) - // We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the - // read FD is given to the InputStream and will be closed there. - // Furthermore we especially don't want to have the FD_CLOEXEC - rFD, w, err := pipe() - if err != nil { - rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to open pipe: %s", err)) - return - } - rw.w = w - - var newStream func(int, bool) uintptr - purego.RegisterLibFunc(&newStream, gtk, "g_unix_input_stream_new") - var unRef func(uintptr) - purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") - stream := newStream(rFD, true) - - /* var reqFinish func(uintptr, uintptr, uintptr, uintptr, int64) int - purego.RegisterLibFunc(&reqFinish, webkit, "webkit_uri_scheme_request_finish") - - header := rw.Header() - defer unRef(stream) - if err := reqFinish(rw.req, code, header, stream, contentLength); err != nil { - rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) - } - */ - if err := webkit_uri_scheme_request_finish(rw.req, code, rw.Header(), stream, contentLength); err != nil { - rw.finishWithError(http.StatusInternalServerError, fmt.Errorf("unable to finish request: %s", err)) - return - } -} - -func (rw *responseWriter) Finish() { - if !rw.wroteHeader { - rw.WriteHeader(http.StatusNotImplemented) - } - - if rw.finished { - return - } - rw.finished = true - if rw.w != nil { - rw.w.Close() - } -} - -func (rw *responseWriter) finishWithError(code int, err error) { - if rw.w != nil { - rw.w.Close() - rw.w = &nopCloser{io.Discard} - } - rw.wErr = err - - var newLiteral func(uint32, string, int, string) uintptr // is this correct? - purego.RegisterLibFunc(&newLiteral, gtk, "g_error_new_literal") - var newQuark func(string) uintptr - purego.RegisterLibFunc(&newQuark, gtk, "g_quark_from_string") - var freeError func(uintptr) - purego.RegisterLibFunc(&freeError, gtk, "g_error_free") - var finishError func(uintptr, uintptr) - purego.RegisterLibFunc(&finishError, webkit, "webkit_uri_scheme_request_finish_error") - - msg := string(err.Error()) - //gquark := newQuark(msg) - gerr := newLiteral(1, msg, code, msg) - finishError(rw.req, gerr) - freeError(gerr) -} - -type nopCloser struct { - io.Writer -} - -func (nopCloser) Close() error { return nil } - -func pipe() (r int, w *os.File, err error) { - var p [2]int - e := syscall.Pipe2(p[0:], 0) - if e != nil { - return 0, nil, fmt.Errorf("pipe2: %s", e) - } - - return p[0], os.NewFile(uintptr(p[1]), "|1"), nil -} diff --git a/v2/pkg/assetserver/webview/webkit2_36+_purego.go b/v2/pkg/assetserver/webview/webkit2_36+_purego.go deleted file mode 100644 index 2386868c3..000000000 --- a/v2/pkg/assetserver/webview/webkit2_36+_purego.go +++ /dev/null @@ -1,94 +0,0 @@ -//go:build linux && (webkit2_36 || webkit2_40) && purego - -package webview - -import ( - "net/http" - "strings" - - "github.com/ebitengine/purego" -) - -func webkit_uri_scheme_request_get_http_method(req uintptr) string { - var getMethod func(uintptr) string - purego.RegisterLibFunc(&getMethod, gtk, "webkit_uri_scheme_request_get_http_method") - return strings.ToUpper(getMethod(req)) -} - -func webkit_uri_scheme_request_get_http_headers(req uintptr) http.Header { - var getHeaders func(uintptr) uintptr - purego.RegisterLibFunc(&getUri, webkit, "webkit_uri_scheme_request_get_http_headers") - - hdrs := getHeaders(req) - - var headersIterInit func(uintptr, uintptr) uintptr - purego.RegisterLibFunc(&headersIterInit, gtk, "soup_message_headers_iter_init") - - // TODO: How do we get a struct? - /* - typedef struct { - SoupMessageHeaders *hdrs; - int index_common; - int index_uncommon; - } SoupMessageHeadersIterReal; - */ - iter := make([]byte, 12) - headersIterInit(&iter, hdrs) - - var iterNext func(uintptr, *string, *string) int - purego.RegisterLibFunc(&iterNext, gtk, "soup_message_headers_iter_next") - - var name string - var value string - h := http.Header{} - - for iterNext(&iter, &name, &value) != 0 { - h.Add(name, value) - } - - return h -} - -func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error { - - var newResponse func(uintptr, int64) string - purego.RegisterLibFunc(&newResponse, webkit, "webkit_uri_scheme_response_new") - var unRef func(uintptr) - purego.RegisterLibFunc(&unRef, gtk, "g_object_unref") - - resp := newResponse(stream, streamLength) - defer unRef(resp) - - var setStatus func(uintptr, int, string) - purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_status") - - setStatus(resp, code, cReason) - - var setContentType func(uintptr, string) - purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_content_type") - - setContentType(resp, header.Get(HeaderContentType)) - - soup := gtk - var soupHeadersNew func(int) uintptr - purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_new") - var soupHeadersAppend func(uintptr, string, string) - purego.RegisterLibFunc(&unRef, soup, "soup_message_headers_append") - - hdrs := soupHeadersNew(SOUP_MESSAGE_HEADERS_RESPONSE) - for name, values := range header { - for _, value := range values { - soupHeadersAppend(hdrs, name, value) - } - } - - var setHttpHeaders func(uintptr, uintptr) - purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_response_set_http_headers") - - setHttpHeaders(resp, hdrs) - var finishWithResponse func(uintptr, uintptr) - purego.RegisterLibFunc(&unRef, webkit, "webkit_uri_scheme_request_finish_with_response") - finishWithResponse(req, resp) - - return nil -} diff --git a/v2/pkg/assetserver/webview/webkit2_40+_purego.go b/v2/pkg/assetserver/webview/webkit2_40+_purego.go deleted file mode 100644 index 1088be25e..000000000 --- a/v2/pkg/assetserver/webview/webkit2_40+_purego.go +++ /dev/null @@ -1,74 +0,0 @@ -//go:build linux && webkit2_40 && purego - -package webview - -import ( - "fmt" - "io" - "net/http" - "unsafe" -) - -func webkit_uri_scheme_request_get_http_body(req *C.WebKitURISchemeRequest) io.ReadCloser { - stream := C.webkit_uri_scheme_request_get_http_body(req) - if stream == nil { - return http.NoBody - } - return &webkitRequestBody{stream: stream} -} - -type webkitRequestBody struct { - stream *C.GInputStream - closed bool -} - -// Read implements io.Reader -func (r *webkitRequestBody) Read(p []byte) (int, error) { - if r.closed { - return 0, io.ErrClosedPipe - } - - var content unsafe.Pointer - var contentLen int - if p != nil { - content = unsafe.Pointer(&p[0]) - contentLen = len(p) - } - - var n C.gsize - var gErr *C.GError - res := C.g_input_stream_read_all(r.stream, content, C.gsize(contentLen), &n, nil, &gErr) - if res == 0 { - return 0, formatGError("stream read failed", gErr) - } else if n == 0 { - return 0, io.EOF - } - return int(n), nil -} - -func (r *webkitRequestBody) Close() error { - if r.closed { - return nil - } - r.closed = true - - // https://docs.gtk.org/gio/method.InputStream.close.html - // Streams will be automatically closed when the last reference is dropped, but you might want to call this function - // to make sure resources are released as early as possible. - var err error - var gErr *C.GError - if C.g_input_stream_close(r.stream, nil, &gErr) == 0 { - err = formatGError("stream close failed", gErr) - } - C.g_object_unref(C.gpointer(r.stream)) - r.stream = nil - return err -} - -func formatGError(msg string, gErr *C.GError, args ...any) error { - if gErr != nil && gErr.message != nil { - msg += ": " + C.GoString(gErr.message) - C.g_error_free(gErr) - } - return fmt.Errorf(msg, args...) -} diff --git a/v2/pkg/assetserver/webview/webkit2_legacy_purego.go b/v2/pkg/assetserver/webview/webkit2_legacy_purego.go deleted file mode 100644 index 2e88864c8..000000000 --- a/v2/pkg/assetserver/webview/webkit2_legacy_purego.go +++ /dev/null @@ -1,36 +0,0 @@ -//go:build linux && !(webkit2_36 || webkit2_40) && purego - -package webview - -import ( - "fmt" - "io" - "net/http" - - "github.com/ebitengine/purego" -) - -const Webkit2MinMinorVersion = 0 - -func webkit_uri_scheme_request_get_http_method(_ uintptr) string { - return http.MethodGet -} - -func webkit_uri_scheme_request_get_http_headers(_ uintptr) http.Header { - return http.Header{} -} - -func webkit_uri_scheme_request_get_http_body(_ uintptr) io.ReadCloser { - return http.NoBody -} - -func webkit_uri_scheme_request_finish(req uintptr, code int, header http.Header, stream uintptr, streamLength int64) error { - if code != http.StatusOK { - return fmt.Errorf("StatusCodes not supported: %d - %s", code, http.StatusText(code)) - } - - var requestFinish func(uintptr, uintptr, int64, string) - purego.RegisterLibFunc(&requestFinish, webkit, "webkit_uri_scheme_request_finish") - requestFinish(req, stream, streamLength, header.Get(HeaderContentType)) - return nil -} diff --git a/v3/STATUS.md b/v3/STATUS.md index 528080052..1bfb6d08a 100644 --- a/v3/STATUS.md +++ b/v3/STATUS.md @@ -14,21 +14,21 @@ Status of features in v3. Incomplete - please add as you see fit. Application interface methods -| Method | Windows | Linux | Mac | Notes | -|---------------------------------------------------------------|---------|-------|-----|------------------------------| -| run() error | Y | Y | Y | | -| destroy() | | Y | Y | | -| setApplicationMenu(menu *Menu) | Y | | Y | | -| name() string | | | Y | | -| getCurrentWindowID() uint | Y | Y | Y | | -| showAboutDialog(name string, description string, icon []byte) | | Y | Y | [linux] No icon possible yet | -| setIcon(icon []byte) | - | | Y | | -| on(id uint) | | | Y | | -| dispatchOnMainThread(fn func()) | Y | Y | Y | | -| hide() | Y | | Y | | -| show() | Y | | Y | | -| getPrimaryScreen() (*Screen, error) | | Y | Y | | -| getScreens() ([]*Screen, error) | | Y | Y | | +| Method | Windows | Linux | Mac | Notes | +|---------------------------------------------------------------|---------|-------|-----|-------| +| run() error | Y | | Y | | +| destroy() | | | Y | | +| setApplicationMenu(menu *Menu) | Y | | Y | | +| name() string | | | Y | | +| getCurrentWindowID() uint | Y | | Y | | +| showAboutDialog(name string, description string, icon []byte) | | | Y | | +| setIcon(icon []byte) | - | | Y | | +| on(id uint) | | | Y | | +| dispatchOnMainThread(fn func()) | Y | | Y | | +| hide() | Y | | Y | | +| show() | Y | | Y | | +| getPrimaryScreen() (*Screen, error) | | | Y | | +| getScreens() ([]*Screen, error) | | | Y | | ## Webview Window @@ -90,7 +90,7 @@ Webview Window Interface Methods | Feature | Windows | Linux | Mac | Notes | |---------|---------|-------|-----|-------| -| Quit | Y | Y | Y | | +| Quit | Y | | Y | | | Hide | Y | | Y | | | Show | Y | | Y | | @@ -132,9 +132,9 @@ explicitly set with `--default-contextmenu: show`. | Feature | Windows | Linux | Mac | Notes | |------------|---------|-------|-----|-------| -| GetAll | Y | Y | Y | | -| GetPrimary | Y | Y | Y | | -| GetCurrent | Y | Y | Y | | +| GetAll | Y | | Y | | +| GetPrimary | Y | | Y | | +| GetCurrent | Y | | Y | | ### Window @@ -180,37 +180,39 @@ U = Untested A 'Y' in the table below indicates that the option has been tested and is applied when the window is created. An 'X' indicates that the option is not supported by the platform. -| Feature | Windows | Linux | Mac | Notes | -|---------------------------------|---------|-------|-----|---------------------------------------------------| -| Name | | | | | -| Title | Y | | | | -| Width | Y | Y | | | -| Height | Y | Y | | | -| AlwaysOnTop | Y | Y | | | -| URL | Y | | | | -| DisableResize | Y | Y | | | -| Frameless | Y | Y | | | -| MinWidth | Y | Y | | | -| MinHeight | Y | Y | | | -| MaxWidth | Y | Y | | | -| MaxHeight | Y | Y | | | -| StartState | Y | | | | -| Mac | - | - | | | -| BackgroundType | | | | Acrylic seems to work but the others don't | -| BackgroundColour | Y | Y | | | -| HTML | Y | Y | | | -| JS | Y | Y | | | -| CSS | Y | Y | | | -| X | Y | Y | | | -| Y | Y | Y | | | -| HideOnClose | Y | Y | | | -| FullscreenButtonEnabled | | ? | | [linux] How is this different from DisableResize? | -| Hidden | Y | | | | -| EnableFraudulentWebsiteWarnings | | | | | -| Zoom | | Y | | | -| EnableDragAndDrop | Y | Y | | | -| Windows | Y | - | - | | -| Focused | Y | | | | +| Feature | Windows | Linux | Mac | Notes | +|---------------------------------|---------|-------|-----|--------------------------------------------| +| AlwaysOnTop | Y | | | | +| BackgroundColour | Y | | | | +| BackgroundType | | | | Acrylic seems to work but the others don't | +| CSS | Y | | | | +| DevToolsEnabled | Y | | Y | | +| DisableResize | Y | | | | +| EnableDragAndDrop | | | | | +| EnableFraudulentWebsiteWarnings | | | | | +| Focused | Y | | | | +| Frameless | Y | | | | +| FullscreenButtonEnabled | Y | | | | +| Height | Y | | | | +| Hidden | Y | | | | +| HTML | Y | | | | +| JS | Y | | | | +| Mac | - | - | | | +| MaxHeight | Y | | | | +| MaxWidth | Y | | | | +| MinHeight | Y | | | | +| MinWidth | Y | | | | +| Name | Y | | | | +| OpenInspectorOnStartup | | | | | +| StartState | Y | | | | +| Title | Y | | | | +| URL | Y | | | | +| Width | Y | | | | +| Windows | Y | - | - | | +| X | Y | | | | +| Y | Y | | | | +| Zoom | | | | | +| ZoomControlEnabled | | | | | ### Log @@ -220,7 +222,7 @@ To log or not to log? System logger vs custom logger. | Event | Windows | Linux | Mac | Notes | |--------------------------|---------|-------|-----|-------| -| Default Application Menu | Y | Y | Y | | +| Default Application Menu | Y | | Y | | ## Tray Menus @@ -293,10 +295,10 @@ Built-in plugin support: | Plugin | Windows | Linux | Mac | Notes | |-----------------|---------|-------|-----|-------| | Browser | Y | | Y | | -| KV Store | Y | Y | Y | | -| Log | Y | Y | Y | | +| KV Store | Y | | Y | | +| Log | Y | | Y | | | Single Instance | Y | | Y | | -| SQLite | Y | Y | Y | | +| SQLite | Y | | Y | | | Start at login | | | Y | | | Server | | | | | @@ -381,4 +383,4 @@ Built-in plugin support: # Beta Release TODO -- [ ] Make better looking examples \ No newline at end of file +- [ ] Make better looking examples diff --git a/v3/go.mod b/v3/go.mod index 44e890a8b..ec8262c08 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -3,7 +3,6 @@ module github.com/wailsapp/wails/v3 go 1.19 require ( - github.com/ebitengine/purego v0.3.2 github.com/bep/debounce v1.2.1 github.com/go-ole/go-ole v1.2.6 github.com/go-task/task/v3 v3.20.0 @@ -76,5 +75,3 @@ require ( ) replace github.com/wailsapp/wails/v2 => ../v2 - -replace github.com/ebitengine/purego v0.3.2 => github.com/TotallyGamerJet/purego v0.2.0-alpha.0.20230404174033-5655abccca7e diff --git a/v3/go.sum b/v3/go.sum index b9f9f1b10..715d5133d 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -11,8 +11,6 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.1 h1:a9Fqx6vQrHQ4CyiaLhktfTTelwGotmFWy8MNhyaohw8= -github.com/TotallyGamerJet/purego v0.2.0-alpha.0.20230404174033-5655abccca7e h1:wQ7ot+e0mwJYkbomtIX9tU0dOV9lFTmwAgUGqvQTIUg= -github.com/TotallyGamerJet/purego v0.2.0-alpha.0.20230404174033-5655abccca7e/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index afb5898cb..4c8c3490d 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -333,9 +333,7 @@ func (a *App) error(message string, args ...any) { func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow { newWindow := NewWindow(windowOptions) id := newWindow.id - if a.windows == nil { - a.windows = make(map[uint]*WebviewWindow) - } + a.windowsLock.Lock() a.windows[id] = newWindow a.windowsLock.Unlock() diff --git a/v3/pkg/application/application_linux.go b/v3/pkg/application/application_linux.go deleted file mode 100644 index 1bb5a3be5..000000000 --- a/v3/pkg/application/application_linux.go +++ /dev/null @@ -1,268 +0,0 @@ -//go:build linux && !purego - -package application - -/* -#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 - -#include -#include -#include -#include -#include -#include - -typedef struct App { - void *app; -} App; - -extern void processApplicationEvent(uint); - -extern void activateLinux(gpointer data); - -static void activate (GtkApplication* app, gpointer data) { - // FIXME: should likely emit a WAILS specific code - // events.Mac.EventApplicationDidFinishLaunching == 1032 - //processApplicationEvent(1032); - - activateLinux(data); -} - -static GtkApplication* init(char* name) { - return gtk_application_new(name, G_APPLICATION_DEFAULT_FLAGS); -} - -static int run(void *app, void *data) { - g_signal_connect (app, "activate", G_CALLBACK (activate), data); - g_application_hold(app); // allows it to run without a window - int status = g_application_run (G_APPLICATION (app), 0, NULL); - g_application_release(app); - g_object_unref (app); - return status; -} - -*/ -import "C" -import ( - "fmt" - "log" - "os" - "strings" - "sync" - "unsafe" - - "github.com/wailsapp/wails/v3/pkg/events" -) - -func init() { - // Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings - _ = os.Setenv("GDK_BACKEND", "x11") -} - -type linuxApp struct { - application unsafe.Pointer - applicationMenu unsafe.Pointer - parent *App - - startupActions []func() - - // Native -> uint - windows map[*C.GtkWindow]uint - windowsLock sync.Mutex -} - -func getNativeApplication() *linuxApp { - return globalApplication.impl.(*linuxApp) -} - -func (m *linuxApp) hide() { - windows := C.gtk_application_get_windows((*C.GtkApplication)(m.application)) - for { - fmt.Println("hiding", windows.data) - C.gtk_widget_hide((*C.GtkWidget)(windows.data)) - windows = windows.next - if windows == nil { - return - } - } -} - -func (m *linuxApp) show() { - windows := C.gtk_application_get_windows((*C.GtkApplication)(m.application)) - for { - fmt.Println("hiding", windows.data) - C.gtk_widget_show_all((*C.GtkWidget)(windows.data)) - windows = windows.next - if windows == nil { - return - } - } -} - -func (m *linuxApp) on(eventID uint) { - log.Println("linuxApp.on()", eventID) - // TODO: Setup signal handling as appropriate - // Note: GTK signals seem to be strings! -} - -func (m *linuxApp) setIcon(icon []byte) { - /* // FIXME: WIP - loader := C.gdk_pixbuf_loader_new() - - if loader == nil { - return - } - - loaded := C.gdk_pixbuf_loader_write(loader, (*C.guchar)(&icon[0]), (C.gsize)(len(icon)), 0) - - if loaded == C.bool(1) && C.gdk_pixbuf_loader_close(loader, 0) { - pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader) - if pixbuf != nil { - ww := m.parent.CurrentWindow() - window := ww.impl.window - C.gtk_window_set_icon(window, pixbuf) - } - } - - C.g_object_unref(loader) - */ -} - -func (m *linuxApp) name() string { - // appName := C.getAppName() - // defer C.free(unsafe.Pointer(appName)) - // return C.GoString(appName) - return "" -} - -func (m *linuxApp) getCurrentWindowID() uint { - // TODO: Add extra metadata to window - window := (*C.GtkWindow)(C.gtk_application_get_active_window((*C.GtkApplication)(m.application))) - if window == nil { - return uint(1) - } - m.windowsLock.Lock() - defer m.windowsLock.Unlock() - identifier, ok := m.windows[window] - if ok { - return identifier - } - return uint(1) -} - -func (m *linuxApp) setApplicationMenu(menu *Menu) { - if menu == nil { - // Create a default menu - menu = defaultApplicationMenu() - } - globalApplication.dispatchOnMainThread(func() { - fmt.Println("setApplicationMenu") - - menu.Update() - m.applicationMenu = (menu.impl).(*linuxMenu).native - }) -} - -func (m *linuxApp) run() error { - - // Add a hook to the ApplicationDidFinishLaunching event - // FIXME: add Wails specific events - i.e. Shouldn't platform specific ones be translated to Wails events? - m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() { - // Do we need to do anything now? - fmt.Println("events.Mac.ApplicationDidFinishLaunching received!") - }) - - var app C.App - app.app = unsafe.Pointer(m) - C.run(m.application, m.application) - return nil -} - -func (m *linuxApp) destroy() { - C.g_application_quit((*C.GApplication)(m.application)) -} - -// register our window to our parent mapping -func (m *linuxApp) registerWindow(window *C.GtkWindow, id uint) { - m.windowsLock.Lock() - m.windows[window] = id - m.windowsLock.Unlock() -} - -func newPlatformApp(parent *App) *linuxApp { - name := strings.ToLower(strings.Replace(parent.options.Name, " ", "", -1)) - if name == "" { - name = "undefined" - } - nameC := C.CString(fmt.Sprintf("org.wails.%s", name)) - app := &linuxApp{ - parent: parent, - application: unsafe.Pointer(C.init(nameC)), - // name: fmt.Sprintf("org.wails.%s", name), - windows: map[*C.GtkWindow]uint{}, - } - C.free(unsafe.Pointer(nameC)) - return app -} - -// executeStartupActions is called by `activateLinux` below to execute -// code which needs to be run after the 'activate' signal is received -func (m *linuxApp) executeStartupActions() { - for _, fn := range m.startupActions { - fn() - } -} - -//export activateLinux -func activateLinux(data unsafe.Pointer) { - getNativeApplication().executeStartupActions() -} - -//export processApplicationEvent -func processApplicationEvent(eventID C.uint) { - // TODO: add translation to Wails events - // currently reusing Mac specific values - applicationEvents <- uint(eventID) -} - -//export processWindowEvent -func processWindowEvent(windowID C.uint, eventID C.uint) { - windowEvents <- &WindowEvent{ - WindowID: uint(windowID), - EventID: uint(eventID), - } -} - -//export processMessage -func processMessage(windowID C.uint, message *C.char) { - windowMessageBuffer <- &windowMessage{ - windowId: uint(windowID), - message: C.GoString(message), - } -} - -//export processDragItems -func processDragItems(windowID C.uint, arr **C.char, length C.int) { - var filenames []string - // Convert the C array to a Go slice - goSlice := (*[1 << 30]*C.char)(unsafe.Pointer(arr))[:length:length] - for _, str := range goSlice { - filenames = append(filenames, C.GoString(str)) - } - windowDragAndDropBuffer <- &dragAndDropMessage{ - windowId: uint(windowID), - filenames: filenames, - } -} - -//export processMenuItemClick -func processMenuItemClick(menuID C.uint) { - menuItemClicked <- uint(menuID) -} - -func setIcon(icon []byte) { - if icon == nil { - return - } - //C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) -} diff --git a/v3/pkg/application/application_linux_purego.go b/v3/pkg/application/application_linux_purego.go deleted file mode 100644 index 6585ad59e..000000000 --- a/v3/pkg/application/application_linux_purego.go +++ /dev/null @@ -1,253 +0,0 @@ -//go:build linux && purego - -package application - -import ( - "fmt" - "log" - "os" - "strings" - "sync" - - "github.com/ebitengine/purego" - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" -) - -const ( - gtk3 = "libgtk-3.so" - gtk4 = "libgtk-4.so" -) - -var ( - gtk uintptr - version int - webkit uintptr -) - -func init() { - // needed for GTK4 to function - _ = os.Setenv("GDK_BACKEND", "x11") - var err error - /* - gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err == nil { - version = 4 - return - } - - log.Println("Failed to open GTK4: Falling back to GTK3") - */ - gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - panic(err) - } - version = 3 - - var webkit4 string = "libwebkit2gtk-4.1.so" - webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL) - if err != nil { - panic(err) - } -} - -type linuxApp struct { - appName string - application uintptr - applicationMenu uintptr - parent *App - - // Native -> uint - windows map[uintptr]uint - windowsLock sync.Mutex -} - -func getNativeApplication() *linuxApp { - return globalApplication.impl.(*linuxApp) -} - -func (m *linuxApp) hide() { - // C.hide() -} - -func (m *linuxApp) show() { - // C.show() -} - -func (m *linuxApp) on(eventID uint) { - log.Println("linuxApp.on()", eventID) - - // TODO: Setup signal handling as appropriate - // Note: GTK signals seem to be strings! -} - -func (m *linuxApp) setIcon(icon []byte) { - // C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) -} - -func (m *linuxApp) name() string { - return m.appName -} - -func (m *linuxApp) getCurrentWindowID() uint { - var getCurrentWindow func(uintptr) uintptr - purego.RegisterLibFunc(&getCurrentWindow, gtk, "gtk_application_get_active_window") - window := getCurrentWindow(m.application) - if window == 0 { - return 1 - } - m.windowsLock.Lock() - defer m.windowsLock.Unlock() - if identifier, ok := m.windows[window]; ok { - return identifier - } - - return 1 -} - -func (m *linuxApp) setApplicationMenu(menu *Menu) { - if menu == nil { - // Create a default menu - menu = defaultApplicationMenu() - } - globalApplication.dispatchOnMainThread(func() { - menu.Update() - m.applicationMenu = (menu.impl).(*linuxMenu).native - }) -} - -func (m *linuxApp) activate() { - fmt.Println("linuxApp.activated!", m.application) - var hold func(uintptr) - purego.RegisterLibFunc(&hold, gtk, "g_application_hold") - - hold(m.application) - - // time.Sleep(50 * time.Millisecond) - // m.parent.activate() -} - -func (m *linuxApp) run() error { - // Add a hook to the ApplicationDidFinishLaunching event - // FIXME: add Wails specific events - i.e. Shouldn't platform specific ones be translated to Wails events? - /* m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() { - // Do we need to do anything now? - fmt.Println("ApplicationDidFinishLaunching!") - }) - */ - m.parent.OnWindowCreation(func(window *WebviewWindow) { - fmt.Println("OnWindowCreation: ", window) - - }) - - var g_signal_connect func(uintptr, string, uintptr, uintptr, bool, int) int - purego.RegisterLibFunc(&g_signal_connect, gtk, "g_signal_connect_data") - g_signal_connect(m.application, "activate", purego.NewCallback(m.activate), m.application, false, 0) - - var run func(uintptr, int, []string) int - purego.RegisterLibFunc(&run, gtk, "g_application_run") - - // FIXME: Convert status to 'error' if needed - status := run(m.application, 0, []string{}) - fmt.Println("status", status) - - var release func(uintptr) - purego.RegisterLibFunc(&release, gtk, "g_application_release") - release(m.application) - - purego.RegisterLibFunc(&release, gtk, "g_object_unref") - release(m.application) - - return nil -} - -func (m *linuxApp) destroy() { - var quit func(uintptr) - purego.RegisterLibFunc(&quit, gtk, "g_application_quit") - quit(m.application) -} - -func (m *linuxApp) registerWindow(address uintptr, window uint) { - m.windowsLock.Lock() - m.windows[address] = window - m.windowsLock.Unlock() -} - -func newPlatformApp(parent *App) *linuxApp { - name := strings.ToLower(parent.options.Name) - if name == "" { - name = "undefined" - } - identifier := fmt.Sprintf("org.wails.%s", strings.Replace(name, " ", "-", -1)) - - var gtkNew func(string, uint) uintptr - purego.RegisterLibFunc(>kNew, gtk, "gtk_application_new") - app := &linuxApp{ - appName: identifier, - parent: parent, - application: gtkNew(identifier, 0), - windows: map[uintptr]uint{}, - } - return app -} - -func processApplicationEvent(eventID uint) { - // TODO: add translation to Wails events - // currently reusing Mac specific values - applicationEvents <- eventID -} - -func processWindowEvent(windowID uint, eventID uint) { - windowEvents <- &WindowEvent{ - WindowID: windowID, - EventID: eventID, - } -} - -func processMessage(windowID uint, message string) { - windowMessageBuffer <- &windowMessage{ - windowId: windowID, - message: message, - } -} - -func processURLRequest(windowID uint, wkUrlSchemeTask uintptr) { - fmt.Println("processURLRequest", windowID, wkUrlSchemeTask) - webviewRequests <- &webViewAssetRequest{ - Request: webview.NewRequest(wkUrlSchemeTask), - windowId: windowID, - windowName: globalApplication.getWindowForID(windowID).Name(), - } -} - -func processDragItems(windowID uint, arr []string, length int) { - windowDragAndDropBuffer <- &dragAndDropMessage{ - windowId: windowID, - filenames: arr, - } -} - -func processMenuItemClick(menuID uint) { - menuItemClicked <- menuID -} - -func setIcon(icon []byte) { - if icon == nil { - return - } - fmt.Println("setIcon") - /* - GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); - if (!loader) - { - return; - } - if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL)) - { - GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); - if (pixbuf) - { - gtk_window_set_icon(window, pixbuf); - } - } - g_object_unref(loader);*/ -} diff --git a/v3/pkg/application/clipboard_linux.go b/v3/pkg/application/clipboard_linux.go deleted file mode 100644 index cfbdf4df1..000000000 --- a/v3/pkg/application/clipboard_linux.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build linux - -package application - -import ( - "sync" -) - -var clipboardLock sync.RWMutex - -type linuxClipboard struct{} - -func (m linuxClipboard) setText(text string) bool { - clipboardLock.Lock() - defer clipboardLock.Unlock() - // cText := C.CString(text) - // success := C.setClipboardText(cText) - // C.free(unsafe.Pointer(cText)) - success := false - return bool(success) -} - -func (m linuxClipboard) text() string { - clipboardLock.RLock() - defer clipboardLock.RUnlock() - // clipboardText := C.getClipboardText() - // result := C.GoString(clipboardText) - return "" -} - -func newClipboardImpl() *linuxClipboard { - return &linuxClipboard{} -} diff --git a/v3/pkg/application/dialogs_linux.go b/v3/pkg/application/dialogs_linux.go deleted file mode 100644 index 85733bdf1..000000000 --- a/v3/pkg/application/dialogs_linux.go +++ /dev/null @@ -1,300 +0,0 @@ -//go:build linux - -package application - -/* -#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 - -#include -#include -#include - -static GtkWidget* new_about_dialog(GtkWindow *parent, const gchar *msg) { - // gtk_message_dialog_new is variadic! Can't call from cgo - GtkWidget *dialog; - dialog = gtk_message_dialog_new( - parent, - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_INFO, - GTK_BUTTONS_CLOSE, - msg); - - g_signal_connect_swapped (dialog, - "response", - G_CALLBACK (gtk_widget_destroy), - dialog); - return dialog; -}; - -*/ -import "C" -import ( - "fmt" - "unsafe" -) - -const AlertStyleWarning = C.int(0) -const AlertStyleInformational = C.int(1) -const AlertStyleCritical = C.int(2) - -var alertTypeMap = map[DialogType]C.int{ - WarningDialog: AlertStyleWarning, - InfoDialog: AlertStyleInformational, - ErrorDialog: AlertStyleCritical, - QuestionDialog: AlertStyleInformational, -} - -func setWindowIcon(window *C.GtkWindow, icon []byte) { - fmt.Println("setWindowIcon", len(icon)) - loader := C.gdk_pixbuf_loader_new() - if loader == nil { - return - } - written := C.gdk_pixbuf_loader_write( - loader, - (*C.uchar)(&icon[0]), - C.ulong(len(icon)), - nil) - if written == 0 { - fmt.Println("failed to write icon") - return - } - C.gdk_pixbuf_loader_close(loader, nil) - pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader) - if pixbuf != nil { - fmt.Println("gtk_window_set_icon", window) - C.gtk_window_set_icon((*C.GtkWindow)(window), pixbuf) - } - C.g_object_unref(C.gpointer(loader)) -} - -func (m *linuxApp) showAboutDialog(title string, message string, icon []byte) { - globalApplication.dispatchOnMainThread(func() { - parent := C.gtk_application_get_active_window((*C.GtkApplication)(m.application)) - cMsg := C.CString(message) - cTitle := C.CString(title) - defer C.free(unsafe.Pointer(cMsg)) - defer C.free(unsafe.Pointer(cTitle)) - dialog := C.new_about_dialog(parent, cMsg) - C.gtk_window_set_title( - (*C.GtkWindow)(unsafe.Pointer(dialog)), - cTitle) - // setWindowIcon((*C.GtkWindow)(dialog), icon) - C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog))) - - }) -} - -type linuxDialog struct { - dialog *MessageDialog - - //nsDialog unsafe.Pointer -} - -func (m *linuxDialog) show() { - globalApplication.dispatchOnMainThread(func() { - - // Mac can only have 4 Buttons on a dialog - if len(m.dialog.Buttons) > 4 { - m.dialog.Buttons = m.dialog.Buttons[:4] - } - - // if m.nsDialog != nil { - // //C.releaseDialog(m.nsDialog) - // } - // var title *C.char - // if m.dialog.Title != "" { - // title = C.CString(m.dialog.Title) - // } - // var message *C.char - // if m.dialog.Message != "" { - // message = C.CString(m.dialog.Message) - // } - // var iconData unsafe.Pointer - // var iconLength C.int - // if m.dialog.Icon != nil { - // iconData = unsafe.Pointer(&m.dialog.Icon[0]) - // iconLength = C.int(len(m.dialog.Icon)) - // } else { - // // if it's an error, use the application Icon - // if m.dialog.DialogType == ErrorDialog { - // iconData = unsafe.Pointer(&globalApplication.options.Icon[0]) - // iconLength = C.int(len(globalApplication.options.Icon)) - // } - // } - - // alertType, ok := alertTypeMap[m.dialog.DialogType] - // if !ok { - // alertType = AlertStyleInformational - // } - - // m.nsDialog = C.createAlert(alertType, title, message, iconData, iconLength) - - // Reverse the Buttons so that the default is on the right - reversedButtons := make([]*Button, len(m.dialog.Buttons)) - var count = 0 - for i := len(m.dialog.Buttons) - 1; i >= 0; i-- { - //button := m.dialog.Buttons[i] - //C.alertAddButton(m.nsDialog, C.CString(button.Label), C.bool(button.IsDefault), C.bool(button.IsCancel)) - reversedButtons[count] = m.dialog.Buttons[i] - count++ - } - - buttonPressed := int(0) //C.dialogRunModal(m.nsDialog)) - if len(m.dialog.Buttons) > buttonPressed { - button := reversedButtons[buttonPressed] - if button.callback != nil { - button.callback() - } - } - }) - -} - -func newDialogImpl(d *MessageDialog) *linuxDialog { - return &linuxDialog{ - dialog: d, - } -} - -type linuxOpenFileDialog struct { - dialog *OpenFileDialog -} - -func newOpenFileDialogImpl(d *OpenFileDialog) *linuxOpenFileDialog { - return &linuxOpenFileDialog{ - dialog: d, - } -} - -func toCString(s string) *C.char { - if s == "" { - return nil - } - return C.CString(s) -} - -func (m *linuxOpenFileDialog) show() ([]string, error) { - openFileResponses[m.dialog.id] = make(chan string) - // nsWindow := unsafe.Pointer(nil) - if m.dialog.window != nil { - // get NSWindow from window - //nsWindow = m.dialog.window.impl.(*macosWebviewWindow).nsWindow - } - - // Massage filter patterns into macOS format - // We iterate all filter patterns, tidy them up and then join them with a semicolon - // This should produce a single string of extensions like "png;jpg;gif" - // var filterPatterns string - // if len(m.dialog.filters) > 0 { - // var allPatterns []string - // for _, filter := range m.dialog.filters { - // patternComponents := strings.Split(filter.Pattern, ";") - // for i, component := range patternComponents { - // filterPattern := strings.TrimSpace(component) - // filterPattern = strings.TrimPrefix(filterPattern, "*.") - // patternComponents[i] = filterPattern - // } - // allPatterns = append(allPatterns, strings.Join(patternComponents, ";")) - // } - // filterPatterns = strings.Join(allPatterns, ";") - // } - - // C.showOpenFileDialog(C.uint(m.dialog.id), - // C.bool(m.dialog.canChooseFiles), - // C.bool(m.dialog.canChooseDirectories), - // C.bool(m.dialog.canCreateDirectories), - // C.bool(m.dialog.showHiddenFiles), - // C.bool(m.dialog.allowsMultipleSelection), - // C.bool(m.dialog.resolvesAliases), - // C.bool(m.dialog.hideExtension), - // C.bool(m.dialog.treatsFilePackagesAsDirectories), - // C.bool(m.dialog.allowsOtherFileTypes), - // toCString(filterPatterns), - // C.uint(len(filterPatterns)), - // toCString(m.dialog.message), - // toCString(m.dialog.directory), - // toCString(m.dialog.buttonText), - // nsWindow) - var result []string - for filename := range openFileResponses[m.dialog.id] { - result = append(result, filename) - } - return result, nil -} - -//export openFileDialogCallback -func openFileDialogCallback(cid C.uint, cpath *C.char) { - path := C.GoString(cpath) - id := uint(cid) - channel, ok := openFileResponses[id] - if ok { - channel <- path - } else { - panic("No channel found for open file dialog") - } -} - -//export openFileDialogCallbackEnd -func openFileDialogCallbackEnd(cid C.uint) { - id := uint(cid) - channel, ok := openFileResponses[id] - if ok { - close(channel) - delete(openFileResponses, id) - freeDialogID(id) - } else { - panic("No channel found for open file dialog") - } -} - -type linuxSaveFileDialog struct { - dialog *SaveFileDialog -} - -func newSaveFileDialogImpl(d *SaveFileDialog) *linuxSaveFileDialog { - return &linuxSaveFileDialog{ - dialog: d, - } -} - -func (m *linuxSaveFileDialog) show() (string, error) { - saveFileResponses[m.dialog.id] = make(chan string) - // nsWindow := unsafe.Pointer(nil) - if m.dialog.window != nil { - // get NSWindow from window - // nsWindow = m.dialog.window.impl.(*linuxWebviewWindow).nsWindow - } - - // C.showSaveFileDialog(C.uint(m.dialog.id), - // C.bool(m.dialog.canCreateDirectories), - // C.bool(m.dialog.showHiddenFiles), - // C.bool(m.dialog.canSelectHiddenExtension), - // C.bool(m.dialog.hideExtension), - // C.bool(m.dialog.treatsFilePackagesAsDirectories), - // C.bool(m.dialog.allowOtherFileTypes), - // toCString(m.dialog.message), - // toCString(m.dialog.directory), - // toCString(m.dialog.buttonText), - // toCString(m.dialog.filename), - // nsWindow) - return <-saveFileResponses[m.dialog.id], nil -} - -//export saveFileDialogCallback -func saveFileDialogCallback(cid C.uint, cpath *C.char) { - // Covert the path to a string - path := C.GoString(cpath) - id := uint(cid) - // put response on channel - channel, ok := saveFileResponses[id] - if ok { - channel <- path - close(channel) - delete(saveFileResponses, id) - freeDialogID(id) - - } else { - panic("No channel found for save file dialog") - } -} diff --git a/v3/pkg/application/dialogs_linux_purego.go b/v3/pkg/application/dialogs_linux_purego.go deleted file mode 100644 index 757b4f3d3..000000000 --- a/v3/pkg/application/dialogs_linux_purego.go +++ /dev/null @@ -1,220 +0,0 @@ -//go:build linux && purego - -package application - -const AlertStyleWarning = 0 -const AlertStyleInformational = 1 -const AlertStyleCritical = 2 - -var alertTypeMap = map[DialogType]int{ - WarningDialog: AlertStyleWarning, - InfoDialog: AlertStyleInformational, - ErrorDialog: AlertStyleCritical, - QuestionDialog: AlertStyleInformational, -} - -func (m *linuxApp) showAboutDialog(title string, message string, icon []byte) { - // var iconData unsafe.Pointer - // if icon != nil { - // iconData = unsafe.Pointer(&icon[0]) - // } - //C.showAboutBox(C.CString(title), C.CString(message), iconData, C.int(len(icon))) -} - -type linuxDialog struct { - dialog *MessageDialog - - //nsDialog unsafe.Pointer -} - -func (m *linuxDialog) show() { - globalApplication.dispatchOnMainThread(func() { - - // Mac can only have 4 Buttons on a dialog - if len(m.dialog.Buttons) > 4 { - m.dialog.Buttons = m.dialog.Buttons[:4] - } - - // if m.nsDialog != nil { - // //C.releaseDialog(m.nsDialog) - // } - // var title *C.char - // if m.dialog.Title != "" { - // title = C.CString(m.dialog.Title) - // } - // var message *C.char - // if m.dialog.Message != "" { - // message = C.CString(m.dialog.Message) - // } - // var iconData unsafe.Pointer - // var iconLength C.int - // if m.dialog.Icon != nil { - // iconData = unsafe.Pointer(&m.dialog.Icon[0]) - // iconLength = C.int(len(m.dialog.Icon)) - // } else { - // // if it's an error, use the application Icon - // if m.dialog.DialogType == ErrorDialog { - // iconData = unsafe.Pointer(&globalApplication.options.Icon[0]) - // iconLength = C.int(len(globalApplication.options.Icon)) - // } - // } - - // alertType, ok := alertTypeMap[m.dialog.DialogType] - // if !ok { - // alertType = AlertStyleInformational - // } - - // m.nsDialog = C.createAlert(alertType, title, message, iconData, iconLength) - - // Reverse the Buttons so that the default is on the right - reversedButtons := make([]*Button, len(m.dialog.Buttons)) - var count = 0 - for i := len(m.dialog.Buttons) - 1; i >= 0; i-- { - //button := m.dialog.Buttons[i] - //C.alertAddButton(m.nsDialog, C.CString(button.Label), C.bool(button.IsDefault), C.bool(button.IsCancel)) - reversedButtons[count] = m.dialog.Buttons[i] - count++ - } - - buttonPressed := int(0) //C.dialogRunModal(m.nsDialog)) - if len(m.dialog.Buttons) > buttonPressed { - button := reversedButtons[buttonPressed] - if button.callback != nil { - button.callback() - } - } - }) - -} - -func newDialogImpl(d *MessageDialog) *linuxDialog { - return &linuxDialog{ - dialog: d, - } -} - -type linuxOpenFileDialog struct { - dialog *OpenFileDialog -} - -func newOpenFileDialogImpl(d *OpenFileDialog) *linuxOpenFileDialog { - return &linuxOpenFileDialog{ - dialog: d, - } -} - -func (m *linuxOpenFileDialog) show() ([]string, error) { - openFileResponses[m.dialog.id] = make(chan string) - // nsWindow := unsafe.Pointer(nil) - if m.dialog.window != nil { - // get NSWindow from window - //nsWindow = m.dialog.window.impl.(*macosWebviewWindow).nsWindow - } - - // Massage filter patterns into macOS format - // We iterate all filter patterns, tidy them up and then join them with a semicolon - // This should produce a single string of extensions like "png;jpg;gif" - // var filterPatterns string - // if len(m.dialog.filters) > 0 { - // var allPatterns []string - // for _, filter := range m.dialog.filters { - // patternComponents := strings.Split(filter.Pattern, ";") - // for i, component := range patternComponents { - // filterPattern := strings.TrimSpace(component) - // filterPattern = strings.TrimPrefix(filterPattern, "*.") - // patternComponents[i] = filterPattern - // } - // allPatterns = append(allPatterns, strings.Join(patternComponents, ";")) - // } - // filterPatterns = strings.Join(allPatterns, ";") - // } - - // C.showOpenFileDialog(C.uint(m.dialog.id), - // C.bool(m.dialog.canChooseFiles), - // C.bool(m.dialog.canChooseDirectories), - // C.bool(m.dialog.canCreateDirectories), - // C.bool(m.dialog.showHiddenFiles), - // C.bool(m.dialog.allowsMultipleSelection), - // C.bool(m.dialog.resolvesAliases), - // C.bool(m.dialog.hideExtension), - // C.bool(m.dialog.treatsFilePackagesAsDirectories), - // C.bool(m.dialog.allowsOtherFileTypes), - // toCString(filterPatterns), - // C.uint(len(filterPatterns)), - // toCString(m.dialog.message), - // toCString(m.dialog.directory), - // toCString(m.dialog.buttonText), - // nsWindow) - var result []string - for filename := range openFileResponses[m.dialog.id] { - result = append(result, filename) - } - return result, nil -} - -func openFileDialogCallback(id uint, path string) { - channel, ok := openFileResponses[id] - if ok { - channel <- path - } else { - panic("No channel found for open file dialog") - } -} - -func openFileDialogCallbackEnd(id uint) { - channel, ok := openFileResponses[id] - if ok { - close(channel) - delete(openFileResponses, id) - freeDialogID(id) - } else { - panic("No channel found for open file dialog") - } -} - -type linuxSaveFileDialog struct { - dialog *SaveFileDialog -} - -func newSaveFileDialogImpl(d *SaveFileDialog) *linuxSaveFileDialog { - return &linuxSaveFileDialog{ - dialog: d, - } -} - -func (m *linuxSaveFileDialog) show() (string, error) { - saveFileResponses[m.dialog.id] = make(chan string) - // nsWindow := unsafe.Pointer(nil) - if m.dialog.window != nil { - // get NSWindow from window - // nsWindow = m.dialog.window.impl.(*linuxWebviewWindow).nsWindow - } - - // C.showSaveFileDialog(C.uint(m.dialog.id), - // C.bool(m.dialog.canCreateDirectories), - // C.bool(m.dialog.showHiddenFiles), - // C.bool(m.dialog.canSelectHiddenExtension), - // C.bool(m.dialog.hideExtension), - // C.bool(m.dialog.treatsFilePackagesAsDirectories), - // C.bool(m.dialog.allowOtherFileTypes), - // toCString(m.dialog.message), - // toCString(m.dialog.directory), - // toCString(m.dialog.buttonText), - // toCString(m.dialog.filename), - // nsWindow) - return <-saveFileResponses[m.dialog.id], nil -} - -func saveFileDialogCallback(cid uint, path string) { - // put response on channel - channel, ok := saveFileResponses[cid] - if ok { - channel <- path - close(channel) - delete(saveFileResponses, cid) - freeDialogID(cid) - - } else { - panic("No channel found for save file dialog") - } -} diff --git a/v3/pkg/application/mainthread_linux.go b/v3/pkg/application/mainthread_linux.go deleted file mode 100644 index 670f01f9f..000000000 --- a/v3/pkg/application/mainthread_linux.go +++ /dev/null @@ -1,51 +0,0 @@ -//go:build linux - -package application - -/* -#cgo linux pkg-config: gtk+-3.0 - -#include -#include "gtk/gtk.h" - -typedef struct CallbackID -{ - unsigned int value; -} CallbackID; - -extern void dispatchOnMainThreadCallback(unsigned int); - -static gboolean dispatchCallback(gpointer data) { - struct CallbackID *args = data; - unsigned int cid = args->value; - dispatchOnMainThreadCallback(cid); - free(args); - - return G_SOURCE_REMOVE; -}; - -static void dispatchOnMainThread(unsigned int id) { - CallbackID *args = malloc(sizeof(CallbackID)); - args->value = id; - g_idle_add((GSourceFunc)dispatchCallback, (gpointer)args); -} - -*/ -import "C" - -func (m *linuxApp) dispatchOnMainThread(id uint) { - C.dispatchOnMainThread(C.uint(id)) -} - -//export dispatchOnMainThreadCallback -func dispatchOnMainThreadCallback(callbackID C.uint) { - mainThreadFunctionStoreLock.RLock() - id := uint(callbackID) - fn := mainThreadFunctionStore[id] - if fn == nil { - Fatal("dispatchCallback called with invalid id: %v", id) - } - delete(mainThreadFunctionStore, id) - mainThreadFunctionStoreLock.RUnlock() - fn() -} diff --git a/v3/pkg/application/mainthread_linux_purego.go b/v3/pkg/application/mainthread_linux_purego.go deleted file mode 100644 index fb69c96e3..000000000 --- a/v3/pkg/application/mainthread_linux_purego.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build linux && purego - -package application - -import "github.com/ebitengine/purego" - -const ( - G_SOURCE_REMOVE = 0 -) - -func (m *linuxApp) dispatchOnMainThread(id uint) { - var dispatch func(uintptr) - purego.RegisterLibFunc(&dispatch, gtk, "g_idle_add") - dispatch(purego.NewCallback(func(uintptr) int { - dispatchOnMainThreadCallback(id) - return G_SOURCE_REMOVE - })) -} - -func dispatchOnMainThreadCallback(callbackID uint) { - mainThreadFunctionStoreLock.RLock() - id := uint(callbackID) - fn := mainThreadFunctionStore[id] - if fn == nil { - Fatal("dispatchCallback called with invalid id: %v", id) - } - delete(mainThreadFunctionStore, id) - mainThreadFunctionStoreLock.RUnlock() - fn() -} diff --git a/v3/pkg/application/menu_linux.go b/v3/pkg/application/menu_linux.go deleted file mode 100644 index e0c0118a9..000000000 --- a/v3/pkg/application/menu_linux.go +++ /dev/null @@ -1,200 +0,0 @@ -//go:build linux - -package application - -/* -#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 - -#include -#include - -void handleClick(void*); -*/ -import "C" -import ( - "fmt" - "unsafe" -) - -var ( - gtkSignalHandlers map[*C.GtkWidget]C.gulong - gtkSignalToMenuItem map[*C.GtkWidget]*MenuItem -) - -func init() { - gtkSignalHandlers = map[*C.GtkWidget]C.gulong{} - gtkSignalToMenuItem = map[*C.GtkWidget]*MenuItem{} -} - -//export handleClick -func handleClick(idPtr unsafe.Pointer) { - id := (*C.GtkWidget)(idPtr) - item, ok := gtkSignalToMenuItem[id] - if !ok { - return - } - - //impl := (item.impl).(*linuxMenuItem) - - switch item.itemType { - case text, checkbox: - processMenuItemClick(C.uint(item.id)) - case radio: - menuItem := (item.impl).(*linuxMenuItem) - if menuItem.isChecked() { - processMenuItemClick(C.uint(item.id)) - } - } -} - -type linuxMenu struct { - menu *Menu - native unsafe.Pointer -} - -func newMenuImpl(menu *Menu) *linuxMenu { - result := &linuxMenu{ - menu: menu, - native: unsafe.Pointer(C.gtk_menu_bar_new()), - } - return result -} - -func (m *linuxMenu) update() { - // fmt.Println("linuxMenu.update()") - // if m.native != nil { - // C.gtk_widget_destroy((*C.GtkWidget)(m.native)) - // m.native = unsafe.Pointer(C.gtk_menu_new()) - // } - m.processMenu(m.menu) -} - -func (m *linuxMenu) processMenu(menu *Menu) { - if menu.impl == nil { - menu.impl = &linuxMenu{ - menu: menu, - native: unsafe.Pointer(C.gtk_menu_new()), - } - } - var currentRadioGroup *C.GSList - - for _, item := range menu.items { - // drop the group if we have run out of radio items - if item.itemType != radio { - currentRadioGroup = nil - } - - switch item.itemType { - case submenu: - menuItem := newMenuItemImpl(item) - item.impl = menuItem - m.processMenu(item.submenu) - m.addSubMenuToItem(item.submenu, item) - m.addMenuItem(menu, item) - case text, checkbox: - menuItem := newMenuItemImpl(item) - item.impl = menuItem - m.addMenuItem(menu, item) - case radio: - menuItem := newRadioItemImpl(item, currentRadioGroup) - item.impl = menuItem - m.addMenuItem(menu, item) - currentRadioGroup = C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(menuItem.native)) - case separator: - m.addMenuSeparator(menu) - } - - } - - for _, item := range menu.items { - if item.callback != nil { - m.attachHandler(item) - } - } - -} - -func (m *linuxMenu) attachHandler(item *MenuItem) { - signal := C.CString("activate") - defer C.free(unsafe.Pointer(signal)) - - impl := (item.impl).(*linuxMenuItem) - widget := impl.native - flags := C.GConnectFlags(0) - handlerId := C.g_signal_connect_object( - C.gpointer(widget), - signal, - C.GCallback(C.handleClick), - C.gpointer(widget), - flags) - - id := (*C.GtkWidget)(widget) - gtkSignalToMenuItem[id] = item - gtkSignalHandlers[id] = handlerId - impl.handlerId = handlerId -} - -func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) { - if menu.impl == nil { - menu.impl = &linuxMenu{ - menu: menu, - native: unsafe.Pointer(C.gtk_menu_new()), - } - } - - C.gtk_menu_item_set_submenu( - (*C.GtkMenuItem)((item.impl).(*linuxMenuItem).native), - (*C.GtkWidget)((menu.impl).(*linuxMenu).native)) - - if item.role == ServicesMenu { - // FIXME: what does this mean? - } -} - -func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) { - // fmt.Println("addMenuIteam", fmt.Sprintf("%+v", parent), fmt.Sprintf("%+v", menu)) - C.gtk_menu_shell_append( - (*C.GtkMenuShell)((parent.impl).(*linuxMenu).native), - (*C.GtkWidget)((menu.impl).(*linuxMenuItem).native), - ) - /* - C.gtk_menu_item_set_submenu( - (*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native), - (*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native), - ) - */ -} - -func (m *linuxMenu) addMenuSeparator(menu *Menu) { - // fmt.Println("addMenuSeparator", fmt.Sprintf("%+v", menu)) - sep := C.gtk_separator_menu_item_new() - native := (menu.impl).(*linuxMenu).native - C.gtk_menu_shell_append((*C.GtkMenuShell)(native), sep) -} - -func (m *linuxMenu) addServicesMenu(menu *Menu) { - fmt.Println("addServicesMenu - not implemented") - //C.addServicesMenu(unsafe.Pointer(menu.impl.(*linuxMenu).nsMenu)) -} - -func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu { - impl := newMenuImpl(&Menu{label: name}) - menu := &Menu{ - label: name, - items: items, - impl: impl, - } - impl.menu = menu - return menu -} - -func defaultApplicationMenu() *Menu { - menu := NewMenu() - menu.AddRole(AppMenu) - menu.AddRole(FileMenu) - menu.AddRole(EditMenu) - menu.AddRole(ViewMenu) - menu.AddRole(WindowMenu) - menu.AddRole(HelpMenu) - return menu -} diff --git a/v3/pkg/application/menu_linux_purego.go b/v3/pkg/application/menu_linux_purego.go deleted file mode 100644 index 601d09b5f..000000000 --- a/v3/pkg/application/menu_linux_purego.go +++ /dev/null @@ -1,179 +0,0 @@ -//go:build linux && purego - -package application - -import ( - "fmt" - - "github.com/ebitengine/purego" -) - -type linuxMenu struct { - menu *Menu - native uintptr -} - -func newMenuImpl(menu *Menu) *linuxMenu { - var newMenuBar func() uintptr - purego.RegisterLibFunc(&newMenuBar, gtk, "gtk_menu_bar_new") - result := &linuxMenu{ - menu: menu, - native: newMenuBar(), - } - return result -} - -func (m *linuxMenu) update() { - m.processMenu(m.menu) -} - -func (m *linuxMenu) processMenu(menu *Menu) { - var newMenu func() uintptr - purego.RegisterLibFunc(&newMenu, gtk, "gtk_menu_new") - if menu.impl == nil { - menu.impl = &linuxMenu{ - menu: menu, - native: newMenu(), - } - } - var currentRadioGroup uintptr - - for _, item := range menu.items { - // drop the group if we have run out of radio items - if item.itemType != radio { - currentRadioGroup = 0 - } - - switch item.itemType { - case submenu: - menuItem := newMenuItemImpl(item) - item.impl = menuItem - m.processMenu(item.submenu) - m.addSubMenuToItem(item.submenu, item) - m.addMenuItem(menu, item) - case text, checkbox: - menuItem := newMenuItemImpl(item) - item.impl = menuItem - m.addMenuItem(menu, item) - case radio: - menuItem := newRadioItemImpl(item, currentRadioGroup) - item.impl = menuItem - m.addMenuItem(menu, item) - - var radioGetGroup func(uintptr) uintptr - purego.RegisterLibFunc(&radioGetGroup, gtk, "gtk_radio_menu_item_get_group") - - currentRadioGroup = radioGetGroup(menuItem.native) - case separator: - m.addMenuSeparator(menu) - } - - } - - for _, item := range menu.items { - if item.callback != nil { - m.attachHandler(item) - } - } - -} - -func (m *linuxMenu) attachHandler(item *MenuItem) { - impl := (item.impl).(*linuxMenuItem) - widget := impl.native - flags := 0 - - var handleClick = func() { - item := item - switch item.itemType { - case text, checkbox: - processMenuItemClick(item.id) - case radio: - menuItem := (item.impl).(*linuxMenuItem) - if menuItem.isChecked() { - processMenuItemClick(item.id) - } - default: - fmt.Println("handleClick", item.itemType, item.id) - } - } - - var signalConnectObject func(uintptr, string, uintptr, uintptr, int) uint - purego.RegisterLibFunc(&signalConnectObject, gtk, "g_signal_connect_object") - handlerId := signalConnectObject( - widget, - "activate", - purego.NewCallback(handleClick), - widget, - flags) - - impl.handlerId = handlerId -} - -func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) { - var newMenu func() uintptr - purego.RegisterLibFunc(&newMenu, gtk, "gtk_menu_new") - if menu.impl == nil { - menu.impl = &linuxMenu{ - menu: menu, - native: newMenu(), -N } - } - var itemSetSubmenu func(uintptr, uintptr) - purego.RegisterLibFunc(&itemSetSubmenu, gtk, "gtk_menu_item_set_submenu") - - itemSetSubmenu( - (item.impl).(*linuxMenuItem).native, - (menu.impl).(*linuxMenu).native) - - if item.role == ServicesMenu { - // FIXME: what does this mean? - } -} - -func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) { - var shellAppend func(uintptr, uintptr) - purego.RegisterLibFunc(&shellAppend, gtk, "gtk_menu_shell_append") - shellAppend( - (parent.impl).(*linuxMenu).native, - (menu.impl).(*linuxMenuItem).native, - ) -} - -func (m *linuxMenu) addMenuSeparator(menu *Menu) { - var newSeparator func() uintptr - purego.RegisterLibFunc(&newSeparator, gtk, "gtk_separator_menu_item_new") - var shellAppend func(uintptr, uintptr) - purego.RegisterLibFunc(&shellAppend, gtk, "gtk_menu_shell_append") - - sep := newSeparator() - native := (menu.impl).(*linuxMenu).native - shellAppend(native, sep) -} - -func (m *linuxMenu) addServicesMenu(menu *Menu) { - fmt.Println("addServicesMenu - not implemented") - //C.addServicesMenu(unsafe.Pointer(menu.impl.(*linuxMenu).nsMenu)) -} - -func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu { - impl := newMenuImpl(&Menu{label: name}) - menu := &Menu{ - label: name, - items: items, - impl: impl, - } - impl.menu = menu - return menu -} - -func defaultApplicationMenu() *Menu { - menu := NewMenu() - menu.AddRole(AppMenu) - menu.AddRole(FileMenu) - menu.AddRole(EditMenu) - menu.AddRole(ViewMenu) - menu.AddRole(WindowMenu) - menu.AddRole(HelpMenu) - return menu -} diff --git a/v3/pkg/application/menuitem.go b/v3/pkg/application/menuitem.go index 74ed4ada4..e30750781 100644 --- a/v3/pkg/application/menuitem.go +++ b/v3/pkg/application/menuitem.go @@ -203,9 +203,7 @@ func (m *MenuItem) handleClick() { if m.itemType == radio { for _, member := range m.radioGroupMembers { member.checked = false - if member.impl != nil { - member.impl.setChecked(false) - } + member.impl.setChecked(false) } m.checked = true ctx.withChecked(true) diff --git a/v3/pkg/application/menuitem_linux.go b/v3/pkg/application/menuitem_linux.go deleted file mode 100644 index d67a02729..000000000 --- a/v3/pkg/application/menuitem_linux.go +++ /dev/null @@ -1,407 +0,0 @@ -//go:build linux - -package application - -import ( - "fmt" - "runtime" - "unsafe" -) - -/* -#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 - -#include -#include "gtk/gtk.h" - - - -*/ -import "C" - -type linuxMenuItem struct { - menuItem *MenuItem - native unsafe.Pointer - handlerId C.gulong -} - -func (l linuxMenuItem) setTooltip(tooltip string) { - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - - value := C.CString(tooltip) - C.gtk_widget_set_tooltip_text( - (*C.GtkWidget)(l.native), - value) - C.free(unsafe.Pointer(value)) - }) -} - -func (l linuxMenuItem) blockSignal() { - if l.handlerId != 0 { - C.g_signal_handler_block(C.gpointer(l.native), l.handlerId) - } -} - -func (l linuxMenuItem) unBlockSignal() { - if l.handlerId != 0 { - C.g_signal_handler_unblock(C.gpointer(l.native), l.handlerId) - } -} - -func (l linuxMenuItem) setLabel(s string) { - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - value := C.CString(s) - C.gtk_menu_item_set_label( - (*C.GtkMenuItem)(l.native), - value) - C.free(unsafe.Pointer(value)) - - }) -} - -func (l linuxMenuItem) isChecked() bool { - if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(l.native)) == C.int(1) { - return true - } - return false -} - -func (l linuxMenuItem) setDisabled(disabled bool) { - - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - - value := C.int(1) - if disabled { - value = C.int(0) - } - C.gtk_widget_set_sensitive( - (*C.GtkWidget)(l.native), - value) - }) -} - -func (l linuxMenuItem) setChecked(checked bool) { - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - - value := C.int(0) - if checked { - value = C.int(1) - } - - C.gtk_check_menu_item_set_active( - (*C.GtkCheckMenuItem)(l.native), - value) - }) -} - -func (l linuxMenuItem) setAccelerator(accelerator *accelerator) { - fmt.Println("setAccelerator", accelerator) - // Set the keyboard shortcut of the menu item - // var modifier C.int - // var key *C.char - if accelerator != nil { - // modifier = C.int(toMacModifier(accelerator.Modifiers)) - // key = C.CString(accelerator.Key) - } - - // Convert the key to a string - // C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) -} - -func newMenuItemImpl(item *MenuItem) *linuxMenuItem { - result := &linuxMenuItem{ - menuItem: item, - } - cLabel := C.CString(item.label) - switch item.itemType { - case text: - result.native = unsafe.Pointer(C.gtk_menu_item_new_with_label(cLabel)) - - case checkbox: - result.native = unsafe.Pointer(C.gtk_check_menu_item_new_with_label(cLabel)) - result.setChecked(item.checked) - if item.itemType == checkbox || item.itemType == radio { - // C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked)) - } - if item.accelerator != nil { - result.setAccelerator(item.accelerator) - } - case radio: - panic("Shouldn't get here with a radio item") - - case submenu: - result.native = unsafe.Pointer(C.gtk_menu_item_new_with_label(cLabel)) - - default: - panic("WTF") - } - result.setDisabled(result.menuItem.disabled) - - C.free(unsafe.Pointer(cLabel)) - return result -} - -func newRadioItemImpl(item *MenuItem, group *C.GSList) *linuxMenuItem { - cLabel := C.CString(item.label) - defer C.free(unsafe.Pointer(cLabel)) - result := &linuxMenuItem{ - menuItem: item, - native: unsafe.Pointer(C.gtk_radio_menu_item_new_with_label(group, cLabel)), - } - result.setChecked(item.checked) - result.setDisabled(result.menuItem.disabled) - return result -} - -func newSpeechMenu() *MenuItem { - speechMenu := NewMenu() - speechMenu.Add("Start Speaking"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+."). - OnClick(func(ctx *Context) { - // C.startSpeaking() - }) - speechMenu.Add("Stop Speaking"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,"). - OnClick(func(ctx *Context) { - // C.stopSpeaking() - }) - subMenu := newSubMenuItem("Speech") - subMenu.submenu = speechMenu - return subMenu -} - -func newHideMenuItem() *MenuItem { - return newMenuItem("Hide " + globalApplication.options.Name). - SetAccelerator("CmdOrCtrl+h"). - OnClick(func(ctx *Context) { - // C.hideApplication() - }) -} - -func newHideOthersMenuItem() *MenuItem { - return newMenuItem("Hide Others"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). - OnClick(func(ctx *Context) { - // C.hideOthers() - }) -} - -func newUnhideMenuItem() *MenuItem { - return newMenuItem("Show All"). - OnClick(func(ctx *Context) { - // C.showAll() - }) -} - -func newUndoMenuItem() *MenuItem { - return newMenuItem("Undo"). - SetAccelerator("CmdOrCtrl+z"). - OnClick(func(ctx *Context) { - // C.undo() - }) -} - -// newRedoMenuItem creates a new menu item for redoing the last action -func newRedoMenuItem() *MenuItem { - return newMenuItem("Redo"). - SetAccelerator("CmdOrCtrl+Shift+z"). - OnClick(func(ctx *Context) { - // C.redo() - }) -} - -func newCutMenuItem() *MenuItem { - return newMenuItem("Cut"). - SetAccelerator("CmdOrCtrl+x"). - OnClick(func(ctx *Context) { - // C.cut() - }) -} - -func newCopyMenuItem() *MenuItem { - return newMenuItem("Copy"). - SetAccelerator("CmdOrCtrl+c"). - OnClick(func(ctx *Context) { - // C.copy() - }) -} - -func newPasteMenuItem() *MenuItem { - return newMenuItem("Paste"). - SetAccelerator("CmdOrCtrl+v"). - OnClick(func(ctx *Context) { - // C.paste() - }) -} - -func newPasteAndMatchStyleMenuItem() *MenuItem { - return newMenuItem("Paste and Match Style"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v"). - OnClick(func(ctx *Context) { - // C.pasteAndMatchStyle() - }) -} - -func newDeleteMenuItem() *MenuItem { - return newMenuItem("Delete"). - SetAccelerator("backspace"). - OnClick(func(ctx *Context) { - // C.delete() - }) -} - -func newQuitMenuItem() *MenuItem { - return newMenuItem("Quit " + globalApplication.options.Name). - SetAccelerator("CmdOrCtrl+q"). - OnClick(func(ctx *Context) { - globalApplication.Quit() - }) -} - -func newSelectAllMenuItem() *MenuItem { - return newMenuItem("Select All"). - SetAccelerator("CmdOrCtrl+a"). - OnClick(func(ctx *Context) { - // C.selectAll() - }) -} - -func newAboutMenuItem() *MenuItem { - return newMenuItem("About " + globalApplication.options.Name). - OnClick(func(ctx *Context) { - globalApplication.ShowAboutDialog() - }) -} - -func newCloseMenuItem() *MenuItem { - return newMenuItem("Close"). - SetAccelerator("CmdOrCtrl+w"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Close() - } - }) -} - -func newReloadMenuItem() *MenuItem { - return newMenuItem("Reload"). - SetAccelerator("CmdOrCtrl+r"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Reload() - } - }) -} - -func newForceReloadMenuItem() *MenuItem { - return newMenuItem("Force Reload"). - SetAccelerator("CmdOrCtrl+Shift+r"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ForceReload() - } - }) -} - -func newToggleFullscreenMenuItem() *MenuItem { - result := newMenuItem("Toggle Full Screen"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ToggleFullscreen() - } - }) - if runtime.GOOS == "darwin" { - result.SetAccelerator("Ctrl+Command+F") - } else { - result.SetAccelerator("F11") - } - return result -} - -func newToggleDevToolsMenuItem() *MenuItem { - return newMenuItem("Toggle Developer Tools"). - SetAccelerator("Alt+Command+I"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ToggleDevTools() - } - }) -} - -func newZoomResetMenuItem() *MenuItem { - // reset zoom menu item - return newMenuItem("Actual Size"). - SetAccelerator("CmdOrCtrl+0"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ZoomReset() - } - }) -} - -func newZoomInMenuItem() *MenuItem { - return newMenuItem("Zoom In"). - SetAccelerator("CmdOrCtrl+plus"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ZoomIn() - } - }) -} - -func newZoomOutMenuItem() *MenuItem { - return newMenuItem("Zoom Out"). - SetAccelerator("CmdOrCtrl+-"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ZoomOut() - } - }) -} - -func newMinimizeMenuItem() *MenuItem { - return newMenuItem("Minimize"). - SetAccelerator("CmdOrCtrl+M"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Minimise() - } - }) -} - -func newZoomMenuItem() *MenuItem { - return newMenuItem("Zoom"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Zoom() - } - }) -} - -func newFullScreenMenuItem() *MenuItem { - return newMenuItem("Fullscreen"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Fullscreen() - } - }) -} diff --git a/v3/pkg/application/menuitem_linux_purego.go b/v3/pkg/application/menuitem_linux_purego.go deleted file mode 100644 index be51f09e8..000000000 --- a/v3/pkg/application/menuitem_linux_purego.go +++ /dev/null @@ -1,403 +0,0 @@ -//go:build linux && purego - -package application - -import ( - "fmt" - "runtime" - - "github.com/ebitengine/purego" -) - -type linuxMenuItem struct { - menuItem *MenuItem - native uintptr - handlerId uint -} - -func (l linuxMenuItem) setTooltip(tooltip string) { - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - var setToolTip func(uintptr, string) - purego.RegisterLibFunc(&setToolTip, gtk, "gtk_widget_set_tooltip_text") - - setToolTip(l.native, tooltip) - }) -} - -func (l linuxMenuItem) blockSignal() { - var block func(uintptr, uint) - purego.RegisterLibFunc(&block, gtk, "g_signal_handler_block") - - if l.handlerId != 0 { - block(l.native, l.handlerId) - } -} - -func (l linuxMenuItem) unBlockSignal() { - var unblock func(uintptr, uint) - purego.RegisterLibFunc(&unblock, gtk, "g_signal_handler_unblock") - - if l.handlerId != 0 { - unblock(l.native, l.handlerId) - } -} - -func (l linuxMenuItem) setLabel(s string) { - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - var setLabel func(uintptr, string) - purego.RegisterLibFunc(&setLabel, gtk, "gtk_menu_item_set_label") - setLabel(l.native, s) - }) -} - -func (l linuxMenuItem) isChecked() bool { - var getActive func(uintptr) int - purego.RegisterLibFunc(&getActive, gtk, "gtk_check_menu_item_get_active") - - if getActive(l.native) == 1 { - return true - } - return false -} - -func (l linuxMenuItem) setDisabled(disabled bool) { - - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - - var setSensitive func(uintptr, int) - purego.RegisterLibFunc(&setSensitive, gtk, "gtk_widget_set_sensitive") - - value := 1 - if disabled { - value = 0 - } - setSensitive(l.native, value) - }) -} - -func (l linuxMenuItem) setChecked(checked bool) { - globalApplication.dispatchOnMainThread(func() { - l.blockSignal() - defer l.unBlockSignal() - - var setActive func(uintptr, int) - purego.RegisterLibFunc(&setActive, gtk, "gtk_check_menu_item_set_active") - - value := 0 - if checked { - value = 1 - } - setActive(l.native, value) - }) -} - -func (l linuxMenuItem) setAccelerator(accelerator *accelerator) { - fmt.Println("setAccelerator", accelerator) - // Set the keyboard shortcut of the menu item - // var modifier C.int - // var key *C.char - if accelerator != nil { - // modifier = C.int(toMacModifier(accelerator.Modifiers)) - // key = C.CString(accelerator.Key) - } - - // Convert the key to a string - // C.setMenuItemKeyEquivalent(m.nsMenuItem, key, modifier) -} - -func newMenuItemImpl(item *MenuItem) *linuxMenuItem { - result := &linuxMenuItem{ - menuItem: item, - } - var newWithLabel func(string) uintptr - purego.RegisterLibFunc(&newWithLabel, gtk, "gtk_menu_item_new_with_label") - var newCBWithLabel func(string) uintptr - purego.RegisterLibFunc(&newCBWithLabel, gtk, "gtk_check_menu_item_new_with_label") - - switch item.itemType { - case text: - result.native = newWithLabel(item.label) - - case checkbox: - result.native = newCBWithLabel(item.label) - result.setChecked(item.checked) - if item.accelerator != nil { - result.setAccelerator(item.accelerator) - } - case radio: - panic("Shouldn't get here with a radio item") - - case submenu: - result.native = newWithLabel(item.label) - - default: - panic("WTF") - } - result.setDisabled(result.menuItem.disabled) - - return result -} - -func newRadioItemImpl(item *MenuItem, group uintptr) *linuxMenuItem { - var newWithLabel func(uintptr, string) uintptr - purego.RegisterLibFunc(&newWithLabel, gtk, "gtk_radio_menu_item_new_with_label") - - result := &linuxMenuItem{ - menuItem: item, - native: newWithLabel(group, item.label), - } - result.setChecked(item.checked) - result.setDisabled(result.menuItem.disabled) - return result -} - -func newSpeechMenu() *MenuItem { - speechMenu := NewMenu() - speechMenu.Add("Start Speaking"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+."). - OnClick(func(ctx *Context) { - // C.startSpeaking() - }) - speechMenu.Add("Stop Speaking"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+,"). - OnClick(func(ctx *Context) { - // C.stopSpeaking() - }) - subMenu := newSubMenuItem("Speech") - subMenu.submenu = speechMenu - return subMenu -} - -func newHideMenuItem() *MenuItem { - return newMenuItem("Hide " + globalApplication.options.Name). - SetAccelerator("CmdOrCtrl+h"). - OnClick(func(ctx *Context) { - // C.hideApplication() - }) -} - -func newHideOthersMenuItem() *MenuItem { - return newMenuItem("Hide Others"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+h"). - OnClick(func(ctx *Context) { - // C.hideOthers() - }) -} - -func newUnhideMenuItem() *MenuItem { - return newMenuItem("Show All"). - OnClick(func(ctx *Context) { - // C.showAll() - }) -} - -func newUndoMenuItem() *MenuItem { - return newMenuItem("Undo"). - SetAccelerator("CmdOrCtrl+z"). - OnClick(func(ctx *Context) { - // C.undo() - }) -} - -// newRedoMenuItem creates a new menu item for redoing the last action -func newRedoMenuItem() *MenuItem { - return newMenuItem("Redo"). - SetAccelerator("CmdOrCtrl+Shift+z"). - OnClick(func(ctx *Context) { - // C.redo() - }) -} - -func newCutMenuItem() *MenuItem { - return newMenuItem("Cut"). - SetAccelerator("CmdOrCtrl+x"). - OnClick(func(ctx *Context) { - // C.cut() - }) -} - -func newCopyMenuItem() *MenuItem { - return newMenuItem("Copy"). - SetAccelerator("CmdOrCtrl+c"). - OnClick(func(ctx *Context) { - // C.copy() - }) -} - -func newPasteMenuItem() *MenuItem { - return newMenuItem("Paste"). - SetAccelerator("CmdOrCtrl+v"). - OnClick(func(ctx *Context) { - // C.paste() - }) -} - -func newPasteAndMatchStyleMenuItem() *MenuItem { - return newMenuItem("Paste and Match Style"). - SetAccelerator("CmdOrCtrl+OptionOrAlt+Shift+v"). - OnClick(func(ctx *Context) { - // C.pasteAndMatchStyle() - }) -} - -func newDeleteMenuItem() *MenuItem { - return newMenuItem("Delete"). - SetAccelerator("backspace"). - OnClick(func(ctx *Context) { - // C.delete() - }) -} - -func newQuitMenuItem() *MenuItem { - return newMenuItem("Quit " + globalApplication.options.Name). - SetAccelerator("CmdOrCtrl+q"). - OnClick(func(ctx *Context) { - globalApplication.Quit() - }) -} - -func newSelectAllMenuItem() *MenuItem { - return newMenuItem("Select All"). - SetAccelerator("CmdOrCtrl+a"). - OnClick(func(ctx *Context) { - // C.selectAll() - }) -} - -func newAboutMenuItem() *MenuItem { - return newMenuItem("About " + globalApplication.options.Name). - OnClick(func(ctx *Context) { - globalApplication.ShowAboutDialog() - }) -} - -func newCloseMenuItem() *MenuItem { - return newMenuItem("Close"). - SetAccelerator("CmdOrCtrl+w"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Close() - } - }) -} - -func newReloadMenuItem() *MenuItem { - return newMenuItem("Reload"). - SetAccelerator("CmdOrCtrl+r"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Reload() - } - }) -} - -func newForceReloadMenuItem() *MenuItem { - return newMenuItem("Force Reload"). - SetAccelerator("CmdOrCtrl+Shift+r"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ForceReload() - } - }) -} - -func newToggleFullscreenMenuItem() *MenuItem { - result := newMenuItem("Toggle Full Screen"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ToggleFullscreen() - } - }) - if runtime.GOOS == "darwin" { - result.SetAccelerator("Ctrl+Command+F") - } else { - result.SetAccelerator("F11") - } - return result -} - -func newToggleDevToolsMenuItem() *MenuItem { - return newMenuItem("Toggle Developer Tools"). - SetAccelerator("Alt+Command+I"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ToggleDevTools() - } - }) -} - -func newZoomResetMenuItem() *MenuItem { - // reset zoom menu item - return newMenuItem("Actual Size"). - SetAccelerator("CmdOrCtrl+0"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ZoomReset() - } - }) -} - -func newZoomInMenuItem() *MenuItem { - return newMenuItem("Zoom In"). - SetAccelerator("CmdOrCtrl+plus"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ZoomIn() - } - }) -} - -func newZoomOutMenuItem() *MenuItem { - return newMenuItem("Zoom Out"). - SetAccelerator("CmdOrCtrl+-"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.ZoomOut() - } - }) -} - -func newMinimizeMenuItem() *MenuItem { - return newMenuItem("Minimize"). - SetAccelerator("CmdOrCtrl+M"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Minimise() - } - }) -} - -func newZoomMenuItem() *MenuItem { - return newMenuItem("Zoom"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Zoom() - } - }) -} - -func newFullScreenMenuItem() *MenuItem { - return newMenuItem("Fullscreen"). - OnClick(func(ctx *Context) { - currentWindow := globalApplication.CurrentWindow() - if currentWindow != nil { - currentWindow.Fullscreen() - } - }) -} diff --git a/v3/pkg/application/messageprocessor_call.go b/v3/pkg/application/messageprocessor_call.go index 0e717994b..51dcc9423 100644 --- a/v3/pkg/application/messageprocessor_call.go +++ b/v3/pkg/application/messageprocessor_call.go @@ -4,16 +4,19 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" ) func (m *MessageProcessor) callErrorCallback(window *WebviewWindow, message string, callID *string, err error) { errorMsg := fmt.Sprintf(message, err) m.Error(errorMsg) - window.CallError(callID, errorMsg) + msg := "_wails.callErrorCallback('" + *callID + "', " + strconv.Quote(errorMsg) + ");" + window.ExecJS(msg) } func (m *MessageProcessor) callCallback(window *WebviewWindow, callID *string, result string, isJSON bool) { - window.CallResponse(callID, result) + msg := fmt.Sprintf("_wails.callCallback('%s', %s, %v);", *callID, strconv.Quote(result), isJSON) + window.ExecJS(msg) } func (m *MessageProcessor) processCallMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) { diff --git a/v3/pkg/application/messageprocessor_dialog.go b/v3/pkg/application/messageprocessor_dialog.go index 0810d8138..83389c9a3 100644 --- a/v3/pkg/application/messageprocessor_dialog.go +++ b/v3/pkg/application/messageprocessor_dialog.go @@ -5,16 +5,19 @@ import ( "fmt" "net/http" "runtime" + "strconv" ) func (m *MessageProcessor) dialogErrorCallback(window *WebviewWindow, message string, dialogID *string, err error) { errorMsg := fmt.Sprintf(message, err) m.Error(errorMsg) - window.DialogError(dialogID, errorMsg) + msg := "_wails.dialogErrorCallback('" + *dialogID + "', " + strconv.Quote(errorMsg) + ");" + window.ExecJS(msg) } func (m *MessageProcessor) dialogCallback(window *WebviewWindow, dialogID *string, result string, isJSON bool) { - window.DialogResponse(dialogID, result) + msg := fmt.Sprintf("_wails.dialogCallback('%s', %s, %v);", *dialogID, strconv.Quote(result), isJSON) + window.ExecJS(msg) } func (m *MessageProcessor) processDialogMethod(method string, rw http.ResponseWriter, r *http.Request, window *WebviewWindow, params QueryParams) { diff --git a/v3/pkg/application/options_linux.go b/v3/pkg/application/options_linux.go deleted file mode 100644 index bf3d0c308..000000000 --- a/v3/pkg/application/options_linux.go +++ /dev/null @@ -1,6 +0,0 @@ -package application - -// LinuxWindow contains macOS specific options -type LinuxWindow struct { - ShowApplicationMenu bool -} diff --git a/v3/pkg/application/screen_linux.go b/v3/pkg/application/screen_linux.go deleted file mode 100644 index 585f7db75..000000000 --- a/v3/pkg/application/screen_linux.go +++ /dev/null @@ -1,94 +0,0 @@ -//go:build linux - -package application - -/* -#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 - -#include -#include -#include -#include - -typedef struct Screen { - const char* id; - const char* name; - int p_width; - int p_height; - int width; - int height; - int x; - int y; - int w_width; - int w_height; - int w_x; - int w_y; - float scale; - double rotation; - bool isPrimary; -} Screen; - - -int GetNumScreens(){ - return 0; -} - -*/ -import "C" -import ( - "fmt" - "sync" - "unsafe" -) - -func (m *linuxApp) getPrimaryScreen() (*Screen, error) { - return nil, fmt.Errorf("not implemented") -} - -func (m *linuxApp) getScreenByIndex(display *C.struct__GdkDisplay, index int) *Screen { - monitor := C.gdk_display_get_monitor(display, C.int(index)) - - // TODO: Do we need to update Screen to contain current info? - // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) - - var geometry C.GdkRectangle - C.gdk_monitor_get_geometry(monitor, &geometry) - primary := false - if C.gdk_monitor_is_primary(monitor) == 1 { - primary = true - } - - return &Screen{ - IsPrimary: primary, - Scale: 1.0, - X: int(geometry.x), - Y: int(geometry.y), - Size: Size{ - Height: int(geometry.height), - Width: int(geometry.width), - }, - } -} - -func (m *linuxApp) getScreens() ([]*Screen, error) { - var wg sync.WaitGroup - var screens []*Screen - wg.Add(1) - globalApplication.dispatchOnMainThread(func() { - window := C.gtk_application_get_active_window((*C.GtkApplication)(m.application)) - display := C.gdk_window_get_display((*C.GdkWindow)(unsafe.Pointer(window))) - count := C.gdk_display_get_n_monitors(display) - for i := 0; i < int(count); i++ { - screens = append(screens, - m.getScreenByIndex(display, i), - ) - } - wg.Done() - }) - wg.Wait() - return screens, nil -} - -func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) { - return window.getScreen() -} diff --git a/v3/pkg/application/screen_linux_purego.go b/v3/pkg/application/screen_linux_purego.go deleted file mode 100644 index 6f7add617..000000000 --- a/v3/pkg/application/screen_linux_purego.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build linux && purego - -package application - -import ( - "fmt" - "sync" - "unsafe" - - "github.com/ebitengine/purego" -) - -func (m *linuxApp) getPrimaryScreen() (*Screen, error) { - return nil, fmt.Errorf("not implemented") -} - -func (m *linuxApp) getScreenByIndex(display uintptr, index int) *Screen { - fmt.Println("getScreenByIndex") - var getMonitor func(uintptr, int) uintptr - purego.RegisterLibFunc(&getMonitor, gtk, "gdk_display_get_monitor") - - monitor := getMonitor(display, index) - - // TODO: Do we need to update Screen to contain current info? - // currentMonitor := C.gdk_display_get_monitor_at_window(display, window) - - var getGeometry func(uintptr, uintptr) - purego.RegisterLibFunc(&getGeometry, gtk, "gdk_monitor_get_geometry") - - //var geometry C.GdkRectangle - /* - struct GdkRectangle { - int x; - int y; - int width; - int height; - } - */ - geometry := make([]byte, 16) - getGeometry(monitor, uintptr(unsafe.Pointer(&geometry[0]))) - fmt.Println("geometry: %v\n", geometry) - - var isPrimary func(uintptr) int - purego.RegisterLibFunc(&isPrimary, gtk, "gdk_monitor_is_primary") - - primary := false - if isPrimary(monitor) == 1 { - primary = true - } - - return &Screen{ - IsPrimary: primary, - Scale: 1.0, - X: 0, //int(geometry.x), - Y: 0, //int(geometry.y), - Size: Size{ - Height: 1024, //int(geometry.height), - Width: 1024, //int(geometry.width), - }, - } -} - -func (m *linuxApp) getScreens() ([]*Screen, error) { - fmt.Println("getScreens") - var wg sync.WaitGroup - var screens []*Screen - wg.Add(1) - - var getWindow func(uintptr) uintptr - purego.RegisterLibFunc(&getWindow, gtk, "gtk_application_get_active_window") - var getDisplay func(uintptr) uintptr - purego.RegisterLibFunc(&getDisplay, gtk, "gdk_window_get_display") - var getMonitorCount func(uintptr) int - purego.RegisterLibFunc(&getMonitorCount, gtk, "getNMonitors") - globalApplication.dispatchOnMainThread(func() { - window := getWindow(m.application) - display := getDisplay(window) - count := getMonitorCount(display) - for i := 0; i < int(count); i++ { - screens = append(screens, - m.getScreenByIndex(display, i), - ) - } - wg.Done() - }) - wg.Wait() - return screens, nil -} - -func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) { - return window.getScreen() -} diff --git a/v3/pkg/application/systemtray_darwin.go b/v3/pkg/application/systemtray_darwin.go index 5e68e312c..fdf125a90 100644 --- a/v3/pkg/application/systemtray_darwin.go +++ b/v3/pkg/application/systemtray_darwin.go @@ -130,10 +130,6 @@ func (s *macosSystemTray) setIcon(icon []byte) { }) } -func (s *macosSystemTray) setDarkModeIcon(icon []byte) { - s.setIcon(icon) -} - func (s *macosSystemTray) setTemplateIcon(icon []byte) { s.icon = icon s.isTemplateIcon = true diff --git a/v3/pkg/application/systemtray_linux.go b/v3/pkg/application/systemtray_linux.go deleted file mode 100644 index 8cc74460a..000000000 --- a/v3/pkg/application/systemtray_linux.go +++ /dev/null @@ -1,90 +0,0 @@ -//go:build linux - -package application - -type linuxSystemTray struct { - id uint - label string - icon []byte - menu *Menu - - iconPosition int - isTemplateIcon bool -} - -func (s *linuxSystemTray) setIconPosition(position int) { - s.iconPosition = position -} - -func (s *linuxSystemTray) setMenu(menu *Menu) { - s.menu = menu -} - -func (s *linuxSystemTray) run() { - globalApplication.dispatchOnMainThread(func() { - // if s.nsStatusItem != nil { - // Fatal("System tray '%d' already running", s.id) - // } - // s.nsStatusItem = unsafe.Pointer(C.systemTrayNew()) - if s.label != "" { - // C.systemTraySetLabel(s.nsStatusItem, C.CString(s.label)) - } - if s.icon != nil { - // s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&s.icon[0]), C.int(len(s.icon)))) - // C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) - } - if s.menu != nil { - s.menu.Update() - // Convert impl to macosMenu object - // s.nsMenu = (s.menu.impl).(*macosMenu).nsMenu - // C.systemTraySetMenu(s.nsStatusItem, s.nsMenu) - } - - }) -} - -func (s *linuxSystemTray) setIcon(icon []byte) { - s.icon = icon - globalApplication.dispatchOnMainThread(func() { - // s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) - // C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) - }) -} - -func (s *linuxSystemTray) setDarkModeIcon(icon []byte) { - s.icon = icon - globalApplication.dispatchOnMainThread(func() { - // s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) - // C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) - }) -} - -func (s *linuxSystemTray) setTemplateIcon(icon []byte) { - s.icon = icon - s.isTemplateIcon = true - globalApplication.dispatchOnMainThread(func() { - // s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon)))) - // C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon)) - }) -} - -func newSystemTrayImpl(s *SystemTray) systemTrayImpl { - return &linuxSystemTray{ - id: s.id, - label: s.label, - icon: s.icon, - menu: s.menu, - iconPosition: s.iconPosition, - isTemplateIcon: s.isTemplateIcon, - } -} - -func (s *linuxSystemTray) setLabel(label string) { - s.label = label - // C.systemTraySetLabel(s.nsStatusItem, C.CString(label)) -} - -func (s *linuxSystemTray) destroy() { - // Remove the status item from the status bar and its associated menu - // C.systemTrayDestroy(s.nsStatusItem) -} diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 857787f27..1fdf8777d 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -1,7 +1,6 @@ package application import ( - "encoding/json" "errors" "fmt" "github.com/samber/lo" @@ -150,39 +149,6 @@ func (w *WebviewWindow) addCancellationFunction(canceller func()) { w.cancellers = append(w.cancellers, canceller) } -// formatJS ensures the 'data' provided marshals to valid json or panics -func (w *WebviewWindow) formatJS(f string, callID string, data string) string { - j, err := json.Marshal(data) - if err != nil { - panic(err) - } - return fmt.Sprintf(f, callID, j) -} - -func (w *WebviewWindow) CallError(callID *string, result string) { - if w.impl != nil { - w.impl.execJS(w.formatJS("_wails.callErrorCallback('%s', %s);", *callID, result)) - } -} - -func (w *WebviewWindow) CallResponse(callID *string, result string) { - if w.impl != nil { - w.impl.execJS(w.formatJS("_wails.callCallback('%s', %s, true);", *callID, result)) - } -} - -func (w *WebviewWindow) DialogError(dialogID *string, result string) { - if w.impl != nil { - w.impl.execJS(w.formatJS("_wails.dialogErrorCallback('%s', %s);", *dialogID, result)) - } -} - -func (w *WebviewWindow) DialogResponse(dialogID *string, result string) { - if w.impl != nil { - w.impl.execJS(w.formatJS("_wails.dialogCallback('%s', %s, true);", *dialogID, result)) - } -} - // SetTitle sets the title of the window func (w *WebviewWindow) SetTitle(title string) *WebviewWindow { w.options.Title = title diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go deleted file mode 100644 index 7cc9843f0..000000000 --- a/v3/pkg/application/webview_window_linux.go +++ /dev/null @@ -1,797 +0,0 @@ -//go:build linux && !purego - -package application - -/* -#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 - -#include -#include -#include -#include -#include -#include - - -// exported below -extern gboolean buttonEvent(GtkWidget *widget, GdkEventButton *event, gpointer user_data); - extern void processRequest(void *request, gpointer user_data); -extern void onDragNDrop( - void *target, - GdkDragContext* context, - gint x, - gint y, - gpointer seldata, - guint info, - guint time, - gpointer data); -// exported below (end) - -static void signal_connect(GtkWidget *widget, char *event, void *cb, void* data) { - // g_signal_connect is a macro and can't be called directly - g_signal_connect(widget, event, cb, data); -} -*/ -import "C" - -import ( - "fmt" - "net/url" - "strings" - "sync" - "unsafe" - - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" - "github.com/wailsapp/wails/v2/pkg/menu" - "github.com/wailsapp/wails/v3/pkg/events" -) - -var showDevTools = func(window unsafe.Pointer) {} - -func gtkBool(input bool) C.gboolean { - if input { - return C.gboolean(1) - } - return C.gboolean(0) -} - -type dragInfo struct { - XRoot int - YRoot int - DragTime int - MouseButton uint -} - -type linuxWebviewWindow struct { - id uint - application unsafe.Pointer - window unsafe.Pointer - webview unsafe.Pointer - parent *WebviewWindow - menubar *C.GtkWidget - vbox *C.GtkWidget - menu *menu.Menu - accels *C.GtkAccelGroup - lastWidth int - lastHeight int - drag dragInfo -} - -var ( - registered bool = false // avoid 'already registered message' about 'wails://' -) - -//export buttonEvent -func buttonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data unsafe.Pointer) C.gboolean { - // Constants (defined here to be easier to use with ) - GdkButtonPress := C.GDK_BUTTON_PRESS // 4 - Gdk2ButtonPress := C.GDK_2BUTTON_PRESS // 5 for double-click - GdkButtonRelease := C.GDK_BUTTON_RELEASE // 7 - - windowId := uint(*((*C.uint)(data))) - window := globalApplication.getWindowForID(windowId) - if window == nil { - return C.gboolean(0) - } - lw, ok := (window.impl).(*linuxWebviewWindow) - if !ok { - return C.gboolean(0) - } - - if event == nil { - return C.gboolean(0) - } - if event.button == 3 { - return C.gboolean(0) - } - - switch int(event._type) { - case GdkButtonPress: - lw.startDrag(uint(event.button), int(event.x_root), int(event.y_root)) - case Gdk2ButtonPress: - fmt.Printf("%d - button %d - double-clicked\n", windowId, int(event.button)) - case GdkButtonRelease: - lw.endDrag(uint(event.button), int(event.x_root), int(event.y_root)) - } - - return C.gboolean(0) -} - -func (w *linuxWebviewWindow) startDrag(button uint, x, y int) { - fmt.Println("startDrag ", button, x, y) - w.drag.XRoot = x - w.drag.YRoot = y -} - -func (w *linuxWebviewWindow) endDrag(button uint, x, y int) { - fmt.Println("endDrag", button, x, y) -} - -//export onDragNDrop -func onDragNDrop(target unsafe.Pointer, context *C.GdkDragContext, x C.gint, y C.gint, seldata unsafe.Pointer, info C.guint, time C.guint, data unsafe.Pointer) { - fmt.Println("target", target, info) - var length C.gint - selection := unsafe.Pointer(C.gtk_selection_data_get_data_with_length((*C.GtkSelectionData)(seldata), &length)) - extracted := C.g_uri_list_extract_uris((*C.char)(selection)) - defer C.g_strfreev(extracted) - - uris := unsafe.Slice( - (**C.char)(unsafe.Pointer(extracted)), - int(length)) - - var filenames []string - for _, uri := range uris { - if uri == nil { - break - } - filenames = append(filenames, strings.TrimPrefix(C.GoString(uri), "file://")) - } - windowDragAndDropBuffer <- &dragAndDropMessage{ - windowId: uint(*((*C.uint)(data))), - filenames: filenames, - } - C.gtk_drag_finish(context, C.true, C.false, time) -} - -//export processRequest -func processRequest(request unsafe.Pointer, data unsafe.Pointer) { - windowId := uint(*((*C.uint)(data))) - webviewRequests <- &webViewAssetRequest{ - Request: webview.NewRequest(request), - windowId: windowId, - windowName: globalApplication.getWindowForID(windowId).Name(), - } -} - -func (w *linuxWebviewWindow) enableDND() { - dnd := C.CString("text/uri-list") - defer C.free(unsafe.Pointer(dnd)) - targetentry := C.gtk_target_entry_new(dnd, 0, C.guint(w.parent.id)) - defer C.gtk_target_entry_free(targetentry) - C.gtk_drag_dest_set((*C.GtkWidget)(w.webview), C.GTK_DEST_DEFAULT_DROP, targetentry, 1, C.GDK_ACTION_COPY) - event := C.CString("drag-data-received") - defer C.free(unsafe.Pointer(event)) - id := C.uint(w.parent.id) - C.signal_connect((*C.GtkWidget)(unsafe.Pointer(w.webview)), event, C.onDragNDrop, unsafe.Pointer(C.gpointer(&id))) -} - -func (w *linuxWebviewWindow) newWebview(gpuPolicy int) unsafe.Pointer { - manager := C.webkit_user_content_manager_new() - external := C.CString("external") - C.webkit_user_content_manager_register_script_message_handler(manager, external) - - C.free(unsafe.Pointer(external)) - webview := C.webkit_web_view_new_with_user_content_manager(manager) - id := C.uint(w.parent.id) - if !registered { - wails := C.CString("wails") - C.webkit_web_context_register_uri_scheme( - C.webkit_web_context_get_default(), - wails, - C.WebKitURISchemeRequestCallback(C.processRequest), - C.gpointer(&id), - nil) - registered = true - C.free(unsafe.Pointer(wails)) - } - settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(unsafe.Pointer(webview))) - wails_io := C.CString("wails.io") - empty := C.CString("") - defer C.free(unsafe.Pointer(wails_io)) - defer C.free(unsafe.Pointer(empty)) - C.webkit_settings_set_user_agent_with_application_details(settings, wails_io, empty) - - switch gpuPolicy { - case 0: - C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS) - break - case 1: - C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) - break - case 2: - C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER) - break - default: - C.webkit_settings_set_hardware_acceleration_policy(settings, C.WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND) - } - return unsafe.Pointer(webview) -} - -func (w *linuxWebviewWindow) connectSignals() { - event := C.CString("delete-event") - defer C.free(unsafe.Pointer(event)) - - // Window close handler - - if w.parent.options.HideOnClose { - C.signal_connect((*C.GtkWidget)(w.window), event, C.gtk_widget_hide_on_delete, C.NULL) - } else { - - // C.signal_connect((*C.GtkWidget)(window), event, C.close_button_pressed, w.parent.id) - } - /* - event = C.CString("load-changed") - defer C.free(unsafe.Pointer(event)) - C.signal_connect(webview, event, C.webviewLoadChanged, unsafe.Pointer(&w.parent.id)) - */ - id := C.uint(w.parent.id) - event = C.CString("button-press-event") - C.signal_connect((*C.GtkWidget)(unsafe.Pointer(w.webview)), event, C.buttonEvent, unsafe.Pointer(&id)) - C.free(unsafe.Pointer(event)) - event = C.CString("button-release-event") - defer C.free(unsafe.Pointer(event)) - C.signal_connect((*C.GtkWidget)(unsafe.Pointer(w.webview)), event, C.buttonEvent, unsafe.Pointer(&id)) -} - -func (w *linuxWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { - // Create the menu - thisMenu := newMenuImpl(menu) - thisMenu.update() - fmt.Println("linux.openContextMenu()") - /* void - gtk_menu_popup_at_rect ( - GtkMenu* menu, - GdkWindow* rect_window, - const GdkRectangle* rect, - GdkGravity rect_anchor, - GdkGravity menu_anchor, - const GdkEvent* trigger_event - ) - */ -} - -func (w *linuxWebviewWindow) getZoom() float64 { - return float64(C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(w.webview))) -} - -func (w *linuxWebviewWindow) setZoom(zoom float64) { - C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), C.double(zoom)) -} - -func (w *linuxWebviewWindow) setFrameless(frameless bool) { - if frameless { - C.gtk_window_set_decorated((*C.GtkWindow)(w.window), C.gboolean(0)) - } else { - C.gtk_window_set_decorated((*C.GtkWindow)(w.window), C.gboolean(1)) - // TODO: Deal with transparency for the titlebar if possible - // Perhaps we just make it undecorated and add a menu bar inside? - } -} - -func (w *linuxWebviewWindow) getScreen() (*Screen, error) { - mx, my, width, height, scale := w.getCurrentMonitorGeometry() - return &Screen{ - ID: fmt.Sprintf("%d", w.id), // A unique identifier for the display - Name: w.parent.Name(), // The name of the display - Scale: float32(scale), // The scale factor of the display - X: mx, // The x-coordinate of the top-left corner of the rectangle - Y: my, // The y-coordinate of the top-left corner of the rectangle - Size: Size{Width: width, Height: height}, // The size of the display - Bounds: Rect{}, // The bounds of the display - WorkArea: Rect{}, // The work area of the display - IsPrimary: false, // Whether this is the primary display - Rotation: 0.0, // The rotation of the display - }, nil -} - -func (w *linuxWebviewWindow) show() { - globalApplication.dispatchOnMainThread(func() { - C.gtk_widget_show_all((*C.GtkWidget)(w.window)) - }) -} - -func (w *linuxWebviewWindow) hide() { - C.gtk_widget_hide((*C.GtkWidget)(w.window)) -} - -func (w *linuxWebviewWindow) isNormal() bool { - return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() -} - -func (w *linuxWebviewWindow) isVisible() bool { - if C.gtk_widget_is_visible((*C.GtkWidget)(w.window)) == 1 { - return true - } - return false -} - -func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) { - // C.setFullscreenButtonEnabled(w.nsWindow, C.bool(enabled)) - fmt.Println("setFullscreenButtonEnabled - not implemented") -} - -func (w *linuxWebviewWindow) disableSizeConstraints() { - x, y, width, height, scale := w.getCurrentMonitorGeometry() - w.setMinMaxSize(x, y, width*scale, height*scale) -} - -func (w *linuxWebviewWindow) unfullscreen() { - fmt.Println("unfullscreen") - globalApplication.dispatchOnMainThread(func() { - C.gtk_window_unfullscreen((*C.GtkWindow)(w.window)) - w.unmaximise() - }) -} - -func (w *linuxWebviewWindow) fullscreen() { - w.maximise() - w.lastWidth, w.lastHeight = w.size() - globalApplication.dispatchOnMainThread(func() { - x, y, width, height, scale := w.getCurrentMonitorGeometry() - if x == -1 && y == -1 && width == -1 && height == -1 { - return - } - w.setMinMaxSize(0, 0, width*scale, height*scale) - w.setSize(width*scale, height*scale) - C.gtk_window_fullscreen((*C.GtkWindow)(w.window)) - w.setRelativePosition(0, 0) - }) -} - -func (w *linuxWebviewWindow) setEnabled(enabled bool) { - globalApplication.dispatchOnMainThread(func() { - C.gtk_widget_set_sensitive((*C.GtkWidget)(w.window), C.gboolean(enabled)) - }) -} - -func (w *linuxWebviewWindow) unminimise() { - C.gtk_window_present((*C.GtkWindow)(w.window)) - // gtk_window_unminimize ((*C.GtkWindow)(w.window)) /// gtk4 -} - -func (w *linuxWebviewWindow) unmaximise() { - C.gtk_window_unmaximize((*C.GtkWindow)(w.window)) -} - -func (w *linuxWebviewWindow) maximise() { - C.gtk_window_maximize((*C.GtkWindow)(w.window)) -} - -func (w *linuxWebviewWindow) minimise() { - C.gtk_window_iconify((*C.GtkWindow)(w.window)) -} - -func (w *linuxWebviewWindow) on(eventID uint) { - // Don't think this is correct! - // GTK Events are strings - fmt.Println("on()", eventID) - //C.registerListener(C.uint(eventID)) -} - -func (w *linuxWebviewWindow) zoom() { - w.zoomIn() -} - -func (w *linuxWebviewWindow) windowZoom() { - w.zoom() // FIXME> This should be removed -} - -func (w *linuxWebviewWindow) close() { - C.gtk_window_close((*C.GtkWindow)(w.window)) - if !w.parent.options.HideOnClose { - globalApplication.deleteWindowByID(w.parent.id) - } -} - -func (w *linuxWebviewWindow) zoomIn() { - lvl := C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(w.webview)) - C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), lvl+0.5) -} - -func (w *linuxWebviewWindow) zoomOut() { - lvl := C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(w.webview)) - C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), lvl-0.5) -} - -func (w *linuxWebviewWindow) zoomReset() { - C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), 0.0) -} - -func (w *linuxWebviewWindow) reload() { - // TODO: This should be a constant somewhere I feel - uri := C.CString("wails://") - C.webkit_web_view_load_uri((*C.WebKitWebView)(w.window), uri) - C.free(unsafe.Pointer(uri)) -} - -func (w *linuxWebviewWindow) forceReload() { - w.reload() -} - -func (w linuxWebviewWindow) getCurrentMonitor() *C.GdkMonitor { - // Get the monitor that the window is currently on - display := C.gtk_widget_get_display((*C.GtkWidget)(w.window)) - gdk_window := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) - if gdk_window == nil { - return nil - } - return C.gdk_display_get_monitor_at_window(display, gdk_window) -} - -func (w linuxWebviewWindow) getCurrentMonitorGeometry() (x int, y int, width int, height int, scale int) { - monitor := w.getCurrentMonitor() - if monitor == nil { - return -1, -1, -1, -1, 1 - } - var result C.GdkRectangle - C.gdk_monitor_get_geometry(monitor, &result) - scale = int(C.gdk_monitor_get_scale_factor(monitor)) - return int(result.x), int(result.y), int(result.width), int(result.height), scale -} - -func (w *linuxWebviewWindow) center() { - globalApplication.dispatchOnMainThread(func() { - x, y, width, height, _ := w.getCurrentMonitorGeometry() - if x == -1 && y == -1 && width == -1 && height == -1 { - return - } - - var windowWidth C.int - var windowHeight C.int - C.gtk_window_get_size((*C.GtkWindow)(w.window), &windowWidth, &windowHeight) - - newX := C.int(((width - int(windowWidth)) / 2) + x) - newY := C.int(((height - int(windowHeight)) / 2) + y) - - // Place the window at the center of the monitor - C.gtk_window_move((*C.GtkWindow)(w.window), newX, newY) - }) -} - -func (w *linuxWebviewWindow) isMinimised() bool { - gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) - state := C.gdk_window_get_state(gdkwindow) - return state&C.GDK_WINDOW_STATE_ICONIFIED > 0 -} - -func (w *linuxWebviewWindow) isMaximised() bool { - return w.syncMainThreadReturningBool(func() bool { - gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) - state := C.gdk_window_get_state(gdkwindow) - return state&C.GDK_WINDOW_STATE_MAXIMIZED > 0 && state&C.GDK_WINDOW_STATE_FULLSCREEN == 0 - }) -} - -func (w *linuxWebviewWindow) isFullscreen() bool { - return w.syncMainThreadReturningBool(func() bool { - gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) - state := C.gdk_window_get_state(gdkwindow) - return state&C.GDK_WINDOW_STATE_FULLSCREEN > 0 - }) -} - -func (w *linuxWebviewWindow) syncMainThreadReturningBool(fn func() bool) bool { - var wg sync.WaitGroup - wg.Add(1) - var result bool - globalApplication.dispatchOnMainThread(func() { - result = fn() - wg.Done() - }) - wg.Wait() - return result -} - -func (w *linuxWebviewWindow) restore() { - // restore window to normal size - // FIXME: never called! - remove from webviewImpl interface -} - -func (w *linuxWebviewWindow) execJS(js string) { - value := C.CString(js) - C.webkit_web_view_evaluate_javascript((*C.WebKitWebView)(w.webview), - value, - C.long(len(js)), - nil, - C.CString(""), - nil, - nil, - nil) - C.free(unsafe.Pointer(value)) -} - -func (w *linuxWebviewWindow) setURL(uri string) { - if uri != "" { - url, err := url.Parse(uri) - if err == nil && url.Scheme == "" && url.Host == "" { - // TODO handle this in a central location, the scheme and host might be platform dependant. - url.Scheme = "wails" - url.Host = "wails" - uri = url.String() - } - } - target := C.CString(uri) - C.webkit_web_view_load_uri((*C.WebKitWebView)(w.webview), target) - C.free(unsafe.Pointer(target)) -} - -func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { - C.gtk_window_set_keep_above((*C.GtkWindow)(w.window), gtkBool(alwaysOnTop)) -} - -func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow { - // (*C.struct__GtkWidget)(m.native) - //var menubar *C.struct__GtkWidget - return &linuxWebviewWindow{ - application: getNativeApplication().application, - parent: parent, - // menubar: menubar, - } -} - -func (w *linuxWebviewWindow) setTitle(title string) { - if !w.parent.options.Frameless { - cTitle := C.CString(title) - C.gtk_window_set_title((*C.GtkWindow)(w.window), cTitle) - C.free(unsafe.Pointer(cTitle)) - } -} - -func (w *linuxWebviewWindow) setSize(width, height int) { - C.gtk_window_resize((*C.GtkWindow)(w.window), C.gint(width), C.gint(height)) -} - -func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight int) { - fmt.Println("setMinMaxSize", minWidth, minHeight, maxWidth, maxHeight) - if minWidth == 0 { - minWidth = -1 - } - if minHeight == 0 { - minHeight = -1 - } - if maxWidth == 0 { - maxWidth = -1 - } - if maxHeight == 0 { - maxHeight = -1 - } - size := C.GdkGeometry{ - min_width: C.int(minWidth), - min_height: C.int(minHeight), - max_width: C.int(maxWidth), - max_height: C.int(maxHeight), - } - C.gtk_window_set_geometry_hints((*C.GtkWindow)(w.window), nil, &size, C.GDK_HINT_MAX_SIZE|C.GDK_HINT_MIN_SIZE) -} - -func (w *linuxWebviewWindow) setMinSize(width, height int) { - w.setMinMaxSize(width, height, w.parent.options.MaxWidth, w.parent.options.MaxHeight) -} - -func (w *linuxWebviewWindow) setMaxSize(width, height int) { - w.setMinMaxSize(w.parent.options.MinWidth, w.parent.options.MinHeight, width, height) -} - -func (w *linuxWebviewWindow) setResizable(resizable bool) { - if resizable { - C.gtk_window_set_resizable((*C.GtkWindow)(w.window), 1) - } else { - C.gtk_window_set_resizable((*C.GtkWindow)(w.window), 0) - } -} - -func (w *linuxWebviewWindow) toggleDevTools() { - settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(w.webview)) - enabled := C.webkit_settings_get_enable_developer_extras(settings) - if enabled == C.int(0) { - enabled = C.int(1) - } else { - enabled = C.int(0) - } - C.webkit_settings_set_enable_developer_extras(settings, enabled) -} - -func (w *linuxWebviewWindow) size() (int, int) { - var width, height C.int - var wg sync.WaitGroup - wg.Add(1) - globalApplication.dispatchOnMainThread(func() { - C.gtk_window_get_size((*C.GtkWindow)(w.window), &width, &height) - wg.Done() - }) - wg.Wait() - return int(width), int(height) -} - -func (w *linuxWebviewWindow) setRelativePosition(x, y int) { - mx, my, _, _, _ := w.getCurrentMonitorGeometry() - globalApplication.dispatchOnMainThread(func() { - C.gtk_window_move((*C.GtkWindow)(w.window), C.int(x+mx), C.int(y+my)) - }) -} - -func (w *linuxWebviewWindow) width() int { - width, _ := w.size() - return width -} - -func (w *linuxWebviewWindow) height() int { - _, height := w.size() - return height -} - -func (w *linuxWebviewWindow) absolutePosition() (int, int) { - var x, y C.int - var wg sync.WaitGroup - wg.Add(1) - globalApplication.dispatchOnMainThread(func() { - C.gtk_window_get_position((*C.GtkWindow)(w.window), &x, &y) - wg.Done() - }) - wg.Wait() - return int(x), int(y) -} - -func (w *linuxWebviewWindow) run() { - for eventId := range w.parent.eventListeners { - w.on(eventId) - } - - app := getNativeApplication() - menu := app.applicationMenu - - globalApplication.dispatchOnMainThread(func() { - w.window = unsafe.Pointer(C.gtk_application_window_new((*C.GtkApplication)(w.application))) - app.registerWindow((*C.GtkWindow)(w.window), w.parent.id) // record our mapping - C.g_object_ref_sink(C.gpointer(w.window)) - w.webview = w.newWebview(1) - w.connectSignals() - if w.parent.options.EnableDragAndDrop { - w.enableDND() - } - w.vbox = C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0) - C.gtk_container_add((*C.GtkContainer)(w.window), w.vbox) - if menu != nil { - C.gtk_box_pack_start((*C.GtkBox)(unsafe.Pointer(w.vbox)), (*C.GtkWidget)(menu), 0, 0, 0) - } - C.gtk_box_pack_start((*C.GtkBox)(unsafe.Pointer(w.vbox)), (*C.GtkWidget)(w.webview), 1, 1, 0) - - w.setTitle(w.parent.options.Title) - w.setAlwaysOnTop(w.parent.options.AlwaysOnTop) - w.setResizable(!w.parent.options.DisableResize) - // only set min/max size if actually set - if w.parent.options.MinWidth != 0 && - w.parent.options.MinHeight != 0 && - w.parent.options.MaxWidth != 0 && - w.parent.options.MaxHeight != 0 { - w.setMinMaxSize( - w.parent.options.MinWidth, - w.parent.options.MinHeight, - w.parent.options.MaxWidth, - w.parent.options.MaxHeight, - ) - } - w.setSize(w.parent.options.Width, w.parent.options.Height) - w.setZoom(w.parent.options.Zoom) - w.setBackgroundColour(w.parent.options.BackgroundColour) - w.setFrameless(w.parent.options.Frameless) - - if w.parent.options.X != 0 || w.parent.options.Y != 0 { - w.setRelativePosition(w.parent.options.X, w.parent.options.Y) - } else { - fmt.Println("attempting to set in the center") - w.center() - } - switch w.parent.options.StartState { - case WindowStateMaximised: - w.maximise() - case WindowStateMinimised: - w.minimise() - case WindowStateFullscreen: - w.fullscreen() - } - - if w.parent.options.URL != "" { - w.setURL(w.parent.options.URL) - } - // We need to wait for the HTML to load before we can execute the javascript - // FIXME: What event is this? DomReady? - w.parent.On(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEventContext) { - if w.parent.options.JS != "" { - w.execJS(w.parent.options.JS) - } - if w.parent.options.CSS != "" { - js := fmt.Sprintf("(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%s')); document.head.appendChild(style); })();", w.parent.options.CSS) - fmt.Println(js) - w.execJS(js) - } - }) - if w.parent.options.HTML != "" { - w.setHTML(w.parent.options.HTML) - } - if !w.parent.options.Hidden { - w.show() - if w.parent.options.X != 0 || w.parent.options.Y != 0 { - w.setRelativePosition(w.parent.options.X, w.parent.options.Y) - } else { - fmt.Println("attempting to set in the center") - w.center() - } - } - }) -} - -func (w *linuxWebviewWindow) setTransparent() { - screen := C.gtk_widget_get_screen((*C.GtkWidget)(w.window)) - visual := C.gdk_screen_get_rgba_visual(screen) - - if visual != nil && C.gdk_screen_is_composited(screen) == C.int(1) { - C.gtk_widget_set_app_paintable((*C.GtkWidget)(w.window), C.gboolean(1)) - C.gtk_widget_set_visual((*C.GtkWidget)(w.window), visual) - } -} - -func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) { - if colour.Alpha != 0 { - w.setTransparent() - } - rgba := C.GdkRGBA{C.double(colour.Red) / 255.0, C.double(colour.Green) / 255.0, C.double(colour.Blue) / 255.0, C.double(colour.Alpha) / 255.0} - fmt.Println(unsafe.Pointer(&rgba)) - C.webkit_web_view_set_background_color((*C.WebKitWebView)(w.webview), &rgba) -} - -func (w *linuxWebviewWindow) relativePosition() (int, int) { - var x, y C.int - var wg sync.WaitGroup - wg.Add(1) - go globalApplication.dispatchOnMainThread(func() { - C.gtk_window_get_position((*C.GtkWindow)(w.window), &x, &y) - - // The position must be relative to the screen it is on - // We need to get the screen it is on - screen := C.gtk_widget_get_screen((*C.GtkWidget)(w.window)) - monitor := C.gdk_screen_get_monitor_at_window(screen, (*C.GdkWindow)(w.window)) - geometry := C.GdkRectangle{} - C.gdk_screen_get_monitor_geometry(screen, monitor, &geometry) - x = x - geometry.x - y = y - geometry.y - - // TODO: Scale based on DPI - - wg.Done() - }) - wg.Wait() - return int(x), int(y) -} - -func (w *linuxWebviewWindow) destroy() { - C.gtk_window_close((*C.GtkWindow)(w.window)) -} - -func (w *linuxWebviewWindow) setHTML(html string) { - cHTML := C.CString(html) - uri := C.CString("wails://") - empty := C.CString("") - defer C.free(unsafe.Pointer(cHTML)) - defer C.free(unsafe.Pointer(uri)) - defer C.free(unsafe.Pointer(empty)) - C.webkit_web_view_load_alternate_html( - (*C.WebKitWebView)(w.webview), - cHTML, - uri, - empty) -} - -func (w *linuxWebviewWindow) nativeWindowHandle() uintptr { - return uintptr(w.window) -} diff --git a/v3/pkg/application/webview_window_linux_purego.go b/v3/pkg/application/webview_window_linux_purego.go deleted file mode 100644 index fa9ff9f57..000000000 --- a/v3/pkg/application/webview_window_linux_purego.go +++ /dev/null @@ -1,764 +0,0 @@ -//go:build linux && purego - -package application - -import ( - "fmt" - "net/url" - "sync" - "unsafe" - - "github.com/ebitengine/purego" - "github.com/wailsapp/wails/v2/pkg/menu" - "github.com/wailsapp/wails/v3/pkg/events" -) - -var ( - registered bool = false // avoid 'already registered message' -) - -const ( - // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkwindow.h#L121 - GDK_HINT_MIN_SIZE = 1 << 1 - GDK_HINT_MAX_SIZE = 1 << 2 - // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkevents.h#L512 - GDK_WINDOW_STATE_ICONIFIED = 1 << 1 - GDK_WINDOW_STATE_MAXIMIZED = 1 << 2 - GDK_WINDOW_STATE_FULLSCREEN = 1 << 4 -) - -type GdkGeometry struct { - minWidth int32 - minHeight int32 - maxWidth int32 - maxHeight int32 - baseWidth int32 - baseHeight int32 - widthInc int32 - heightInc int32 - padding int32 - minAspect float64 - maxAspect float64 - GdkGravity int32 -} - -type linuxWebviewWindow struct { - application uintptr - window uintptr - webview uintptr - parent *WebviewWindow - menubar uintptr - vbox uintptr - menu *menu.Menu - accels uintptr - minWidth, minHeight, maxWidth, maxHeight int -} - -func (w *linuxWebviewWindow) newWebview(gpuPolicy int) uintptr { - var newContentMgr func() uintptr - purego.RegisterLibFunc( - &newContentMgr, - webkit, - "webkit_user_content_manager_new") - var registerScriptMessageHandler func(uintptr, string) - purego.RegisterLibFunc(®isterScriptMessageHandler, webkit, "webkit_user_content_manager_register_script_message_handler") - var newWebview func(uintptr) uintptr - purego.RegisterLibFunc(&newWebview, webkit, "webkit_web_view_new_with_user_content_manager") - - manager := newContentMgr() - registerScriptMessageHandler(manager, "external") - webview := newWebview(manager) - if !registered { - var registerUriScheme func(uintptr, string, uintptr, uintptr, uintptr) - purego.RegisterLibFunc(®isterUriScheme, webkit, "webkit_web_context_register_uri_scheme") - cb := purego.NewCallback(func(request uintptr) { - processURLRequest(w.parent.id, request) - }) - var defaultContext func() uintptr - purego.RegisterLibFunc(&defaultContext, webkit, "webkit_web_context_get_default") - registerUriScheme(defaultContext(), "wails", cb, 0, 0) - } - - var g_signal_connect func(uintptr, string, uintptr, uintptr, bool, int) int - purego.RegisterLibFunc(&g_signal_connect, gtk, "g_signal_connect_data") - - loadChanged := purego.NewCallback(func(window uintptr) { - //fmt.Println("loadChanged", window) - }) - if g_signal_connect(webview, "load-changed", loadChanged, 0, false, 0) == 0 { - fmt.Println("failed to connect 'load-changed' event") - } - - if g_signal_connect(webview, "button-press-event", purego.NewCallback(w.buttonPress), 0, false, 0) == 0 { - fmt.Println("failed to connect 'button-press-event") - } - if g_signal_connect(webview, "button-release-event", purego.NewCallback(w.buttonRelease), 0, false, 0) == 0 { - fmt.Println("failed to connect 'button-release-event") - } - - handleDelete := purego.NewCallback(func(uintptr) { - w.close() - if !w.parent.options.HideOnClose { - fmt.Println("Need to do more!") - } - }) - g_signal_connect(w.window, "delete-event", handleDelete, 0, false, 0) - - var getSettings func(uintptr) uintptr - purego.RegisterLibFunc(&getSettings, webkit, "webkit_web_view_get_settings") - var setSettings func(uintptr, uintptr) - purego.RegisterLibFunc(&setSettings, webkit, "webkit_web_view_set_settings") - var setUserAgent func(uintptr, string, string) - purego.RegisterLibFunc(&setUserAgent, webkit, "webkit_settings_set_user_agent_with_application_details") - settings := getSettings(webview) - setUserAgent(settings, "wails.io", "") - - var setHWAccel func(uintptr, int) - purego.RegisterLibFunc(&setHWAccel, webkit, "webkit_settings_set_hardware_acceleration_policy") - - setHWAccel(settings, gpuPolicy) - setSettings(webview, settings) - - return webview -} - -func (w *linuxWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) { - // Create the menu - thisMenu := newMenuImpl(menu) - thisMenu.update() - fmt.Println("linux.openContextMenu()") - //C.windowShowMenu(w.nsWindow, thisMenu.nsMenu, C.int(data.X), C.int(data.Y)) -} - -func (w *linuxWebviewWindow) getZoom() float64 { - var getZoom func(uintptr) float32 - purego.RegisterLibFunc(&getZoom, webkit, "webkit_web_view_get_zoom_level") - return float64(getZoom(w.webview)) -} - -func (w *linuxWebviewWindow) setZoom(zoom float64) { - var setZoom func(uintptr, float64) - purego.RegisterLibFunc(&setZoom, webkit, "webkit_web_view_set_zoom_level") - setZoom(w.webview, zoom) -} - -func (w *linuxWebviewWindow) setFrameless(frameless bool) { - var setDecorated func(uintptr, int) - purego.RegisterLibFunc(&setDecorated, gtk, "gtk_window_set_decorated") - decorated := 1 - if frameless { - decorated = 0 - } - setDecorated(w.window, decorated) - if !frameless { - // TODO: Deal with transparency for the titlebar if possible - // Perhaps we just make it undecorated and add a menu bar inside? - } -} - -func (w *linuxWebviewWindow) getScreen() (*Screen, error) { - return getScreenForWindow(w) -} - -func (w *linuxWebviewWindow) show() { - var widgetShow func(uintptr) - purego.RegisterLibFunc(&widgetShow, gtk, "gtk_widget_show_all") - globalApplication.dispatchOnMainThread(func() { - widgetShow(w.window) - }) -} - -func (w *linuxWebviewWindow) hide() { - var widgetHide func(uintptr) - purego.RegisterLibFunc(&widgetHide, gtk, "gtk_widget_hide") - widgetHide(w.window) -} - -func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) { - // C.setFullscreenButtonEnabled(w.nsWindow, C.bool(enabled)) - fmt.Println("setFullscreenButtonEnabled - not implemented") - -} - -func (w *linuxWebviewWindow) disableSizeConstraints() { - x, y, width, height, scale := w.getCurrentMonitorGeometry() - w.setMinMaxSize(x, y, width*scale, height*scale) -} - -func (w *linuxWebviewWindow) unfullscreen() { - var unfullScreen func(uintptr) - purego.RegisterLibFunc(&unfullScreen, gtk, "gtk_window_unfullscreen") - - globalApplication.dispatchOnMainThread(func() { - unfullScreen(w.window) - w.unmaximise() - }) -} - -func (w *linuxWebviewWindow) setEnabled(enabled bool) { - var gtkWidgetSensitive func(uintptr, int) - purego.RegisterLibFunc(>kWidgetSensitive, gtk, "gtk_widget_set_sensitive") - globalApplication.dispatchOnMainThread(func() { - gtkWidgetSensitive(w.window, boolToInt(enabled)) - }) -} - -func (w *linuxWebviewWindow) fullscreen() { - var fullScreen func(uintptr) - purego.RegisterLibFunc(&fullScreen, gtk, "gtk_window_fullscreen") - - globalApplication.dispatchOnMainThread(func() { - w.maximise() - // w.lastWidth, w.lastHeight = w.size() // do we need this? - - x, y, width, height, scale := w.getCurrentMonitorGeometry() - if x == -1 && y == -1 && width == -1 && height == -1 { - return - } - w.setMinMaxSize(0, 0, width*scale, height*scale) - w.setSize(width*scale, height*scale) - w.setRelativePosition(0, 0) - fullScreen(w.window) - }) -} - -func (w *linuxWebviewWindow) unminimise() { - var present func(uintptr) - purego.RegisterLibFunc(&present, gtk, "gtk_window_present") - present(w.window) -} - -func (w *linuxWebviewWindow) unmaximise() { - var unmaximize func(uintptr) - purego.RegisterLibFunc(&unmaximize, gtk, "gtk_window_unmaximize") - unmaximize(w.window) -} - -func (w *linuxWebviewWindow) maximise() { - var maximize func(uintptr) - purego.RegisterLibFunc(&maximize, gtk, "gtk_window_maximize") - maximize(w.window) -} - -func (w *linuxWebviewWindow) minimise() { - var iconify func(uintptr) - purego.RegisterLibFunc(&iconify, gtk, "gtk_window_iconify") - iconify(w.window) -} - -func (w *linuxWebviewWindow) on(eventID uint) { - // Don't think this is correct! - // GTK Events are strings - fmt.Println("on()", eventID) - //C.registerListener(C.uint(eventID)) -} - -func (w *linuxWebviewWindow) zoom() { - w.zoomIn() -} - -func (w *linuxWebviewWindow) windowZoom() { - w.zoom() -} - -func (w *linuxWebviewWindow) close() { - var close func(uintptr) - purego.RegisterLibFunc(&close, gtk, "gtk_window_close") - close(w.window) -} - -func (w *linuxWebviewWindow) zoomIn() { - var getZoom func(uintptr) float32 - purego.RegisterLibFunc(&getZoom, webkit, "webkit_web_view_get_zoom_level") - var setZoom func(uintptr, float32) - purego.RegisterLibFunc(&setZoom, webkit, "webkit_web_view_set_zoom_level") - lvl := getZoom(w.webview) - setZoom(w.webview, lvl+0.5) -} - -func (w *linuxWebviewWindow) zoomOut() { - var getZoom func(uintptr) float32 - purego.RegisterLibFunc(&getZoom, webkit, "webkit_web_view_get_zoom_level") - var setZoom func(uintptr, float32) - purego.RegisterLibFunc(&setZoom, webkit, "webkit_web_view_set_zoom_level") - lvl := getZoom(w.webview) - setZoom(w.webview, lvl-0.5) -} - -func (w *linuxWebviewWindow) zoomReset() { - var setZoom func(uintptr, float32) - purego.RegisterLibFunc(&setZoom, webkit, "webkit_web_view_set_zoom_level") - setZoom(w.webview, 0.0) -} - -func (w *linuxWebviewWindow) toggleDevTools() { - var getSettings func(uintptr) uintptr - purego.RegisterLibFunc(&getSettings, webkit, "webkit_web_view_get_settings") - var isEnabled func(uintptr) bool - purego.RegisterLibFunc(&isEnabled, webkit, "webkit_settings_get_enable_developer_extras") - var enableDev func(uintptr, bool) - purego.RegisterLibFunc(&enableDev, webkit, "webkit_settings_set_enable_developer_extras") - settings := getSettings(w.webview) - enabled := isEnabled(settings) - enableDev(settings, !enabled) -} - -func (w *linuxWebviewWindow) reload() { - var reload func(uintptr) - purego.RegisterLibFunc(&reload, webkit, "webkit_web_view_reload") - reload(w.webview) -} - -func (w *linuxWebviewWindow) forceReload() { - var reload func(uintptr) - purego.RegisterLibFunc(&reload, webkit, "webkit_web_view_reload_bypass_cache") - reload(w.webview) -} - -func (w linuxWebviewWindow) getCurrentMonitor() uintptr { - var getDisplay func(uintptr) uintptr - purego.RegisterLibFunc(&getDisplay, gtk, "gtk_widget_get_display") - var getWindow func(uintptr) uintptr - purego.RegisterLibFunc(&getWindow, gtk, "gtk_widget_get_window") - var getMonitor func(uintptr, uintptr) uintptr - purego.RegisterLibFunc(&getMonitor, gtk, "gdk_display_get_monitor_at_window") - - display := getDisplay(w.window) - window := getWindow(w.window) - if window == 0 { - return 0 - } - return getMonitor(display, window) -} - -func (w linuxWebviewWindow) getCurrentMonitorGeometry() (x int, y int, width int, height int, scale int) { - var getGeometry func(uintptr, uintptr) - purego.RegisterLibFunc(&getGeometry, gtk, "gdk_monitor_get_geometry") - var getScaleFactor func(uintptr) int - purego.RegisterLibFunc(&getScaleFactor, gtk, "gdk_monitor_get_scale_factor") - - monitor := w.getCurrentMonitor() - if monitor == 0 { - return -1, -1, -1, -1, 1 - } - result := struct { - x int32 - y int32 - width int32 - height int32 - }{} - getGeometry(monitor, uintptr(unsafe.Pointer(&result))) - scale = getScaleFactor(monitor) - return int(result.x), int(result.y), int(result.width), int(result.height), scale -} - -func (w *linuxWebviewWindow) center() { - fmt.Println("attempting to set in the center") - - x, y, width, height, _ := w.getCurrentMonitorGeometry() - if x == -1 && y == -1 && width == -1 && height == -1 { - return - } - - windowWidth, windowHeight := w.size() - - newX := ((width - int(windowWidth)) / 2) + x - newY := ((height - int(windowHeight)) / 2) + y - - w.setRelativePosition(newX, newY) -} - -func (w *linuxWebviewWindow) isMinimised() bool { - var getWindow func(uintptr) uintptr - purego.RegisterLibFunc(&getWindow, gtk, "gtk_widget_get_window") - var getWindowState func(uintptr) int - purego.RegisterLibFunc(&getWindowState, gtk, "gdk_window_get_state") - - return w.syncMainThreadReturningBool(func() bool { - state := getWindowState(getWindow(w.window)) - return state&GDK_WINDOW_STATE_ICONIFIED > 0 - }) -} - -func (w *linuxWebviewWindow) isMaximised() bool { - var getWindow func(uintptr) uintptr - purego.RegisterLibFunc(&getWindow, gtk, "gtk_widget_get_window") - var getWindowState func(uintptr) int - purego.RegisterLibFunc(&getWindowState, gtk, "gdk_window_get_state") - - return w.syncMainThreadReturningBool(func() bool { - state := getWindowState(getWindow(w.window)) - return state&GDK_WINDOW_STATE_MAXIMIZED > 0 && state&GDK_WINDOW_STATE_FULLSCREEN == 0 - }) -} - -func (w *linuxWebviewWindow) isFullscreen() bool { - var getWindow func(uintptr) uintptr - purego.RegisterLibFunc(&getWindow, gtk, "gtk_widget_get_window") - var getWindowState func(uintptr) int - purego.RegisterLibFunc(&getWindowState, gtk, "gdk_window_get_state") - - return w.syncMainThreadReturningBool(func() bool { - state := getWindowState(getWindow(w.window)) - return state&GDK_WINDOW_STATE_FULLSCREEN > 0 - }) -} - -func (w *linuxWebviewWindow) syncMainThreadReturningBool(fn func() bool) bool { - var wg sync.WaitGroup - wg.Add(1) - var result bool - globalApplication.dispatchOnMainThread(func() { - result = fn() - wg.Done() - }) - wg.Wait() - return result -} - -func (w *linuxWebviewWindow) restore() { - // restore window to normal size - fmt.Println("restore") -} - -func (w *linuxWebviewWindow) execJS(js string) { - var evalJS func(uintptr, string, int, uintptr, string, uintptr, uintptr, uintptr) - purego.RegisterLibFunc(&evalJS, webkit, "webkit_web_view_evaluate_javascript") - evalJS(w.webview, js, len(js), 0, "", 0, 0, 0) -} - -func (w *linuxWebviewWindow) setURL(uri string) { - fmt.Println("setURL", uri) - var loadUri func(uintptr, string) - purego.RegisterLibFunc(&loadUri, webkit, "webkit_web_view_load_uri") - - url, err := url.Parse(uri) - if url != nil && err == nil && url.Scheme == "" && url.Host == "" { - // TODO handle this in a central location, the scheme and host might be platform dependant - url.Scheme = "wails" - url.Host = "wails" - uri = url.String() - loadUri(w.webview, uri) - } -} - -func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { - var keepAbove func(uintptr, bool) - purego.RegisterLibFunc(&keepAbove, gtk, "gtk_window_set_keep_above") - keepAbove(w.window, alwaysOnTop) -} - -func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow { - return &linuxWebviewWindow{ - application: getNativeApplication().application, - parent: parent, - } -} - -func (w *linuxWebviewWindow) setTitle(title string) { - if !w.parent.options.Frameless { - var setTitle func(uintptr, string) - purego.RegisterLibFunc(&setTitle, gtk, "gtk_window_set_title") - setTitle(w.window, title) - } -} - -func (w *linuxWebviewWindow) setSize(width, height int) { - var setSize func(uintptr, int, int) - purego.RegisterLibFunc(&setSize, gtk, "gtk_window_set_default_size") - setSize(w.window, width, height) -} - -func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight int) { - fmt.Println("setMinMaxSize", minWidth, minHeight, maxWidth, maxHeight) - if minWidth == 0 { - minWidth = -1 - } - if minHeight == 0 { - minHeight = -1 - } - if maxWidth == 0 { - maxWidth = -1 - } - if maxHeight == 0 { - maxHeight = -1 - } - size := GdkGeometry{ - minWidth: int32(minWidth), - minHeight: int32(minHeight), - maxWidth: int32(maxWidth), - maxHeight: int32(maxHeight), - } - - var setHints func(uintptr, uintptr, uintptr, int) - purego.RegisterLibFunc(&setHints, gtk, "gtk_window_set_geometry_hints") - setHints(w.window, 0, uintptr(unsafe.Pointer(&size)), GDK_HINT_MIN_SIZE|GDK_HINT_MAX_SIZE) -} - -func (w *linuxWebviewWindow) setMinSize(width, height int) { - w.setMinMaxSize(width, height, w.parent.options.MaxWidth, w.parent.options.MaxHeight) -} - -func (w *linuxWebviewWindow) setMaxSize(width, height int) { - w.setMinMaxSize(w.parent.options.MinWidth, w.parent.options.MinHeight, width, height) -} - -func (w *linuxWebviewWindow) setResizable(resizable bool) { - var setResizable func(uintptr, int) - purego.RegisterLibFunc(&setResizable, gtk, "gtk_window_set_resizable") - globalApplication.dispatchOnMainThread(func() { - if resizable { - setResizable(w.window, 1) - } else { - setResizable(w.window, 0) - } - }) -} - -func (w *linuxWebviewWindow) size() (int, int) { - var width, height int - var windowGetSize func(uintptr, *int, *int) - purego.RegisterLibFunc(&windowGetSize, gtk, "gtk_window_get_size") - - var wg sync.WaitGroup - wg.Add(1) - globalApplication.dispatchOnMainThread(func() { - windowGetSize(w.window, &width, &height) - wg.Done() - }) - wg.Wait() - return width, height -} - -func (w *linuxWebviewWindow) setRelativePosition(x, y int) { - var windowMove func(uintptr, int, int) - purego.RegisterLibFunc(&windowMove, gtk, "gtk_window_move") - mx, my, _, _, _ := w.getCurrentMonitorGeometry() - fmt.Println("setRelativePosition", mx, my) - globalApplication.dispatchOnMainThread(func() { - windowMove(w.window, x+mx, y+my) - }) -} - -func (w *linuxWebviewWindow) width() int { - width, _ := w.size() - return width -} - -func (w *linuxWebviewWindow) height() int { - _, height := w.size() - return height -} - -func (w *linuxWebviewWindow) buttonPress(widget uintptr, event uintptr, user_data uintptr) { - GdkEventButton := (*byte)(unsafe.Pointer(event)) - fmt.Println("buttonpress", w.parent.id, widget, GdkEventButton, user_data) -} - -func (w *linuxWebviewWindow) buttonRelease(widget uintptr, event uintptr, user_data uintptr) { - GdkEventButton := (*byte)(unsafe.Pointer(event)) - fmt.Println("buttonrelease", w.parent.id, widget, GdkEventButton, user_data) -} - -func (w *linuxWebviewWindow) run() { - for eventId := range w.parent.eventListeners { - w.on(eventId) - } - - globalApplication.dispatchOnMainThread(func() { - app := getNativeApplication() - menu := app.applicationMenu - var newWindow func(uintptr) uintptr - purego.RegisterLibFunc(&newWindow, gtk, "gtk_application_window_new") - var refSink func(uintptr) - purego.RegisterLibFunc(&refSink, gtk, "g_object_ref_sink") - var boxNew func(int, int) uintptr - purego.RegisterLibFunc(&boxNew, gtk, "gtk_box_new") - var containerAdd func(uintptr, uintptr) - purego.RegisterLibFunc(&containerAdd, gtk, "gtk_container_add") - var boxPackStart func(uintptr, uintptr, int, int, int) - purego.RegisterLibFunc(&boxPackStart, gtk, "gtk_box_pack_start") - - var g_signal_connect func(uintptr, string, uintptr, uintptr, bool, int) int - purego.RegisterLibFunc(&g_signal_connect, gtk, "g_signal_connect_data") - - w.window = newWindow(w.application) - - refSink(w.window) - w.webview = w.newWebview(1) - w.vbox = boxNew(1, 0) - containerAdd(w.window, w.vbox) - if menu != 0 { - w.menubar = menu - boxPackStart(w.vbox, menu, 0, 0, 0) - } - boxPackStart(w.vbox, w.webview, 1, 1, 0) - - w.setSize(w.parent.options.Width, w.parent.options.Height) - w.setTitle(w.parent.options.Title) - w.setAlwaysOnTop(w.parent.options.AlwaysOnTop) - w.setResizable(!w.parent.options.DisableResize) - if w.parent.options.MinWidth != 0 && - w.parent.options.MinHeight != 0 && - w.parent.options.MaxWidth != 0 && - w.parent.options.MaxHeight != 0 { - w.setMinMaxSize( - w.parent.options.MinWidth, - w.parent.options.MinHeight, - w.parent.options.MaxWidth, - w.parent.options.MaxHeight, - ) - } - w.setZoom(w.parent.options.Zoom) - w.setBackgroundColour(w.parent.options.BackgroundColour) - w.setFrameless(w.parent.options.Frameless) - - switch w.parent.options.StartState { - case WindowStateMaximised: - w.maximise() - case WindowStateMinimised: - w.minimise() - case WindowStateFullscreen: - w.fullscreen() - - } - if w.parent.options.URL != "" { - w.setURL(w.parent.options.URL) - } - // We need to wait for the HTML to load before we can execute the javascript - w.parent.On(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEventContext) { - if w.parent.options.JS != "" { - w.execJS(w.parent.options.JS) - } - if w.parent.options.CSS != "" { - js := fmt.Sprintf("(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%s')); document.head.appendChild(style); })();", w.parent.options.CSS) - w.execJS(js) - } - }) - if w.parent.options.HTML != "" { - w.setHTML(w.parent.options.HTML) - } - if w.parent.options.Hidden == false { - w.show() - if w.parent.options.X != 0 || w.parent.options.Y != 0 { - w.setRelativePosition(w.parent.options.X, w.parent.options.Y) - } else { - w.center() - } - } - }) -} - -func (w *linuxWebviewWindow) setTransparent() { - var getScreen func(uintptr) uintptr - purego.RegisterLibFunc(&getScreen, gtk, "gtk_widget_get_screen") - var getVisual func(uintptr) uintptr - purego.RegisterLibFunc(&getVisual, gtk, "gdk_screen_get_rgba_visual") - var isComposited func(uintptr) int - purego.RegisterLibFunc(&isComposited, gtk, "gdk_screen_is_composited") - var setPaintable func(uintptr, int) - purego.RegisterLibFunc(&setPaintable, gtk, "gtk_widget_set_app_paintable") - var setVisual func(uintptr, uintptr) - purego.RegisterLibFunc(&setVisual, gtk, "gtk_widget_set_visual") - - screen := getScreen(w.window) - visual := getVisual(screen) - if visual == 0 { - return - } - if isComposited(screen) == 1 { - setPaintable(w.window, 1) - setVisual(w.window, visual) - } -} - -func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) { - if colour.Alpha != 0 { - w.setTransparent() - } - - var rgbaParse func(uintptr, string) bool - purego.RegisterLibFunc(&rgbaParse, gtk, "gdk_rgba_parse") - var setBackgroundColor func(uintptr, uintptr) - purego.RegisterLibFunc(&setBackgroundColor, webkit, "webkit_web_view_set_background_color") - - rgba := make([]byte, 4*8) // C.sizeof_GdkRGBA == 32 - pointer := uintptr(unsafe.Pointer(&rgba[0])) - if !rgbaParse( - pointer, - fmt.Sprintf("rgba(%v,%v,%v,%v)", - colour.Red, - colour.Green, - colour.Blue, - float32(colour.Alpha)/255.0, - )) { - return - } - setBackgroundColor(w.webview, pointer) -} - -func (w *linuxWebviewWindow) relativePosition() (int, int) { - var getPosition func(uintptr, *int, *int) bool - purego.RegisterLibFunc(&getPosition, gtk, "gtk_window_get_position") - - var x, y int - var wg sync.WaitGroup - wg.Add(1) - go globalApplication.dispatchOnMainThread(func() { - getPosition(w.window, &x, &y) - - // Get the position of the window relative to the screen - var getOrigin func(uintptr, *int, *int) - purego.RegisterLibFunc(&getOrigin, gtk, "gtk_widget_translate_coordinates") - getOrigin(w.window, &x, &y) - - wg.Done() - }) - wg.Wait() - return x, y -} - -func (w *linuxWebviewWindow) absolutePosition() (int, int) { - var getOrigin func(uintptr, *int, *int) - purego.RegisterLibFunc(&getOrigin, gtk, "gtk_widget_translate_coordinates") - - var x, y int - var wg sync.WaitGroup - wg.Add(1) - go globalApplication.dispatchOnMainThread(func() { - getOrigin(w.window, nil, nil) - wg.Done() - }) - wg.Wait() - return x, y -} - -func (w *linuxWebviewWindow) destroy() { - var close func(uintptr) - purego.RegisterLibFunc(&close, gtk, "gtk_window_close") - go globalApplication.dispatchOnMainThread(func() { - close(w.window) - }) -} - -func (w *linuxWebviewWindow) setHTML(html string) { - var loadHTML func(uintptr, string, string, *string) - purego.RegisterLibFunc(&loadHTML, webkit, "webkit_web_view_load_alternate_html") - go globalApplication.dispatchOnMainThread(func() { - loadHTML(w.webview, html, "wails://", nil) - }) -} - -func (w *linuxWebviewWindow) isNormal() bool { - return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen() -} - -func (w *linuxWebviewWindow) isVisible() bool { - var isVisible func(uintptr) bool - purego.RegisterLibFunc(&isVisible, gtk, "gtk_widget_is_visible") - return isVisible(w.window) -} - -func (w *linuxWebviewWindow) nativeWindowHandle() uintptr { - return w.window -}