5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 14:13:00 +08:00

Revert "Merge branch 'v3-alpha_linux' into v3-alpha"

This reverts commit b317efaf2c, reversing
changes made to 29b9c5200f.
This commit is contained in:
Travis McLane 2023-06-23 17:29:49 -05:00
parent 56d11ab419
commit 94e1ec91ad
31 changed files with 69 additions and 4770 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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 | | | | |

View File

@ -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

View File

@ -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=

View File

@ -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()

View File

@ -1,268 +0,0 @@
//go:build linux && !purego
package application
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <webkit2/webkit2.h>
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
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)))
}

View File

@ -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(&gtkNew, 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);*/
}

View File

@ -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{}
}

View File

@ -1,300 +0,0 @@
//go:build linux
package application
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <stdio.h>
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")
}
}

View File

@ -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")
}
}

View File

@ -1,51 +0,0 @@
//go:build linux
package application
/*
#cgo linux pkg-config: gtk+-3.0
#include <stdio.h>
#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()
}

View File

@ -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()
}

View File

@ -1,200 +0,0 @@
//go:build linux
package application
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <gtk/gtk.h>
#include <gdk/gdk.h>
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
}

View File

@ -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
}

View File

@ -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)

View File

@ -1,407 +0,0 @@
//go:build linux
package application
import (
"fmt"
"runtime"
"unsafe"
)
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdio.h>
#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()
}
})
}

View File

@ -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()
}
})
}

View File

@ -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) {

View File

@ -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) {

View File

@ -1,6 +0,0 @@
package application
// LinuxWindow contains macOS specific options
type LinuxWindow struct {
ShowApplicationMenu bool
}

View File

@ -1,94 +0,0 @@
//go:build linux
package application
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <stdlib.h>
#include <stdbool.h>
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()
}

View File

@ -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()
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -1,797 +0,0 @@
//go:build linux && !purego
package application
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <webkit2/webkit2.h>
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
// 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)
}

View File

@ -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(&registerScriptMessageHandler, 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(&registerUriScheme, 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(&gtkWidgetSensitive, 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
}