5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 18:19:36 +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 Application interface methods
| Method | Windows | Linux | Mac | Notes | | Method | Windows | Linux | Mac | Notes |
|---------------------------------------------------------------|---------|-------|-----|------------------------------| |---------------------------------------------------------------|---------|-------|-----|-------|
| run() error | Y | Y | Y | | | run() error | Y | | Y | |
| destroy() | | Y | Y | | | destroy() | | | Y | |
| setApplicationMenu(menu *Menu) | Y | | Y | | | setApplicationMenu(menu *Menu) | Y | | Y | |
| name() string | | | Y | | | name() string | | | Y | |
| getCurrentWindowID() uint | Y | Y | Y | | | getCurrentWindowID() uint | Y | | Y | |
| showAboutDialog(name string, description string, icon []byte) | | Y | Y | [linux] No icon possible yet | | showAboutDialog(name string, description string, icon []byte) | | | Y | |
| setIcon(icon []byte) | - | | Y | | | setIcon(icon []byte) | - | | Y | |
| on(id uint) | | | Y | | | on(id uint) | | | Y | |
| dispatchOnMainThread(fn func()) | Y | Y | Y | | | dispatchOnMainThread(fn func()) | Y | | Y | |
| hide() | Y | | Y | | | hide() | Y | | Y | |
| show() | Y | | Y | | | show() | Y | | Y | |
| getPrimaryScreen() (*Screen, error) | | Y | Y | | | getPrimaryScreen() (*Screen, error) | | | Y | |
| getScreens() ([]*Screen, error) | | Y | Y | | | getScreens() ([]*Screen, error) | | | Y | |
## Webview Window ## Webview Window
@ -90,7 +90,7 @@ Webview Window Interface Methods
| Feature | Windows | Linux | Mac | Notes | | Feature | Windows | Linux | Mac | Notes |
|---------|---------|-------|-----|-------| |---------|---------|-------|-----|-------|
| Quit | Y | Y | Y | | | Quit | Y | | Y | |
| Hide | Y | | Y | | | Hide | Y | | Y | |
| Show | Y | | Y | | | Show | Y | | Y | |
@ -132,9 +132,9 @@ explicitly set with `--default-contextmenu: show`.
| Feature | Windows | Linux | Mac | Notes | | Feature | Windows | Linux | Mac | Notes |
|------------|---------|-------|-----|-------| |------------|---------|-------|-----|-------|
| GetAll | Y | Y | Y | | | GetAll | Y | | Y | |
| GetPrimary | Y | Y | Y | | | GetPrimary | Y | | Y | |
| GetCurrent | Y | Y | Y | | | GetCurrent | Y | | Y | |
### Window ### 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. 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. An 'X' indicates that the option is not supported by the platform.
| Feature | Windows | Linux | Mac | Notes | | Feature | Windows | Linux | Mac | Notes |
|---------------------------------|---------|-------|-----|---------------------------------------------------| |---------------------------------|---------|-------|-----|--------------------------------------------|
| Name | | | | | | AlwaysOnTop | Y | | | |
| Title | Y | | | | | BackgroundColour | Y | | | |
| Width | Y | Y | | | | BackgroundType | | | | Acrylic seems to work but the others don't |
| Height | Y | Y | | | | CSS | Y | | | |
| AlwaysOnTop | Y | Y | | | | DevToolsEnabled | Y | | Y | |
| URL | Y | | | | | DisableResize | Y | | | |
| DisableResize | Y | Y | | | | EnableDragAndDrop | | | | |
| Frameless | Y | Y | | | | EnableFraudulentWebsiteWarnings | | | | |
| MinWidth | Y | Y | | | | Focused | Y | | | |
| MinHeight | Y | Y | | | | Frameless | Y | | | |
| MaxWidth | Y | Y | | | | FullscreenButtonEnabled | Y | | | |
| MaxHeight | Y | Y | | | | Height | Y | | | |
| StartState | Y | | | | | Hidden | Y | | | |
| Mac | - | - | | | | HTML | Y | | | |
| BackgroundType | | | | Acrylic seems to work but the others don't | | JS | Y | | | |
| BackgroundColour | Y | Y | | | | Mac | - | - | | |
| HTML | Y | Y | | | | MaxHeight | Y | | | |
| JS | Y | Y | | | | MaxWidth | Y | | | |
| CSS | Y | Y | | | | MinHeight | Y | | | |
| X | Y | Y | | | | MinWidth | Y | | | |
| Y | Y | Y | | | | Name | Y | | | |
| HideOnClose | Y | Y | | | | OpenInspectorOnStartup | | | | |
| FullscreenButtonEnabled | | ? | | [linux] How is this different from DisableResize? | | StartState | Y | | | |
| Hidden | Y | | | | | Title | Y | | | |
| EnableFraudulentWebsiteWarnings | | | | | | URL | Y | | | |
| Zoom | | Y | | | | Width | Y | | | |
| EnableDragAndDrop | Y | Y | | | | Windows | Y | - | - | |
| Windows | Y | - | - | | | X | Y | | | |
| Focused | Y | | | | | Y | Y | | | |
| Zoom | | | | |
| ZoomControlEnabled | | | | |
### Log ### Log
@ -220,7 +222,7 @@ To log or not to log? System logger vs custom logger.
| Event | Windows | Linux | Mac | Notes | | Event | Windows | Linux | Mac | Notes |
|--------------------------|---------|-------|-----|-------| |--------------------------|---------|-------|-----|-------|
| Default Application Menu | Y | Y | Y | | | Default Application Menu | Y | | Y | |
## Tray Menus ## Tray Menus
@ -293,10 +295,10 @@ Built-in plugin support:
| Plugin | Windows | Linux | Mac | Notes | | Plugin | Windows | Linux | Mac | Notes |
|-----------------|---------|-------|-----|-------| |-----------------|---------|-------|-----|-------|
| Browser | Y | | Y | | | Browser | Y | | Y | |
| KV Store | Y | Y | Y | | | KV Store | Y | | Y | |
| Log | Y | Y | Y | | | Log | Y | | Y | |
| Single Instance | Y | | Y | | | Single Instance | Y | | Y | |
| SQLite | Y | Y | Y | | | SQLite | Y | | Y | |
| Start at login | | | Y | | | Start at login | | | Y | |
| Server | | | | | | Server | | | | |

