5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-19 02:19:31 +08:00

Merge branch 'v3-alpha-linux' into v3-alpha

This commit is contained in:
Travis McLane 2023-06-23 21:51:21 -05:00
commit eb18f02fd2
29 changed files with 2462 additions and 3090 deletions

View File

@ -4,7 +4,6 @@
package webview package webview
import ( import (
"fmt"
"io" "io"
"net/http" "net/http"
@ -76,7 +75,6 @@ func (r *request) Body() (io.ReadCloser, error) {
} }
func (r *request) Response() ResponseWriter { func (r *request) Response() ResponseWriter {
fmt.Println("r.Response()")
if r.rw != nil { if r.rw != nil {
return r.rw return r.rw
} }

View File

@ -89,7 +89,6 @@ func (rw *responseWriter) WriteHeader(code int) {
contentLength = pLen 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 // 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. // read FD is given to the InputStream and will be closed there.
// Furthermore we especially don't want to have the FD_CLOEXEC // Furthermore we especially don't want to have the FD_CLOEXEC

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 | Y | |
| destroy() | | Y | Y | | | destroy() | | Y | Y | |
| setApplicationMenu(menu *Menu) | Y | | Y | | | setApplicationMenu(menu *Menu) | Y | Y | Y | |
| name() string | | | Y | | | name() string | | Y | Y | |
| getCurrentWindowID() uint | Y | Y | Y | | | getCurrentWindowID() uint | Y | Y | Y | |
| showAboutDialog(name string, description string, icon []byte) | | Y | Y | [linux] No icon possible yet | | showAboutDialog(name string, description string, icon []byte) | | Y | Y | |
| setIcon(icon []byte) | - | | Y | | | setIcon(icon []byte) | - | Y | Y | |
| on(id uint) | | | Y | | | on(id uint) | | | Y | |
| dispatchOnMainThread(fn func()) | Y | Y | Y | | | dispatchOnMainThread(fn func()) | Y | Y | Y | |
| hide() | Y | | Y | | | hide() | Y | Y | Y | |
| show() | Y | | Y | | | show() | Y | Y | Y | |
| getPrimaryScreen() (*Screen, error) | | Y | Y | | | getPrimaryScreen() (*Screen, error) | | Y | Y | |
| getScreens() ([]*Screen, error) | | Y | Y | | | getScreens() ([]*Screen, error) | | Y | 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 | Y | |
| Hide | Y | | Y | | | Hide | Y | | Y | |
| Show | Y | | Y | | | Show | Y | | Y | |
@ -98,10 +98,10 @@ Webview Window Interface Methods
| Feature | Windows | Linux | Mac | Notes | | Feature | Windows | Linux | Mac | Notes |
|----------|---------|-------|-----|-------| |----------|---------|-------|-----|-------|
| Info | Y | | Y | | | Info | Y | Y | Y | |
| Warning | Y | | Y | | | Warning | Y | Y | Y | |
| Error | Y | | Y | | | Error | Y | Y | Y | |
| Question | Y | | Y | | | Question | Y | Y | Y | |
| OpenFile | Y | | Y | | | OpenFile | Y | | Y | |
| SaveFile | Y | | Y | | | SaveFile | Y | | Y | |
@ -180,37 +180,40 @@ 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 | Y | | |
| Width | Y | Y | | | | BackgroundType | | | | Acrylic seems to work but the others don't |
| Height | Y | Y | | | | CSS | Y | Y | | |
| AlwaysOnTop | Y | Y | | | | DevToolsEnabled | Y | Y | Y | |
| URL | Y | | | | | DisableResize | Y | Y | | |
| DisableResize | Y | Y | | | | EnableDragAndDrop | | Y | | |
| Frameless | Y | Y | | | | EnableFraudulentWebsiteWarnings | | | | |
| MinWidth | Y | Y | | | | Focused | Y | Y | | |
| MinHeight | Y | Y | | | | Frameless | Y | Y | | |
| MaxWidth | Y | Y | | | | FullscreenButtonEnabled | Y | | | |
| MaxHeight | Y | Y | | | | Height | Y | Y | | |
| StartState | Y | | | | | Hidden | Y | Y | | |
| Mac | - | - | | | | HTML | Y | Y | | |
| BackgroundType | | | | Acrylic seems to work but the others don't | | JS | Y | Y | | |
| BackgroundColour | Y | Y | | | | Mac | - | - | | |
| HTML | Y | Y | | | | MaxHeight | Y | Y | | |
| JS | Y | Y | | | | MaxWidth | Y | Y | | |
| CSS | Y | Y | | | | MinHeight | Y | Y | | |
| X | Y | Y | | | | MinWidth | Y | Y | | |
| Y | Y | Y | | | | Name | Y | Y | | |
| HideOnClose | Y | Y | | | | OpenInspectorOnStartup | | | | |
| FullscreenButtonEnabled | | ? | | [linux] How is this different from DisableResize? | | StartState | Y | | | |
| Hidden | Y | | | | | Title | Y | Y | | |
| EnableFraudulentWebsiteWarnings | | | | | | URL | Y | Y | | |
| Zoom | | Y | | | | Width | Y | Y | | |
| EnableDragAndDrop | Y | Y | | | | Windows | Y | - | - | |
| Windows | Y | - | - | | | X | Y | Y | | |
| Focused | Y | | | | | Y | Y | Y | | |
| Zoom | | | | |
| ZoomControlEnabled | | | | |
### Log ### Log
@ -220,7 +223,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 | Y | |
## Tray Menus ## Tray Menus
@ -300,6 +303,8 @@ Built-in plugin support:
| Start at login | | | Y | | | Start at login | | | Y | |
| Server | | | | | | Server | | | | |
TODO:
- Ensure each plugin has a JS wrapper that can be injected into the window. - Ensure each plugin has a JS wrapper that can be injected into the window.
## Packaging ## Packaging
@ -318,7 +323,8 @@ Built-in plugin support:
| Feature | Windows | Linux | Mac | Notes | | Feature | Windows | Linux | Mac | Notes |
|---------|---------|-------|-----|-----------------------------------------------| |---------|---------|-------|-----|-----------------------------------------------|
| Resize | | | | | | Resize | | | | |
| Drag | | Y | | Linux - can always drag with `Alt`+left mouse | | Drag | | Y | | Linux - can always drag with `Meta`+left mouse |
## Mac Specific ## Mac Specific
@ -353,6 +359,24 @@ Built-in plugin support:
## Linux Specific ## Linux Specific
Implementation details for the functions utilized by the `*_linux.go` files are located in the following files:
- linux_cgo.go: CGo implementation
- linux_purego.go: PureGo implementation
### CGO
By default CGO is utilized to compile the Linux port. This prevents easy cross-compilation and so the PureGo implementation is also being simultaneously developed.
### Purego
The examples can be compiled using the following command:
CGO_ENABLED=0 go build -tags purego
Note: things are currently not working after the refactor
## Examples ## Examples
| Example | Windows | Linux | Mac | | Example | Windows | Linux | Mac |
@ -381,4 +405,4 @@ Built-in plugin support:
# Beta Release TODO # Beta Release TODO
- [ ] Make better looking examples - [ ] Make better looking examples

View File

@ -6,6 +6,7 @@ require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
require ( require (
github.com/bep/debounce v1.2.1 // indirect github.com/bep/debounce v1.2.1 // indirect
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
@ -16,7 +17,7 @@ require (
github.com/samber/lo v1.37.0 // indirect github.com/samber/lo v1.37.0 // indirect
github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b // indirect github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect github.com/wailsapp/wails/v2 v2.5.1 // indirect
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.8.0 // indirect
@ -25,3 +26,5 @@ require (
replace github.com/wailsapp/wails/v3 => ../.. replace github.com/wailsapp/wails/v3 => ../..
replace github.com/wailsapp/wails/v2 => ../../../v2 replace github.com/wailsapp/wails/v2 => ../../../v2
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01

View File

@ -26,6 +26,8 @@ github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpo
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01 h1:oQwu3iNDywGp1Hry+PDvz+grwbCGpzY+ckSnWKCnX5Y=
github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b h1:cztK9x+ikg6nFscy5c8NgtfIXv/d0ESdENy9+JkE8i4= github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b h1:cztK9x+ikg6nFscy5c8NgtfIXv/d0ESdENy9+JkE8i4=
github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=

View File

@ -3,8 +3,8 @@ 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/ebitengine/purego v0.4.0-alpha.4
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
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
@ -77,4 +77,4 @@ 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 replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01

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=
@ -145,6 +143,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8= github.com/tc-hib/winres v0.1.6 h1:qgsYHze+BxQPEYilxIz/KCQGaClvI2+yLBAZs+3+0B8=
github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= github.com/tc-hib/winres v0.1.6/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A=
github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01 h1:oQwu3iNDywGp1Hry+PDvz+grwbCGpzY+ckSnWKCnX5Y=
github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b h1:cztK9x+ikg6nFscy5c8NgtfIXv/d0ESdENy9+JkE8i4= github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b h1:cztK9x+ikg6nFscy5c8NgtfIXv/d0ESdENy9+JkE8i4=
github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo= github.com/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=

View File

@ -2,7 +2,6 @@ package application
import ( import (
"encoding/json" "encoding/json"
"github.com/wailsapp/wails/v3/internal/capabilities"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -10,6 +9,8 @@ import (
"strconv" "strconv"
"sync" "sync"
"github.com/wailsapp/wails/v3/internal/capabilities"
"github.com/wailsapp/wails/v3/pkg/icons" "github.com/wailsapp/wails/v3/pkg/icons"
"github.com/samber/lo" "github.com/samber/lo"
@ -333,9 +334,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()
@ -416,7 +415,7 @@ func (a *App) Run() error {
a.runLock.Unlock() a.runLock.Unlock()
// set the application menu // set the application menu
if runtime.GOOS == "darwin" { if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
a.impl.setApplicationMenu(a.ApplicationMenu) a.impl.setApplicationMenu(a.ApplicationMenu)
} }
a.impl.setIcon(a.options.Icon) a.impl.setIcon(a.options.Icon)

View File

@ -1,153 +1,69 @@
//go:build linux && !purego //go:build linux
package application 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 ( import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings" "strings"
"sync" "sync"
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/events"
) )
func init() { func init() {
// FIXME: This should be handled appropriately in the individual files most likely.
// Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings // Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings
_ = os.Setenv("GDK_BACKEND", "x11") _ = os.Setenv("GDK_BACKEND", "x11")
} }
type linuxApp struct { type linuxApp struct {
application unsafe.Pointer application pointer
applicationMenu unsafe.Pointer applicationMenu pointer
parent *App parent *App
startupActions []func() startupActions []func()
// Native -> uint // Native -> uint
windows map[*C.GtkWindow]uint windows map[windowPointer]uint
windowsLock sync.Mutex windowsLock sync.Mutex
} }
func (m *linuxApp) GetFlags(options Options) map[string]any {
if options.Flags == nil {
options.Flags = make(map[string]any)
}
return options.Flags
}
func getNativeApplication() *linuxApp { func getNativeApplication() *linuxApp {
return globalApplication.impl.(*linuxApp) return globalApplication.impl.(*linuxApp)
} }
func (m *linuxApp) hide() { func (m *linuxApp) hide() {
windows := C.gtk_application_get_windows((*C.GtkApplication)(m.application)) hideAllWindows(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() { func (m *linuxApp) show() {
windows := C.gtk_application_get_windows((*C.GtkApplication)(m.application)) showAllWindows(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) { func (m *linuxApp) on(eventID uint) {
// TODO: What do we need to do here?
log.Println("linuxApp.on()", eventID) log.Println("linuxApp.on()", eventID)
// TODO: Setup signal handling as appropriate
// Note: GTK signals seem to be strings!
} }
func (m *linuxApp) setIcon(icon []byte) { func (m *linuxApp) setIcon(icon []byte) {
/* // FIXME: WIP fmt.Println("linuxApp.setIcon", "not implemented")
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 { func (m *linuxApp) name() string {
// appName := C.getAppName() return appName()
// defer C.free(unsafe.Pointer(appName))
// return C.GoString(appName)
return ""
} }
func (m *linuxApp) getCurrentWindowID() uint { func (m *linuxApp) getCurrentWindowID() uint {
// TODO: Add extra metadata to window return getCurrentWindowID(m.application, m.windows)
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) { func (m *linuxApp) setApplicationMenu(menu *Menu) {
@ -172,20 +88,22 @@ func (m *linuxApp) run() error {
fmt.Println("events.Mac.ApplicationDidFinishLaunching received!") fmt.Println("events.Mac.ApplicationDidFinishLaunching received!")
}) })
var app C.App return appRun(m.application)
app.app = unsafe.Pointer(m)
C.run(m.application, m.application)
return nil
} }
func (m *linuxApp) destroy() { func (m *linuxApp) destroy() {
C.g_application_quit((*C.GApplication)(m.application)) appDestroy(m.application)
}
func (m *linuxApp) isOnMainThread() bool {
// FIXME: How do we detect this properly?
return false
} }
// register our window to our parent mapping // register our window to our parent mapping
func (m *linuxApp) registerWindow(window *C.GtkWindow, id uint) { func (m *linuxApp) registerWindow(window pointer, id uint) {
m.windowsLock.Lock() m.windowsLock.Lock()
m.windows[window] = id m.windows[windowPointer(window)] = id
m.windowsLock.Unlock() m.windowsLock.Unlock()
} }
@ -194,30 +112,15 @@ func newPlatformApp(parent *App) *linuxApp {
if name == "" { if name == "" {
name = "undefined" name = "undefined"
} }
nameC := C.CString(fmt.Sprintf("org.wails.%s", name))
app := &linuxApp{ app := &linuxApp{
parent: parent, parent: parent,
application: unsafe.Pointer(C.init(nameC)), application: appNew(name),
// name: fmt.Sprintf("org.wails.%s", name), windows: map[windowPointer]uint{},
windows: map[*C.GtkWindow]uint{},
} }
C.free(unsafe.Pointer(nameC))
return app 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 //export processApplicationEvent
func processApplicationEvent(eventID C.uint) { func processApplicationEvent(eventID C.uint) {
// TODO: add translation to Wails events // TODO: add translation to Wails events
@ -256,7 +159,7 @@ func processDragItems(windowID C.uint, arr **C.char, length C.int) {
} }
//export processMenuItemClick //export processMenuItemClick
func processMenuItemClick(menuID C.uint) { func processMenuItemClick(menuID identifier) {
menuItemClicked <- uint(menuID) menuItemClicked <- uint(menuID)
} }
@ -266,3 +169,4 @@ func setIcon(icon []byte) {
} }
//C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon))) //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

@ -20,12 +20,12 @@ func (m linuxClipboard) setText(text string) bool {
return bool(success) return bool(success)
} }
func (m linuxClipboard) text() string { func (m linuxClipboard) text() (string, bool) {
clipboardLock.RLock() clipboardLock.RLock()
defer clipboardLock.RUnlock() defer clipboardLock.RUnlock()
// clipboardText := C.getClipboardText() // clipboardText := C.getClipboardText()
// result := C.GoString(clipboardText) // result := C.GoString(clipboardText)
return "" return "", false
} }
func newClipboardImpl() *linuxClipboard { func newClipboardImpl() *linuxClipboard {

View File

@ -1,154 +1,40 @@
//go:build linux
package application 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) { func (m *linuxApp) showAboutDialog(title string, message string, icon []byte) {
globalApplication.dispatchOnMainThread(func() { window := globalApplication.getWindowForID(m.getCurrentWindowID())
parent := C.gtk_application_get_active_window((*C.GtkApplication)(m.application)) var parent pointer
cMsg := C.CString(message) if window != nil {
cTitle := C.CString(title) parent = window.impl.(*linuxWebviewWindow).window
defer C.free(unsafe.Pointer(cMsg)) }
defer C.free(unsafe.Pointer(cTitle)) about := newMessageDialog(InfoDialog)
dialog := C.new_about_dialog(parent, cMsg) about.SetTitle(title).
C.gtk_window_set_title( SetMessage(message).
(*C.GtkWindow)(unsafe.Pointer(dialog)), SetIcon(icon)
cTitle) runQuestionDialog(
// setWindowIcon((*C.GtkWindow)(dialog), icon) parent,
C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog))) about,
)
})
} }
type linuxDialog struct { type linuxDialog struct {
dialog *MessageDialog dialog *MessageDialog
//nsDialog unsafe.Pointer
} }
func (m *linuxDialog) show() { func (m *linuxDialog) show() {
globalApplication.dispatchOnMainThread(func() { windowId := getNativeApplication().getCurrentWindowID()
window := globalApplication.getWindowForID(windowId)
var parent pointer
if window != nil {
parent = window.impl.(*linuxWebviewWindow).window
}
// Mac can only have 4 Buttons on a dialog response := runQuestionDialog(parent, m.dialog)
if len(m.dialog.Buttons) > 4 { if response >= 0 && response < len(m.dialog.Buttons) {
m.dialog.Buttons = m.dialog.Buttons[:4] button := m.dialog.Buttons[response]
if button.Callback != nil {
go button.Callback()
} }
}
// 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 { func newDialogImpl(d *MessageDialog) *linuxDialog {
@ -167,85 +53,8 @@ func newOpenFileDialogImpl(d *OpenFileDialog) *linuxOpenFileDialog {
} }
} }
func toCString(s string) *C.char {
if s == "" {
return nil
}
return C.CString(s)
}
func (m *linuxOpenFileDialog) show() ([]string, error) { func (m *linuxOpenFileDialog) show() ([]string, error) {
openFileResponses[m.dialog.id] = make(chan string) return runOpenFileDialog(m.dialog)
// 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 { type linuxSaveFileDialog struct {
@ -259,42 +68,5 @@ func newSaveFileDialogImpl(d *SaveFileDialog) *linuxSaveFileDialog {
} }
func (m *linuxSaveFileDialog) show() (string, error) { func (m *linuxSaveFileDialog) show() (string, error) {
saveFileResponses[m.dialog.id] = make(chan string) return runSaveFileDialog(m.dialog)
// 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

@ -0,0 +1,997 @@
//go:build linux && cgo
package application
import (
"fmt"
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
"github.com/wailsapp/wails/v3/pkg/events"
)
/*
#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 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);
}
typedef struct WindowEvent {
uint id;
uint event;
} WindowEvent;
// exported below
void activateLinux(gpointer data);
extern void emit(WindowEvent* data);
void handleClick(void*);
extern gboolean onButtonEvent(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
extern void onDragNDrop(
void *target,
GdkDragContext* context,
gint x,
gint y,
gpointer seldata,
guint info,
guint time,
gpointer data);
extern void onProcessRequest(void *request, gpointer user_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);
}
static void* new_message_dialog(GtkWindow *parent, const gchar *msg, int dialogType, bool hasButtons) {
// gtk_message_dialog_new is variadic! Can't call from cgo directly
GtkWidget *dialog;
int buttonMask;
// buttons will be added after creation
buttonMask = GTK_BUTTONS_OK;
if (hasButtons) {
buttonMask = GTK_BUTTONS_NONE;
}
dialog = gtk_message_dialog_new(
parent,
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
dialogType,
buttonMask,
msg);
// g_signal_connect_swapped (dialog,
// "response",
// G_CALLBACK (callback),
// dialog);
return dialog;
};
extern void messageDialogCB(gint button);
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;
static int GetNumScreens(){
return 0;
}
*/
import "C"
type windowPointer *C.GtkWindow
type identifier C.uint
type pointer unsafe.Pointer
type GSList C.GSList
type GSListPointer *GSList
var (
nilRadioGroup GSListPointer = nil
gtkSignalHandlers map[*C.GtkWidget]C.gulong
gtkSignalToMenuItem map[*C.GtkWidget]*MenuItem
)
func init() {
fmt.Println("linux_cgo")
gtkSignalHandlers = map[*C.GtkWidget]C.gulong{}
gtkSignalToMenuItem = map[*C.GtkWidget]*MenuItem{}
}
// mainthread stuff
func dispatchOnMainThread(id uint) {
C.dispatchOnMainThread(C.uint(id))
}
//export dispatchOnMainThreadCallback
func dispatchOnMainThreadCallback(callbackID C.uint) {
executeOnMainThread(uint(callbackID))
}
//export activateLinux
func activateLinux(data pointer) {
// NOOP: Callback for now
}
// implementation below
func appName() string {
name := C.g_get_application_name()
defer C.free(unsafe.Pointer(name))
return C.GoString(name)
}
func appNew(name string) pointer {
nameC := C.CString(fmt.Sprintf("org.wails.%s", name))
defer C.free(unsafe.Pointer(nameC))
return pointer(C.gtk_application_new(nameC, C.G_APPLICATION_DEFAULT_FLAGS))
}
func appRun(app pointer) error {
application := (*C.GApplication)(app)
C.g_application_hold(application) // allows it to run without a window
signal := C.CString("activate")
C.g_signal_connect_data(C.gpointer(application), signal, C.GCallback(C.activateLinux), nil, nil, 0)
status := C.g_application_run(application, 0, nil)
C.g_application_release(application)
C.g_object_unref(C.gpointer(app))
var err error
if status != 0 {
err = fmt.Errorf("exit code: %d", status)
}
return err
}
func appDestroy(application pointer) {
C.g_application_quit((*C.GApplication)(application))
}
func getCurrentWindowID(application pointer, windows map[windowPointer]uint) uint {
// TODO: Add extra metadata to window and use it!
window := (*C.GtkWindow)(C.gtk_application_get_active_window((*C.GtkApplication)(application)))
if window == nil {
return uint(1)
}
identifier, ok := windows[window]
if ok {
return identifier
}
// FIXME: Should we panic here if not found?
return uint(1)
}
func getWindows(application pointer) []pointer {
result := []pointer{}
windows := C.gtk_application_get_windows((*C.GtkApplication)(application))
for {
result = append(result, pointer(windows.data))
windows = windows.next
if windows == nil {
return result
}
}
}
func hideAllWindows(application pointer) {
for _, window := range getWindows(application) {
C.gtk_widget_hide((*C.GtkWidget)(window))
}
}
func showAllWindows(application pointer) {
for _, window := range getWindows(application) {
C.gtk_widget_show_all((*C.GtkWidget)(window))
}
}
// Menu
func menuAddSeparator(menu *Menu) {
C.gtk_menu_shell_append(
(*C.GtkMenuShell)((menu.impl).(*linuxMenu).native),
C.gtk_separator_menu_item_new())
}
func menuAppend(parent *Menu, menu *MenuItem) {
C.gtk_menu_shell_append(
(*C.GtkMenuShell)((parent.impl).(*linuxMenu).native),
(*C.GtkWidget)((menu.impl).(*linuxMenuItem).native),
)
/* gtk4
C.gtk_menu_item_set_submenu(
(*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native),
(*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native),
)
*/
}
func menuBarNew() pointer {
return pointer(C.gtk_menu_bar_new())
}
func menuNew() pointer {
return pointer(C.gtk_menu_new())
}
func menuSetSubmenu(item *MenuItem, menu *Menu) {
C.gtk_menu_item_set_submenu(
(*C.GtkMenuItem)((item.impl).(*linuxMenuItem).native),
(*C.GtkWidget)((menu.impl).(*linuxMenu).native))
}
func menuGetRadioGroup(item *linuxMenuItem) *GSList {
return (*GSList)(C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(item.native)))
}
//export handleClick
func handleClick(idPtr unsafe.Pointer) {
id := (*C.GtkWidget)(idPtr)
item, ok := gtkSignalToMenuItem[id]
if !ok {
return
}
switch item.itemType {
case text, checkbox:
menuItemClicked <- item.id
case radio:
menuItem := (item.impl).(*linuxMenuItem)
if menuItem.isChecked() {
menuItemClicked <- item.id
}
}
}
func attachMenuHandler(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 = uint(handlerId)
}
// menuItem
func menuItemChecked(widget pointer) bool {
if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(widget)) == C.int(1) {
return true
}
return false
}
func menuItemNew(label string) pointer {
cLabel := C.CString(label)
defer C.free(unsafe.Pointer(cLabel))
return pointer(C.gtk_menu_item_new_with_label(cLabel))
}
func menuCheckItemNew(label string) pointer {
cLabel := C.CString(label)
defer C.free(unsafe.Pointer(cLabel))
return pointer(C.gtk_check_menu_item_new_with_label(cLabel))
}
func menuItemSetChecked(widget pointer, checked bool) {
value := C.int(0)
if checked {
value = C.int(1)
}
C.gtk_check_menu_item_set_active(
(*C.GtkCheckMenuItem)(widget),
value)
}
func menuItemSetDisabled(widget pointer, disabled bool) {
value := C.int(1)
if disabled {
value = C.int(0)
}
C.gtk_widget_set_sensitive(
(*C.GtkWidget)(widget),
value)
}
func menuItemSetLabel(widget pointer, label string) {
value := C.CString(label)
C.gtk_menu_item_set_label(
(*C.GtkMenuItem)(widget),
value)
C.free(unsafe.Pointer(value))
}
func menuItemSetToolTip(widget pointer, tooltip string) {
value := C.CString(tooltip)
C.gtk_widget_set_tooltip_text(
(*C.GtkWidget)(widget),
value)
C.free(unsafe.Pointer(value))
}
func menuItemSignalBlock(widget pointer, handlerId uint, block bool) {
if block {
C.g_signal_handler_block(C.gpointer(widget), C.ulong(handlerId))
} else {
C.g_signal_handler_unblock(C.gpointer(widget), C.ulong(handlerId))
}
}
func menuRadioItemNew(group *GSList, label string) pointer {
cLabel := C.CString(label)
defer C.free(unsafe.Pointer(cLabel))
return pointer(C.gtk_radio_menu_item_new_with_label((*C.GSList)(group), cLabel))
}
// screen related
func 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 getScreens(app pointer) ([]*Screen, error) {
var screens []*Screen
window := C.gtk_application_get_active_window((*C.GtkApplication)(app))
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, getScreenByIndex(display, i))
}
return screens, nil
}
// widgets
func widgetSetSensitive(widget pointer, enabled bool) {
value := C.int(0)
if enabled {
value = C.int(1)
}
C.gtk_widget_set_sensitive((*C.GtkWidget)(widget), value)
}
func widgetSetVisible(widget pointer, hidden bool) {
if hidden {
C.gtk_widget_hide((*C.GtkWidget)(widget))
} else {
C.gtk_widget_show((*C.GtkWidget)(widget))
}
}
// window related functions
func windowClose(window pointer) {
C.gtk_window_close((*C.GtkWindow)(window))
}
func windowEnableDND(id uint, webview pointer) {
dnd := C.CString("text/uri-list")
defer C.free(unsafe.Pointer(dnd))
targetentry := C.gtk_target_entry_new(dnd, 0, C.guint(id))
defer C.gtk_target_entry_free(targetentry)
C.gtk_drag_dest_set((*C.GtkWidget)(webview), C.GTK_DEST_DEFAULT_DROP, targetentry, 1, C.GDK_ACTION_COPY)
event := C.CString("drag-data-received")
defer C.free(unsafe.Pointer(event))
windowId := C.uint(id)
C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onDragNDrop, unsafe.Pointer(C.gpointer(&windowId)))
}
func windowExecJS(webview pointer, js string) {
value := C.CString(js)
C.webkit_web_view_evaluate_javascript((*C.WebKitWebView)(webview),
value,
C.long(len(js)),
nil,
C.CString(""),
nil,
nil,
nil)
C.free(unsafe.Pointer(value))
}
func windowDestroy(window pointer) {
// Should this truly 'destroy' ?
C.gtk_window_close((*C.GtkWindow)(window))
//C.gtk_widget_destroy((*C.GtkWidget)(window))
}
func windowFullscreen(window pointer) {
C.gtk_window_fullscreen((*C.GtkWindow)(window))
}
func windowGetCurrentMonitor(window pointer) *C.GdkMonitor {
// Get the monitor that the window is currently on
display := C.gtk_widget_get_display((*C.GtkWidget)(window))
gdk_window := C.gtk_widget_get_window((*C.GtkWidget)(window))
if gdk_window == nil {
return nil
}
return C.gdk_display_get_monitor_at_window(display, gdk_window)
}
func windowGetCurrentMonitorGeometry(window pointer) (x int, y int, width int, height int, scale int) {
monitor := windowGetCurrentMonitor(window)
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 windowGetAbsolutePosition(window pointer) (int, int) {
var x C.int
var y C.int
C.gtk_window_get_position((*C.GtkWindow)(window), &x, &y)
return int(x), int(y)
}
func windowGetSize(window pointer) (int, int) {
var windowWidth C.int
var windowHeight C.int
C.gtk_window_get_size((*C.GtkWindow)(window), &windowWidth, &windowHeight)
return int(windowWidth), int(windowHeight)
}
func windowGetRelativePosition(window pointer) (int, int) {
x, y := windowGetAbsolutePosition(window)
// The position must be relative to the screen it is on
// We need to get the screen it is on
monitor := windowGetCurrentMonitor(window)
geometry := C.GdkRectangle{}
C.gdk_monitor_get_geometry(monitor, &geometry)
x = x - int(geometry.x)
y = y - int(geometry.y)
// TODO: Scale based on DPI
return x, y
}
func windowHide(window pointer) {
C.gtk_widget_hide((*C.GtkWidget)(window))
}
func windowIsFullscreen(window pointer) bool {
gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(window))
state := C.gdk_window_get_state(gdkwindow)
return state&C.GDK_WINDOW_STATE_FULLSCREEN > 0
}
func windowIsMaximized(window pointer) bool {
gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(window))
state := C.gdk_window_get_state(gdkwindow)
return state&C.GDK_WINDOW_STATE_MAXIMIZED > 0 && state&C.GDK_WINDOW_STATE_FULLSCREEN == 0
}
func windowIsMinimized(window pointer) bool {
gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(window))
state := C.gdk_window_get_state(gdkwindow)
return state&C.GDK_WINDOW_STATE_ICONIFIED > 0
}
func windowIsVisible(window pointer) bool {
if C.gtk_widget_is_visible((*C.GtkWidget)(window)) == 1 {
return true
}
return false
}
func windowMaximize(window pointer) {
C.gtk_window_maximize((*C.GtkWindow)(window))
}
func windowMinimize(window pointer) {
C.gtk_window_iconify((*C.GtkWindow)(window))
}
func windowNew(application pointer, menu pointer, windowId uint, gpuPolicy int) (window pointer, webview pointer) {
window = pointer(C.gtk_application_window_new((*C.GtkApplication)(application)))
C.g_object_ref_sink(C.gpointer(window))
webview = windowNewWebview(windowId, gpuPolicy)
vbox := pointer(C.gtk_box_new(C.GTK_ORIENTATION_VERTICAL, 0))
C.gtk_container_add((*C.GtkContainer)(window), (*C.GtkWidget)(vbox))
if menu != nil {
C.gtk_box_pack_start((*C.GtkBox)(vbox), (*C.GtkWidget)(menu), 0, 0, 0)
}
C.gtk_box_pack_start((*C.GtkBox)(unsafe.Pointer(vbox)), (*C.GtkWidget)(webview), 1, 1, 0)
return
}
func windowNewWebview(parentId uint, gpuPolicy int) 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(parentId)
if !registered {
wails := C.CString("wails")
C.webkit_web_context_register_uri_scheme(
C.webkit_web_context_get_default(),
wails,
C.WebKitURISchemeRequestCallback(C.onProcessRequest),
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 pointer(webview)
}
func windowPresent(window pointer) {
C.gtk_window_present((*C.GtkWindow)(window))
// gtk_window_unminimize ((*C.GtkWindow)(w.window)) /// gtk4
}
func windowReload(webview pointer, address string) {
uri := C.CString(address)
C.webkit_web_view_load_uri((*C.WebKitWebView)(webview), uri)
C.free(unsafe.Pointer(uri))
}
func windowResize(window pointer, width, height int) {
C.gtk_window_resize(
(*C.GtkWindow)(window),
C.gint(width),
C.gint(height))
}
func windowShow(window pointer) {
C.gtk_widget_show_all((*C.GtkWidget)(window))
}
func windowSetBackgroundColour(webview pointer, colour RGBA) {
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}
C.webkit_web_view_set_background_color((*C.WebKitWebView)(webview), &rgba)
}
func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHeight int) {
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)(window), nil, &size, C.GDK_HINT_MAX_SIZE|C.GDK_HINT_MIN_SIZE)
}
func windowSetFrameless(window pointer, frameless bool) {
C.gtk_window_set_decorated((*C.GtkWindow)(window), gtkBool(!frameless))
// TODO: Deal with transparency for the titlebar if possible when !frameless
// Perhaps we just make it undecorated and add a menu bar inside?
}
// TODO: confirm this is working properly
func windowSetHTML(webview pointer, 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)(webview),
cHTML,
uri,
empty)
}
func windowSetKeepAbove(window pointer, alwaysOnTop bool) {
C.gtk_window_set_keep_above((*C.GtkWindow)(window), gtkBool(alwaysOnTop))
}
func windowSetResizable(window pointer, resizable bool) {
C.gtk_window_set_resizable((*C.GtkWindow)(window), gtkBool(resizable))
}
func windowSetTitle(window pointer, title string) {
cTitle := C.CString(title)
C.gtk_window_set_title((*C.GtkWindow)(window), cTitle)
C.free(unsafe.Pointer(cTitle))
}
func windowSetTransparent(window pointer) {
screen := C.gtk_widget_get_screen((*C.GtkWidget)(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)(window), C.gboolean(1))
C.gtk_widget_set_visual((*C.GtkWidget)(window), visual)
}
}
func windowSetURL(webview pointer, uri string) {
target := C.CString(uri)
C.webkit_web_view_load_uri((*C.WebKitWebView)(webview), target)
C.free(unsafe.Pointer(target))
}
//export emit
func emit(we *C.WindowEvent) {
window := globalApplication.getWindowForID(uint(we.id))
if window != nil {
window.emit(events.WindowEventType(we.event))
}
}
func windowSetupSignalHandlers(windowId uint, window, webview pointer, emit func(e events.WindowEventType)) {
event := C.CString("delete-event")
defer C.free(unsafe.Pointer(event))
wEvent := C.WindowEvent{
id: C.uint(windowId),
event: C.uint(events.Common.WindowClosing),
}
C.signal_connect((*C.GtkWidget)(window), event, C.emit, unsafe.Pointer(&wEvent))
/*
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(windowId)
event = C.CString("button-press-event")
C.signal_connect((*C.GtkWidget)(unsafe.Pointer(webview)), event, C.onButtonEvent, 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(webview)), event, C.onButtonEvent, unsafe.Pointer(&id))
}
func windowToggleDevTools(webview pointer) {
settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(webview))
enabled := C.webkit_settings_get_enable_developer_extras(settings)
switch enabled {
case C.int(0):
enabled = C.int(1)
case C.int(1):
enabled = C.int(0)
}
C.webkit_settings_set_enable_developer_extras(settings, enabled)
}
func windowUnfullscreen(window pointer) {
C.gtk_window_unfullscreen((*C.GtkWindow)(window))
}
func windowUnmaximize(window pointer) {
C.gtk_window_unmaximize((*C.GtkWindow)(window))
}
func windowZoom(webview pointer) float64 {
return float64(C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(webview)))
}
// FIXME: ZoomIn/Out is assumed to be incorrect!
func windowZoomIn(webview pointer) {
ZoomInFactor := 1.10
windowZoomSet(webview, windowZoom(webview)*ZoomInFactor)
}
func windowZoomOut(webview pointer) {
ZoomOutFactor := -1.10
windowZoomSet(webview, windowZoom(webview)*ZoomOutFactor)
}
func windowZoomSet(webview pointer, zoom float64) {
C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(webview), C.double(zoom))
}
func windowMove(window pointer, x, y int) {
C.gtk_window_move((*C.GtkWindow)(window), C.int(x), C.int(y))
}
//export onButtonEvent
func onButtonEvent(_ *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)
}
//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) {
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 onProcessRequest
func onProcessRequest(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 gtkBool(input bool) C.gboolean {
if input {
return C.gboolean(1)
}
return C.gboolean(0)
}
// dialog related
func setWindowIcon(window pointer, 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))
}
//export messageDialogCB
func messageDialogCB(button C.int) {
fmt.Println("messageDialogCB", button)
}
func runOpenFileDialog(dialog *OpenFileDialog) ([]string, error) {
return []string{}, fmt.Errorf("not implemented")
}
func runQuestionDialog(parent pointer, options *MessageDialog) int {
cMsg := C.CString(options.Message)
cTitle := C.CString(options.Title)
defer C.free(unsafe.Pointer(cMsg))
defer C.free(unsafe.Pointer(cTitle))
hasButtons := false
if len(options.Buttons) > 0 {
hasButtons = true
}
dType, ok := map[DialogType]C.int{
InfoDialog: C.GTK_MESSAGE_INFO,
QuestionDialog: C.GTK_MESSAGE_QUESTION,
WarningDialog: C.GTK_MESSAGE_WARNING,
}[options.DialogType]
if !ok {
// FIXME: Add logging here!
dType = C.GTK_MESSAGE_INFO
}
dialog := C.new_message_dialog((*C.GtkWindow)(parent), cMsg, dType, C.bool(hasButtons))
if options.Title != "" {
C.gtk_window_set_title(
(*C.GtkWindow)(unsafe.Pointer(dialog)),
cTitle)
}
if img, err := pngToImage(options.Icon); err == nil {
gbytes := C.g_bytes_new_static(
C.gconstpointer(unsafe.Pointer(&img.Pix[0])),
C.ulong(len(img.Pix)))
defer C.g_bytes_unref(gbytes)
pixBuf := C.gdk_pixbuf_new_from_bytes(
gbytes,
C.GDK_COLORSPACE_RGB,
1, // has_alpha
8,
C.int(img.Bounds().Dx()),
C.int(img.Bounds().Dy()),
C.int(img.Stride),
)
image := C.gtk_image_new_from_pixbuf(pixBuf)
C.gtk_widget_set_visible((*C.GtkWidget)(image), C.gboolean(1))
contentArea := C.gtk_dialog_get_content_area((*C.GtkDialog)(dialog))
C.gtk_container_add(
(*C.GtkContainer)(unsafe.Pointer(contentArea)),
(*C.GtkWidget)(image))
}
for i, button := range options.Buttons {
cLabel := C.CString(button.Label)
defer C.free(unsafe.Pointer(cLabel))
index := C.int(i)
C.gtk_dialog_add_button(
(*C.GtkDialog)(dialog), cLabel, index)
if button.IsDefault {
C.gtk_dialog_set_default_response((*C.GtkDialog)(dialog), index)
}
}
defer C.gtk_widget_destroy((*C.GtkWidget)(dialog))
return int(C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog))))
}
func runSaveFileDialog(dialog *SaveFileDialog) (string, error) {
return "", fmt.Errorf("not implemented")
}
//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")
}
}
//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 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")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,50 +2,17 @@
package application 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) { func (m *linuxApp) dispatchOnMainThread(id uint) {
C.dispatchOnMainThread(C.uint(id)) dispatchOnMainThread(id)
} }
//export dispatchOnMainThreadCallback func executeOnMainThread(callbackID uint) {
func dispatchOnMainThreadCallback(callbackID C.uint) {
mainThreadFunctionStoreLock.RLock() mainThreadFunctionStoreLock.RLock()
id := uint(callbackID) fn := mainThreadFunctionStore[callbackID]
fn := mainThreadFunctionStore[id]
if fn == nil { if fn == nil {
Fatal("dispatchCallback called with invalid id: %v", id) Fatal("dispatchCallback called with invalid id: %v", callbackID)
} }
delete(mainThreadFunctionStore, id) delete(mainThreadFunctionStore, callbackID)
mainThreadFunctionStoreLock.RUnlock() mainThreadFunctionStoreLock.RUnlock()
fn() 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

@ -2,70 +2,20 @@
package application 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 { type linuxMenu struct {
menu *Menu menu *Menu
native unsafe.Pointer native pointer
} }
func newMenuImpl(menu *Menu) *linuxMenu { func newMenuImpl(menu *Menu) *linuxMenu {
result := &linuxMenu{ result := &linuxMenu{
menu: menu, menu: menu,
native: unsafe.Pointer(C.gtk_menu_bar_new()), native: menuBarNew(),
} }
return result return result
} }
func (m *linuxMenu) update() { 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) m.processMenu(m.menu)
} }
@ -73,15 +23,15 @@ func (m *linuxMenu) processMenu(menu *Menu) {
if menu.impl == nil { if menu.impl == nil {
menu.impl = &linuxMenu{ menu.impl = &linuxMenu{
menu: menu, menu: menu,
native: unsafe.Pointer(C.gtk_menu_new()), native: menuNew(),
} }
} }
var currentRadioGroup *C.GSList var currentRadioGroup GSListPointer
for _, item := range menu.items { for _, item := range menu.items {
// drop the group if we have run out of radio items // drop the group if we have run out of radio items
if item.itemType != radio { if item.itemType != radio {
currentRadioGroup = nil currentRadioGroup = nilRadioGroup
} }
switch item.itemType { switch item.itemType {
@ -99,7 +49,7 @@ func (m *linuxMenu) processMenu(menu *Menu) {
menuItem := newRadioItemImpl(item, currentRadioGroup) menuItem := newRadioItemImpl(item, currentRadioGroup)
item.impl = menuItem item.impl = menuItem
m.addMenuItem(menu, item) m.addMenuItem(menu, item)
currentRadioGroup = C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(menuItem.native)) currentRadioGroup = menuGetRadioGroup(menuItem)
case separator: case separator:
m.addMenuSeparator(menu) m.addMenuSeparator(menu)
} }
@ -115,66 +65,30 @@ func (m *linuxMenu) processMenu(menu *Menu) {
} }
func (m *linuxMenu) attachHandler(item *MenuItem) { func (m *linuxMenu) attachHandler(item *MenuItem) {
signal := C.CString("activate") attachMenuHandler(item)
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) { func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) {
if menu.impl == nil { if menu.impl == nil {
menu.impl = &linuxMenu{ menu.impl = &linuxMenu{
menu: menu, menu: menu,
native: unsafe.Pointer(C.gtk_menu_new()), native: menuNew(),
} }
} }
menuSetSubmenu(item, menu)
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) { func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) {
// fmt.Println("addMenuIteam", fmt.Sprintf("%+v", parent), fmt.Sprintf("%+v", menu)) menuAppend(parent, 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) { func (m *linuxMenu) addMenuSeparator(menu *Menu) {
// fmt.Println("addMenuSeparator", fmt.Sprintf("%+v", menu)) menuAddSeparator(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) { func (m *linuxMenu) addServicesMenu(menu *Menu) {
fmt.Println("addServicesMenu - not implemented") // FIXME: Should this be required?
//C.addServicesMenu(unsafe.Pointer(menu.impl.(*linuxMenu).nsMenu))
} }
func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu { func (l *linuxMenu) createMenu(name string, items []*MenuItem) *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

@ -5,48 +5,31 @@ package application
import ( import (
"fmt" "fmt"
"runtime" "runtime"
"unsafe"
) )
/*
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdio.h>
#include "gtk/gtk.h"
*/
import "C"
type linuxMenuItem struct { type linuxMenuItem struct {
menuItem *MenuItem menuItem *MenuItem
native unsafe.Pointer native pointer
handlerId C.gulong handlerId uint
} }
func (l linuxMenuItem) setTooltip(tooltip string) { func (l linuxMenuItem) setTooltip(tooltip string) {
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
l.blockSignal() l.blockSignal()
defer l.unBlockSignal() defer l.unBlockSignal()
menuItemSetToolTip(l.native, tooltip)
value := C.CString(tooltip)
C.gtk_widget_set_tooltip_text(
(*C.GtkWidget)(l.native),
value)
C.free(unsafe.Pointer(value))
}) })
} }
func (l linuxMenuItem) blockSignal() { func (l linuxMenuItem) blockSignal() {
if l.handlerId != 0 { if l.handlerId != 0 {
C.g_signal_handler_block(C.gpointer(l.native), l.handlerId) menuItemSignalBlock(l.native, l.handlerId, true)
} }
} }
func (l linuxMenuItem) unBlockSignal() { func (l linuxMenuItem) unBlockSignal() {
if l.handlerId != 0 { if l.handlerId != 0 {
C.g_signal_handler_unblock(C.gpointer(l.native), l.handlerId) menuItemSignalBlock(l.native, l.handlerId, false)
} }
} }
@ -54,35 +37,19 @@ func (l linuxMenuItem) setLabel(s string) {
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
l.blockSignal() l.blockSignal()
defer l.unBlockSignal() defer l.unBlockSignal()
value := C.CString(s) menuItemSetLabel(l.native, s)
C.gtk_menu_item_set_label(
(*C.GtkMenuItem)(l.native),
value)
C.free(unsafe.Pointer(value))
}) })
} }
func (l linuxMenuItem) isChecked() bool { func (l linuxMenuItem) isChecked() bool {
if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(l.native)) == C.int(1) { return menuItemChecked(l.native)
return true
}
return false
} }
func (l linuxMenuItem) setDisabled(disabled bool) { func (l linuxMenuItem) setDisabled(disabled bool) {
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
l.blockSignal() l.blockSignal()
defer l.unBlockSignal() defer l.unBlockSignal()
menuItemSetDisabled(l.native, disabled)
value := C.int(1)
if disabled {
value = C.int(0)
}
C.gtk_widget_set_sensitive(
(*C.GtkWidget)(l.native),
value)
}) })
} }
@ -90,15 +57,15 @@ func (l linuxMenuItem) setChecked(checked bool) {
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
l.blockSignal() l.blockSignal()
defer l.unBlockSignal() defer l.unBlockSignal()
menuItemSetChecked(l.native, checked)
})
}
value := C.int(0) func (l linuxMenuItem) setHidden(hidden bool) {
if checked { globalApplication.dispatchOnMainThread(func() {
value = C.int(1) l.blockSignal()
} defer l.unBlockSignal()
widgetSetVisible(l.native, hidden)
C.gtk_check_menu_item_set_active(
(*C.GtkCheckMenuItem)(l.native),
value)
}) })
} }
@ -120,41 +87,30 @@ func newMenuItemImpl(item *MenuItem) *linuxMenuItem {
result := &linuxMenuItem{ result := &linuxMenuItem{
menuItem: item, menuItem: item,
} }
cLabel := C.CString(item.label)
switch item.itemType { switch item.itemType {
case text: case text:
result.native = unsafe.Pointer(C.gtk_menu_item_new_with_label(cLabel)) result.native = menuItemNew(item.label)
case checkbox: case checkbox:
result.native = unsafe.Pointer(C.gtk_check_menu_item_new_with_label(cLabel)) result.native = menuCheckItemNew(item.label)
result.setChecked(item.checked) result.setChecked(item.checked)
if item.itemType == checkbox || item.itemType == radio {
// C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked))
}
if item.accelerator != nil { if item.accelerator != nil {
result.setAccelerator(item.accelerator) result.setAccelerator(item.accelerator)
} }
case radio:
panic("Shouldn't get here with a radio item")
case submenu: case submenu:
result.native = unsafe.Pointer(C.gtk_menu_item_new_with_label(cLabel)) result.native = menuItemNew(item.label)
default: default:
panic("WTF") panic(fmt.Sprintf("Unknown menu type: %v", item.itemType))
} }
result.setDisabled(result.menuItem.disabled) result.setDisabled(result.menuItem.disabled)
C.free(unsafe.Pointer(cLabel))
return result return result
} }
func newRadioItemImpl(item *MenuItem, group *C.GSList) *linuxMenuItem { func newRadioItemImpl(item *MenuItem, group GSListPointer) *linuxMenuItem {
cLabel := C.CString(item.label)
defer C.free(unsafe.Pointer(cLabel))
result := &linuxMenuItem{ result := &linuxMenuItem{
menuItem: item, menuItem: item,
native: unsafe.Pointer(C.gtk_radio_menu_item_new_with_label(group, cLabel)), native: menuRadioItemNew(group, item.label),
} }
result.setChecked(item.checked) result.setChecked(item.checked)
result.setDisabled(result.menuItem.disabled) result.setDisabled(result.menuItem.disabled)
@ -182,6 +138,7 @@ func newHideMenuItem() *MenuItem {
return newMenuItem("Hide " + globalApplication.options.Name). return newMenuItem("Hide " + globalApplication.options.Name).
SetAccelerator("CmdOrCtrl+h"). SetAccelerator("CmdOrCtrl+h").
OnClick(func(ctx *Context) { OnClick(func(ctx *Context) {
// C.hideApplication() // C.hideApplication()
}) })
} }

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

@ -2,91 +2,26 @@
package application 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 ( import (
"fmt" "fmt"
"sync" "sync"
"unsafe"
) )
func (m *linuxApp) getPrimaryScreen() (*Screen, error) { func (m *linuxApp) getPrimaryScreen() (*Screen, error) {
return nil, fmt.Errorf("not implemented") 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) { func (m *linuxApp) getScreens() ([]*Screen, error) {
var wg sync.WaitGroup var wg sync.WaitGroup
var screens []*Screen var screens []*Screen
var err error
wg.Add(1) wg.Add(1)
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
window := C.gtk_application_get_active_window((*C.GtkApplication)(m.application)) screens, err = getScreens(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.Done()
}) })
wg.Wait() wg.Wait()
return screens, nil return screens, err
} }
func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) { func getScreenForWindow(window *linuxWebviewWindow) (*Screen, error) {

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,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,60 +1,19 @@
//go:build linux && !purego //go:build linux
package application 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 ( import (
"fmt" "fmt"
"net/url" "net/url"
"strings"
"sync" "sync"
"unsafe" "unsafe"
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
"github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v3/pkg/events" "github.com/wailsapp/wails/v3/pkg/events"
) )
var showDevTools = func(window unsafe.Pointer) {} 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 { type dragInfo struct {
XRoot int XRoot int
YRoot int YRoot int
@ -64,14 +23,14 @@ type dragInfo struct {
type linuxWebviewWindow struct { type linuxWebviewWindow struct {
id uint id uint
application unsafe.Pointer application pointer
window unsafe.Pointer window pointer
webview unsafe.Pointer webview pointer
parent *WebviewWindow parent *WebviewWindow
menubar *C.GtkWidget menubar pointer
vbox *C.GtkWidget vbox pointer
menu *menu.Menu menu *menu.Menu
accels *C.GtkAccelGroup accels pointer
lastWidth int lastWidth int
lastHeight int lastHeight int
drag dragInfo drag dragInfo
@ -81,173 +40,30 @@ var (
registered bool = false // avoid 'already registered message' about 'wails://' registered bool = false // avoid 'already registered message' about 'wails://'
) )
//export buttonEvent func (w *linuxWebviewWindow) startDrag() error {
func buttonEvent(_ *C.GtkWidget, event *C.GdkEventButton, data unsafe.Pointer) C.gboolean { return nil
// 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) { func (w *linuxWebviewWindow) endDrag(button uint, x, y int) {
fmt.Println("endDrag", button, x, y) 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() { func (w *linuxWebviewWindow) enableDND() {
dnd := C.CString("text/uri-list") windowEnableDND(w.parent.id, w.webview)
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() { func (w *linuxWebviewWindow) connectSignals() {
event := C.CString("delete-event") cb := func(e events.WindowEventType) {
defer C.free(unsafe.Pointer(event)) w.parent.emit(e)
// 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)
} }
/* windowSetupSignalHandlers(w.parent.id, w.window, w.webview, cb)
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) { func (w *linuxWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) {
// Create the menu // Create the menu
thisMenu := newMenuImpl(menu) thisMenu := newMenuImpl(menu)
thisMenu.update() thisMenu.update()
fmt.Println("linux.openContextMenu()") fmt.Println("linux.openContextMenu() - not implemented")
/* void /* void
gtk_menu_popup_at_rect ( gtk_menu_popup_at_rect (
GtkMenu* menu, GtkMenu* menu,
@ -261,25 +77,19 @@ func (w *linuxWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData)
} }
func (w *linuxWebviewWindow) getZoom() float64 { func (w *linuxWebviewWindow) getZoom() float64 {
return float64(C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(w.webview))) return windowZoom(w.webview)
} }
func (w *linuxWebviewWindow) setZoom(zoom float64) { func (w *linuxWebviewWindow) setZoom(zoom float64) {
C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), C.double(zoom)) windowZoomSet(w.webview, zoom)
} }
func (w *linuxWebviewWindow) setFrameless(frameless bool) { func (w *linuxWebviewWindow) setFrameless(frameless bool) {
if frameless { windowSetFrameless(w.window, 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) { func (w *linuxWebviewWindow) getScreen() (*Screen, error) {
mx, my, width, height, scale := w.getCurrentMonitorGeometry() mx, my, width, height, scale := windowGetCurrentMonitorGeometry(w.window)
return &Screen{ return &Screen{
ID: fmt.Sprintf("%d", w.id), // A unique identifier for the display ID: fmt.Sprintf("%d", w.id), // A unique identifier for the display
Name: w.parent.Name(), // The name of the display Name: w.parent.Name(), // The name of the display
@ -294,14 +104,22 @@ func (w *linuxWebviewWindow) getScreen() (*Screen, error) {
}, nil }, nil
} }
func (w *linuxWebviewWindow) focus() {
globalApplication.dispatchOnMainThread(func() {
windowPresent(w.window)
})
}
func (w *linuxWebviewWindow) show() { func (w *linuxWebviewWindow) show() {
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
C.gtk_widget_show_all((*C.GtkWidget)(w.window)) windowShow(w.window)
}) })
} }
func (w *linuxWebviewWindow) hide() { func (w *linuxWebviewWindow) hide() {
C.gtk_widget_hide((*C.GtkWidget)(w.window)) globalApplication.dispatchOnMainThread(func() {
windowHide(w.window)
})
} }
func (w *linuxWebviewWindow) isNormal() bool { func (w *linuxWebviewWindow) isNormal() bool {
@ -309,10 +127,7 @@ func (w *linuxWebviewWindow) isNormal() bool {
} }
func (w *linuxWebviewWindow) isVisible() bool { func (w *linuxWebviewWindow) isVisible() bool {
if C.gtk_widget_is_visible((*C.GtkWidget)(w.window)) == 1 { return windowIsVisible(w.window)
return true
}
return false
} }
func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) { func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) {
@ -321,14 +136,14 @@ func (w *linuxWebviewWindow) setFullscreenButtonEnabled(enabled bool) {
} }
func (w *linuxWebviewWindow) disableSizeConstraints() { func (w *linuxWebviewWindow) disableSizeConstraints() {
x, y, width, height, scale := w.getCurrentMonitorGeometry() x, y, width, height, scale := windowGetCurrentMonitorGeometry(w.window)
w.setMinMaxSize(x, y, width*scale, height*scale) w.setMinMaxSize(x, y, width*scale, height*scale)
} }
func (w *linuxWebviewWindow) unfullscreen() { func (w *linuxWebviewWindow) unfullscreen() {
fmt.Println("unfullscreen") fmt.Println("unfullscreen")
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
C.gtk_window_unfullscreen((*C.GtkWindow)(w.window)) windowUnfullscreen(w.window)
w.unmaximise() w.unmaximise()
}) })
} }
@ -337,38 +152,31 @@ func (w *linuxWebviewWindow) fullscreen() {
w.maximise() w.maximise()
w.lastWidth, w.lastHeight = w.size() w.lastWidth, w.lastHeight = w.size()
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
x, y, width, height, scale := w.getCurrentMonitorGeometry() x, y, width, height, scale := windowGetCurrentMonitorGeometry(w.window)
if x == -1 && y == -1 && width == -1 && height == -1 { if x == -1 && y == -1 && width == -1 && height == -1 {
return return
} }
w.setMinMaxSize(0, 0, width*scale, height*scale) w.setMinMaxSize(0, 0, width*scale, height*scale)
w.setSize(width*scale, height*scale) w.setSize(width*scale, height*scale)
C.gtk_window_fullscreen((*C.GtkWindow)(w.window)) windowFullscreen(w.window)
w.setRelativePosition(0, 0) 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() { func (w *linuxWebviewWindow) unminimise() {
C.gtk_window_present((*C.GtkWindow)(w.window)) windowPresent(w.window)
// gtk_window_unminimize ((*C.GtkWindow)(w.window)) /// gtk4
} }
func (w *linuxWebviewWindow) unmaximise() { func (w *linuxWebviewWindow) unmaximise() {
C.gtk_window_unmaximize((*C.GtkWindow)(w.window)) windowUnmaximize(w.window)
} }
func (w *linuxWebviewWindow) maximise() { func (w *linuxWebviewWindow) maximise() {
C.gtk_window_maximize((*C.GtkWindow)(w.window)) windowMaximize(w.window)
} }
func (w *linuxWebviewWindow) minimise() { func (w *linuxWebviewWindow) minimise() {
C.gtk_window_iconify((*C.GtkWindow)(w.window)) windowMinimize(w.window)
} }
func (w *linuxWebviewWindow) on(eventID uint) { func (w *linuxWebviewWindow) on(eventID uint) {
@ -387,96 +195,58 @@ func (w *linuxWebviewWindow) windowZoom() {
} }
func (w *linuxWebviewWindow) close() { func (w *linuxWebviewWindow) close() {
C.gtk_window_close((*C.GtkWindow)(w.window)) windowClose(w.window)
if !w.parent.options.HideOnClose {
globalApplication.deleteWindowByID(w.parent.id)
}
} }
func (w *linuxWebviewWindow) zoomIn() { func (w *linuxWebviewWindow) zoomIn() {
lvl := C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(w.webview)) windowZoomIn(w.webview)
C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), lvl+0.5)
} }
func (w *linuxWebviewWindow) zoomOut() { func (w *linuxWebviewWindow) zoomOut() {
lvl := C.webkit_web_view_get_zoom_level((*C.WebKitWebView)(w.webview)) windowZoomOut(w.webview)
C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), lvl-0.5)
} }
func (w *linuxWebviewWindow) zoomReset() { func (w *linuxWebviewWindow) zoomReset() {
C.webkit_web_view_set_zoom_level((*C.WebKitWebView)(w.webview), 0.0) windowZoomSet(w.webview, 0.0)
} }
func (w *linuxWebviewWindow) reload() { func (w *linuxWebviewWindow) reload() {
// TODO: This should be a constant somewhere I feel windowReload(w.webview, "wails://")
uri := C.CString("wails://")
C.webkit_web_view_load_uri((*C.WebKitWebView)(w.window), uri)
C.free(unsafe.Pointer(uri))
} }
func (w *linuxWebviewWindow) forceReload() { func (w *linuxWebviewWindow) forceReload() {
w.reload() 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() { func (w *linuxWebviewWindow) center() {
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
x, y, width, height, _ := w.getCurrentMonitorGeometry() x, y, width, height, _ := windowGetCurrentMonitorGeometry(w.window)
if x == -1 && y == -1 && width == -1 && height == -1 { if x == -1 && y == -1 && width == -1 && height == -1 {
return return
} }
windowWidth, windowHeight := windowGetSize(w.window)
var windowWidth C.int newX := ((width - int(windowWidth)) / 2) + x
var windowHeight C.int newY := ((height - int(windowHeight)) / 2) + y
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 // Place the window at the center of the monitor
C.gtk_window_move((*C.GtkWindow)(w.window), newX, newY) windowMove(w.window, newX, newY)
}) })
} }
func (w *linuxWebviewWindow) isMinimised() bool { func (w *linuxWebviewWindow) isMinimised() bool {
gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) return windowIsMinimized(w.window)
state := C.gdk_window_get_state(gdkwindow)
return state&C.GDK_WINDOW_STATE_ICONIFIED > 0
} }
func (w *linuxWebviewWindow) isMaximised() bool { func (w *linuxWebviewWindow) isMaximised() bool {
return w.syncMainThreadReturningBool(func() bool { return w.syncMainThreadReturningBool(func() bool {
gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) return windowIsMaximized(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 { func (w *linuxWebviewWindow) isFullscreen() bool {
return w.syncMainThreadReturningBool(func() bool { return w.syncMainThreadReturningBool(func() bool {
gdkwindow := C.gtk_widget_get_window((*C.GtkWidget)(w.window)) return windowIsFullscreen(w.window)
state := C.gdk_window_get_state(gdkwindow)
return state&C.GDK_WINDOW_STATE_FULLSCREEN > 0
}) })
} }
@ -498,16 +268,7 @@ func (w *linuxWebviewWindow) restore() {
} }
func (w *linuxWebviewWindow) execJS(js string) { func (w *linuxWebviewWindow) execJS(js string) {
value := C.CString(js) windowExecJS(w.webview, 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) { func (w *linuxWebviewWindow) setURL(uri string) {
@ -520,13 +281,11 @@ func (w *linuxWebviewWindow) setURL(uri string) {
uri = url.String() uri = url.String()
} }
} }
target := C.CString(uri) windowSetURL(w.webview, uri)
C.webkit_web_view_load_uri((*C.WebKitWebView)(w.webview), target)
C.free(unsafe.Pointer(target))
} }
func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) { func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {
C.gtk_window_set_keep_above((*C.GtkWindow)(w.window), gtkBool(alwaysOnTop)) windowSetKeepAbove(w.window, alwaysOnTop)
} }
func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow { func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow {
@ -541,18 +300,15 @@ func newWindowImpl(parent *WebviewWindow) *linuxWebviewWindow {
func (w *linuxWebviewWindow) setTitle(title string) { func (w *linuxWebviewWindow) setTitle(title string) {
if !w.parent.options.Frameless { if !w.parent.options.Frameless {
cTitle := C.CString(title) windowSetTitle(w.window, title)
C.gtk_window_set_title((*C.GtkWindow)(w.window), cTitle)
C.free(unsafe.Pointer(cTitle))
} }
} }
func (w *linuxWebviewWindow) setSize(width, height int) { func (w *linuxWebviewWindow) setSize(width, height int) {
C.gtk_window_resize((*C.GtkWindow)(w.window), C.gint(width), C.gint(height)) windowResize(w.window, width, height)
} }
func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight int) { func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHeight int) {
fmt.Println("setMinMaxSize", minWidth, minHeight, maxWidth, maxHeight)
if minWidth == 0 { if minWidth == 0 {
minWidth = -1 minWidth = -1
} }
@ -565,13 +321,7 @@ func (w *linuxWebviewWindow) setMinMaxSize(minWidth, minHeight, maxWidth, maxHei
if maxHeight == 0 { if maxHeight == 0 {
maxHeight = -1 maxHeight = -1
} }
size := C.GdkGeometry{ windowSetGeometryHints(w.window, minWidth, minHeight, maxWidth, maxHeight)
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) { func (w *linuxWebviewWindow) setMinSize(width, height int) {
@ -583,40 +333,33 @@ func (w *linuxWebviewWindow) setMaxSize(width, height int) {
} }
func (w *linuxWebviewWindow) setResizable(resizable bool) { func (w *linuxWebviewWindow) setResizable(resizable bool) {
if resizable { windowSetResizable(w.window, 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() { func (w *linuxWebviewWindow) toggleDevTools() {
settings := C.webkit_web_view_get_settings((*C.WebKitWebView)(w.webview)) windowToggleDevTools(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) { func (w *linuxWebviewWindow) size() (int, int) {
var width, height C.int /* var width, height C.int
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
C.gtk_window_get_size((*C.GtkWindow)(w.window), &width, &height)
wg.Done() C.gtk_window_get_size((*C.GtkWindow)(w.window), &width, &height)
}) wg.Done()
wg.Wait() })
return int(width), int(height) wg.Wait()
return int(width), int(height)
*/
// Does this need to be guarded?
return windowGetSize(w.window)
} }
func (w *linuxWebviewWindow) setRelativePosition(x, y int) { func (w *linuxWebviewWindow) setRelativePosition(x, y int) {
mx, my, _, _, _ := w.getCurrentMonitorGeometry() mx, my, _, _, _ := windowGetCurrentMonitorGeometry(w.window)
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
C.gtk_window_move((*C.GtkWindow)(w.window), C.int(x+mx), C.int(y+my)) windowMove(w.window, x+mx, y+my)
}) })
} }
@ -630,16 +373,21 @@ func (w *linuxWebviewWindow) height() int {
return height return height
} }
func (w *linuxWebviewWindow) setAbsolutePosition(x int, y int) {
// Set the window's absolute position
windowMove(w.window, x, y)
}
func (w *linuxWebviewWindow) absolutePosition() (int, int) { func (w *linuxWebviewWindow) absolutePosition() (int, int) {
var x, y C.int var x, y int
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
C.gtk_window_get_position((*C.GtkWindow)(w.window), &x, &y) x, y = windowGetAbsolutePosition(w.window)
wg.Done() wg.Done()
}) })
wg.Wait() wg.Wait()
return int(x), int(y) return x, y
} }
func (w *linuxWebviewWindow) run() { func (w *linuxWebviewWindow) run() {
@ -651,21 +399,12 @@ func (w *linuxWebviewWindow) run() {
menu := app.applicationMenu menu := app.applicationMenu
globalApplication.dispatchOnMainThread(func() { globalApplication.dispatchOnMainThread(func() {
w.window = unsafe.Pointer(C.gtk_application_window_new((*C.GtkApplication)(w.application))) w.window, w.webview = windowNew(app.application, menu, w.parent.id, 1)
app.registerWindow((*C.GtkWindow)(w.window), w.parent.id) // record our mapping app.registerWindow(w.window, w.parent.id) // record our mapping
C.g_object_ref_sink(C.gpointer(w.window))
w.webview = w.newWebview(1)
w.connectSignals() w.connectSignals()
if w.parent.options.EnableDragAndDrop { if w.parent.options.EnableDragAndDrop {
w.enableDND() 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.setTitle(w.parent.options.Title)
w.setAlwaysOnTop(w.parent.options.AlwaysOnTop) w.setAlwaysOnTop(w.parent.options.AlwaysOnTop)
w.setResizable(!w.parent.options.DisableResize) w.setResizable(!w.parent.options.DisableResize)
@ -712,7 +451,6 @@ func (w *linuxWebviewWindow) run() {
} }
if w.parent.options.CSS != "" { 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) 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) w.execJS(js)
} }
}) })
@ -724,74 +462,59 @@ func (w *linuxWebviewWindow) run() {
if w.parent.options.X != 0 || w.parent.options.Y != 0 { if w.parent.options.X != 0 || w.parent.options.Y != 0 {
w.setRelativePosition(w.parent.options.X, w.parent.options.Y) w.setRelativePosition(w.parent.options.X, w.parent.options.Y)
} else { } else {
fmt.Println("attempting to set in the center") w.center() // needs to be queued until after GTK starts up!
w.center()
} }
} }
}) })
} }
func (w *linuxWebviewWindow) setTransparent() { func (w *linuxWebviewWindow) setTransparent() {
screen := C.gtk_widget_get_screen((*C.GtkWidget)(w.window)) windowSetTransparent(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) { func (w *linuxWebviewWindow) setBackgroundColour(colour RGBA) {
if colour.Alpha != 0 { if colour.Alpha != 0 {
w.setTransparent() 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} windowSetBackgroundColour(w.webview, colour)
fmt.Println(unsafe.Pointer(&rgba))
C.webkit_web_view_set_background_color((*C.WebKitWebView)(w.webview), &rgba)
} }
func (w *linuxWebviewWindow) relativePosition() (int, int) { func (w *linuxWebviewWindow) relativePosition() (int, int) {
var x, y C.int var x, y int
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
go globalApplication.dispatchOnMainThread(func() { go globalApplication.dispatchOnMainThread(func() {
C.gtk_window_get_position((*C.GtkWindow)(w.window), &x, &y) x, y = windowGetRelativePosition(w.window)
// 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.Done()
}) })
wg.Wait() wg.Wait()
return int(x), int(y) return x, y
} }
func (w *linuxWebviewWindow) destroy() { func (w *linuxWebviewWindow) destroy() {
C.gtk_window_close((*C.GtkWindow)(w.window)) windowDestroy(w.window)
}
func (w *linuxWebviewWindow) setEnabled(enabled bool) {
globalApplication.dispatchOnMainThread(func() {
widgetSetSensitive(w.window, enabled)
})
} }
func (w *linuxWebviewWindow) setHTML(html string) { func (w *linuxWebviewWindow) setHTML(html string) {
cHTML := C.CString(html) windowSetHTML(w.webview, html)
uri := C.CString("wails://") }
empty := C.CString("")
defer C.free(unsafe.Pointer(cHTML)) func (w *linuxWebviewWindow) startResize(border string) error {
defer C.free(unsafe.Pointer(uri)) // FIXME: what do we need to do here?
defer C.free(unsafe.Pointer(empty)) return nil
C.webkit_web_view_load_alternate_html(
(*C.WebKitWebView)(w.webview),
cHTML,
uri,
empty)
} }
func (w *linuxWebviewWindow) nativeWindowHandle() uintptr { func (w *linuxWebviewWindow) nativeWindowHandle() uintptr {
return uintptr(w.window) return uintptr(w.window)
} }
func (w *linuxWebviewWindow) print() error {
w.execJS("window.print();")
return nil
}

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
}