View File

@ -3,7 +3,6 @@ module github.com/wailsapp/wails/v3
go 1.19 go 1.19
require ( require (
github.com/ebitengine/purego v0.3.2
github.com/bep/debounce v1.2.1 github.com/bep/debounce v1.2.1
github.com/go-ole/go-ole v1.2.6 github.com/go-ole/go-ole v1.2.6
github.com/go-task/task/v3 v3.20.0 github.com/go-task/task/v3 v3.20.0
@ -76,5 +75,3 @@ require (
) )
replace github.com/wailsapp/wails/v2 => ../v2 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.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
github.com/MarvinJWendt/testza v0.5.1 h1:a9Fqx6vQrHQ4CyiaLhktfTTelwGotmFWy8MNhyaohw8= 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/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 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= 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 { func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow {
newWindow := NewWindow(windowOptions) newWindow := NewWindow(windowOptions)
id := newWindow.id id := newWindow.id
if a.windows == nil {
a.windows = make(map[uint]*WebviewWindow)
}
a.windowsLock.Lock() a.windowsLock.Lock()
a.windows[id] = newWindow a.windows[id] = newWindow
a.windowsLock.Unlock() 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 { if m.itemType == radio {
for _, member := range m.radioGroupMembers { for _, member := range m.radioGroupMembers {
member.checked = false member.checked = false
if member.impl != nil { member.impl.setChecked(false)
member.impl.setChecked(false)
}
} }
m.checked = true m.checked = true
ctx.withChecked(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" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
) )
func (m *MessageProcessor) callErrorCallback(window *WebviewWindow, message string, callID *string, err error) { func (m *MessageProcessor) callErrorCallback(window *WebviewWindow, message string, callID *string, err error) {
errorMsg := fmt.Sprintf(message, err) errorMsg := fmt.Sprintf(message, err)
m.Error(errorMsg) 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) { 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) { func (m *MessageProcessor) processCallMethod(method string, rw http.ResponseWriter, _ *http.Request, window *WebviewWindow, params QueryParams) {

View File

@ -5,16 +5,19 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"runtime" "runtime"
"strconv"
) )
func (m *MessageProcessor) dialogErrorCallback(window *WebviewWindow, message string, dialogID *string, err error) { func (m *MessageProcessor) dialogErrorCallback(window *WebviewWindow, message string, dialogID *string, err error) {
errorMsg := fmt.Sprintf(message, err) errorMsg := fmt.Sprintf(message, err)
m.Error(errorMsg) 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) { 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) { 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) { func (s *macosSystemTray) setTemplateIcon(icon []byte) {
s.icon = icon s.icon = icon
s.isTemplateIcon = true 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 package application
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/samber/lo" "github.com/samber/lo"
@ -150,39 +149,6 @@ func (w *WebviewWindow) addCancellationFunction(canceller func()) {
w.cancellers = append(w.cancellers, canceller) 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 // SetTitle sets the title of the window
func (w *WebviewWindow) SetTitle(title string) *WebviewWindow { func (w *WebviewWindow) SetTitle(title string) *WebviewWindow {
w.options.Title = title 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
}