mirror of
https://github.com/wavetermdev/wails.git
synced 2026-08-05 13:53:43 -07:00
Merge branch 'v3-alpha-linux' into v3-alpha
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
package webview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
@@ -76,7 +75,6 @@ func (r *request) Body() (io.ReadCloser, error) {
|
||||
}
|
||||
|
||||
func (r *request) Response() ResponseWriter {
|
||||
fmt.Println("r.Response()")
|
||||
if r.rw != nil {
|
||||
return r.rw
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ func (rw *responseWriter) WriteHeader(code int) {
|
||||
contentLength = pLen
|
||||
}
|
||||
}
|
||||
fmt.Println("content_length", contentLength)
|
||||
// We can't use os.Pipe here, because that returns files with a finalizer for closing the FD. But the control over the
|
||||
// read FD is given to the InputStream and will be closed there.
|
||||
// Furthermore we especially don't want to have the FD_CLOEXEC
|
||||
|
||||
+78
-54
@@ -14,21 +14,21 @@ Status of features in v3. Incomplete - please add as you see fit.
|
||||
|
||||
Application interface methods
|
||||
|
||||
| Method | Windows | Linux | Mac | Notes |
|
||||
|---------------------------------------------------------------|---------|-------|-----|------------------------------|
|
||||
| run() error | Y | Y | Y | |
|
||||
| destroy() | | Y | Y | |
|
||||
| setApplicationMenu(menu *Menu) | Y | | Y | |
|
||||
| name() string | | | Y | |
|
||||
| getCurrentWindowID() uint | Y | Y | Y | |
|
||||
| showAboutDialog(name string, description string, icon []byte) | | Y | Y | [linux] No icon possible yet |
|
||||
| setIcon(icon []byte) | - | | Y | |
|
||||
| on(id uint) | | | Y | |
|
||||
| dispatchOnMainThread(fn func()) | Y | Y | Y | |
|
||||
| hide() | Y | | Y | |
|
||||
| show() | Y | | Y | |
|
||||
| getPrimaryScreen() (*Screen, error) | | Y | Y | |
|
||||
| getScreens() ([]*Screen, error) | | Y | Y | |
|
||||
| Method | Windows | Linux | Mac | Notes |
|
||||
|---------------------------------------------------------------|---------|-------|-----|-------|
|
||||
| run() error | Y | Y | Y | |
|
||||
| destroy() | | Y | Y | |
|
||||
| setApplicationMenu(menu *Menu) | Y | Y | Y | |
|
||||
| name() string | | Y | Y | |
|
||||
| getCurrentWindowID() uint | Y | Y | Y | |
|
||||
| showAboutDialog(name string, description string, icon []byte) | | Y | Y | |
|
||||
| setIcon(icon []byte) | - | Y | Y | |
|
||||
| on(id uint) | | | Y | |
|
||||
| dispatchOnMainThread(fn func()) | Y | Y | Y | |
|
||||
| hide() | Y | Y | Y | |
|
||||
| show() | Y | Y | Y | |
|
||||
| getPrimaryScreen() (*Screen, error) | | Y | Y | |
|
||||
| getScreens() ([]*Screen, error) | | Y | Y | |
|
||||
|
||||
## Webview Window
|
||||
|
||||
@@ -90,7 +90,7 @@ Webview Window Interface Methods
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------|---------|-------|-----|-------|
|
||||
| Quit | Y | Y | Y | |
|
||||
| Quit | Y | Y | Y | |
|
||||
| Hide | Y | | Y | |
|
||||
| Show | Y | | Y | |
|
||||
|
||||
@@ -98,10 +98,10 @@ Webview Window Interface Methods
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|----------|---------|-------|-----|-------|
|
||||
| Info | Y | | Y | |
|
||||
| Warning | Y | | Y | |
|
||||
| Error | Y | | Y | |
|
||||
| Question | Y | | Y | |
|
||||
| Info | Y | Y | Y | |
|
||||
| Warning | Y | Y | Y | |
|
||||
| Error | Y | Y | Y | |
|
||||
| Question | Y | Y | Y | |
|
||||
| OpenFile | 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.
|
||||
An 'X' indicates that the option is not supported by the platform.
|
||||
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------------------------------|---------|-------|-----|---------------------------------------------------|
|
||||
| Name | | | | |
|
||||
| Title | Y | | | |
|
||||
| Width | Y | Y | | |
|
||||
| Height | Y | Y | | |
|
||||
| AlwaysOnTop | Y | Y | | |
|
||||
| URL | Y | | | |
|
||||
| DisableResize | Y | Y | | |
|
||||
| Frameless | Y | Y | | |
|
||||
| MinWidth | Y | Y | | |
|
||||
| MinHeight | Y | Y | | |
|
||||
| MaxWidth | Y | Y | | |
|
||||
| MaxHeight | Y | Y | | |
|
||||
| StartState | Y | | | |
|
||||
| Mac | - | - | | |
|
||||
| BackgroundType | | | | Acrylic seems to work but the others don't |
|
||||
| BackgroundColour | Y | Y | | |
|
||||
| HTML | Y | Y | | |
|
||||
| JS | Y | Y | | |
|
||||
| CSS | Y | Y | | |
|
||||
| X | Y | Y | | |
|
||||
| Y | Y | Y | | |
|
||||
| HideOnClose | Y | Y | | |
|
||||
| FullscreenButtonEnabled | | ? | | [linux] How is this different from DisableResize? |
|
||||
| Hidden | Y | | | |
|
||||
| EnableFraudulentWebsiteWarnings | | | | |
|
||||
| Zoom | | Y | | |
|
||||
| EnableDragAndDrop | Y | Y | | |
|
||||
| Windows | Y | - | - | |
|
||||
| Focused | Y | | | |
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------------------------------|---------|-------|-----|--------------------------------------------|
|
||||
| AlwaysOnTop | Y | | | |
|
||||
| BackgroundColour | Y | Y | | |
|
||||
| BackgroundType | | | | Acrylic seems to work but the others don't |
|
||||
| CSS | Y | Y | | |
|
||||
| DevToolsEnabled | Y | Y | Y | |
|
||||
| DisableResize | Y | Y | | |
|
||||
| EnableDragAndDrop | | Y | | |
|
||||
| EnableFraudulentWebsiteWarnings | | | | |
|
||||
| Focused | Y | Y | | |
|
||||
| Frameless | Y | Y | | |
|
||||
| FullscreenButtonEnabled | Y | | | |
|
||||
| Height | Y | Y | | |
|
||||
| Hidden | Y | Y | | |
|
||||
| HTML | Y | Y | | |
|
||||
| JS | Y | Y | | |
|
||||
| Mac | - | - | | |
|
||||
| MaxHeight | Y | Y | | |
|
||||
| MaxWidth | Y | Y | | |
|
||||
| MinHeight | Y | Y | | |
|
||||
| MinWidth | Y | Y | | |
|
||||
| Name | Y | Y | | |
|
||||
| OpenInspectorOnStartup | | | | |
|
||||
| StartState | Y | | | |
|
||||
| Title | Y | Y | | |
|
||||
| URL | Y | Y | | |
|
||||
| Width | Y | Y | | |
|
||||
| Windows | Y | - | - | |
|
||||
| X | Y | Y | | |
|
||||
| Y | Y | Y | | |
|
||||
| Zoom | | | | |
|
||||
| ZoomControlEnabled | | | | |
|
||||
|
||||
|
||||
### Log
|
||||
|
||||
@@ -220,7 +223,7 @@ To log or not to log? System logger vs custom logger.
|
||||
|
||||
| Event | Windows | Linux | Mac | Notes |
|
||||
|--------------------------|---------|-------|-----|-------|
|
||||
| Default Application Menu | Y | Y | Y | |
|
||||
| Default Application Menu | Y | Y | Y | |
|
||||
|
||||
## Tray Menus
|
||||
|
||||
@@ -300,6 +303,8 @@ Built-in plugin support:
|
||||
| Start at login | | | Y | |
|
||||
| Server | | | | |
|
||||
|
||||
TODO:
|
||||
|
||||
- Ensure each plugin has a JS wrapper that can be injected into the window.
|
||||
|
||||
## Packaging
|
||||
@@ -318,7 +323,8 @@ Built-in plugin support:
|
||||
| Feature | Windows | Linux | Mac | Notes |
|
||||
|---------|---------|-------|-----|-----------------------------------------------|
|
||||
| Resize | | | | |
|
||||
| Drag | | Y | | Linux - can always drag with `Alt`+left mouse |
|
||||
| Drag | | Y | | Linux - can always drag with `Meta`+left mouse |
|
||||
|
||||
|
||||
## Mac Specific
|
||||
|
||||
@@ -353,6 +359,24 @@ Built-in plugin support:
|
||||
|
||||
## 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
|
||||
|
||||
| Example | Windows | Linux | Mac |
|
||||
@@ -381,4 +405,4 @@ Built-in plugin support:
|
||||
|
||||
# Beta Release TODO
|
||||
|
||||
- [ ] Make better looking examples
|
||||
- [ ] Make better looking examples
|
||||
|
||||
@@ -6,6 +6,7 @@ require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
|
||||
|
||||
require (
|
||||
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/google/uuid v1.3.0 // 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/wailsapp/go-webview2 v1.0.2-0.20230604075323-d593c659ca7b // 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/net v0.7.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/v2 => ../../../v2
|
||||
|
||||
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01
|
||||
|
||||
@@ -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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
|
||||
@@ -3,8 +3,8 @@ module github.com/wailsapp/wails/v3
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/ebitengine/purego v0.3.2
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/ebitengine/purego v0.4.0-alpha.4
|
||||
github.com/go-ole/go-ole v1.2.6
|
||||
github.com/go-task/task/v3 v3.20.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
@@ -77,4 +77,4 @@ require (
|
||||
|
||||
replace github.com/wailsapp/wails/v2 => ../v2
|
||||
|
||||
replace github.com/ebitengine/purego v0.3.2 => github.com/TotallyGamerJet/purego v0.2.0-alpha.0.20230404174033-5655abccca7e
|
||||
replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01
|
||||
|
||||
@@ -11,8 +11,6 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX
|
||||
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
|
||||
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
|
||||
github.com/MarvinJWendt/testza v0.5.1 h1:a9Fqx6vQrHQ4CyiaLhktfTTelwGotmFWy8MNhyaohw8=
|
||||
github.com/TotallyGamerJet/purego v0.2.0-alpha.0.20230404174033-5655abccca7e h1:wQ7ot+e0mwJYkbomtIX9tU0dOV9lFTmwAgUGqvQTIUg=
|
||||
github.com/TotallyGamerJet/purego v0.2.0-alpha.0.20230404174033-5655abccca7e/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
@@ -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/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/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/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
|
||||
@@ -2,7 +2,6 @@ package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v3/internal/capabilities"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -10,6 +9,8 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/capabilities"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/icons"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -333,9 +334,7 @@ func (a *App) error(message string, args ...any) {
|
||||
func (a *App) NewWebviewWindowWithOptions(windowOptions WebviewWindowOptions) *WebviewWindow {
|
||||
newWindow := NewWindow(windowOptions)
|
||||
id := newWindow.id
|
||||
if a.windows == nil {
|
||||
a.windows = make(map[uint]*WebviewWindow)
|
||||
}
|
||||
|
||||
a.windowsLock.Lock()
|
||||
a.windows[id] = newWindow
|
||||
a.windowsLock.Unlock()
|
||||
@@ -416,7 +415,7 @@ func (a *App) Run() error {
|
||||
a.runLock.Unlock()
|
||||
|
||||
// set the application menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
|
||||
a.impl.setApplicationMenu(a.ApplicationMenu)
|
||||
}
|
||||
a.impl.setIcon(a.options.Icon)
|
||||
|
||||
@@ -1,153 +1,69 @@
|
||||
//go:build linux && !purego
|
||||
//go:build linux
|
||||
|
||||
package application
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <webkit2/webkit2.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct App {
|
||||
void *app;
|
||||
} App;
|
||||
|
||||
extern void processApplicationEvent(uint);
|
||||
|
||||
extern void activateLinux(gpointer data);
|
||||
|
||||
static void activate (GtkApplication* app, gpointer data) {
|
||||
// FIXME: should likely emit a WAILS specific code
|
||||
// events.Mac.EventApplicationDidFinishLaunching == 1032
|
||||
//processApplicationEvent(1032);
|
||||
|
||||
activateLinux(data);
|
||||
}
|
||||
|
||||
static GtkApplication* init(char* name) {
|
||||
return gtk_application_new(name, G_APPLICATION_DEFAULT_FLAGS);
|
||||
}
|
||||
|
||||
static int run(void *app, void *data) {
|
||||
g_signal_connect (app, "activate", G_CALLBACK (activate), data);
|
||||
g_application_hold(app); // allows it to run without a window
|
||||
int status = g_application_run (G_APPLICATION (app), 0, NULL);
|
||||
g_application_release(app);
|
||||
g_object_unref (app);
|
||||
return status;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// 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
|
||||
_ = os.Setenv("GDK_BACKEND", "x11")
|
||||
}
|
||||
|
||||
type linuxApp struct {
|
||||
application unsafe.Pointer
|
||||
applicationMenu unsafe.Pointer
|
||||
application pointer
|
||||
applicationMenu pointer
|
||||
parent *App
|
||||
|
||||
startupActions []func()
|
||||
|
||||
// Native -> uint
|
||||
windows map[*C.GtkWindow]uint
|
||||
windows map[windowPointer]uint
|
||||
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 {
|
||||
return globalApplication.impl.(*linuxApp)
|
||||
}
|
||||
|
||||
func (m *linuxApp) hide() {
|
||||
windows := C.gtk_application_get_windows((*C.GtkApplication)(m.application))
|
||||
for {
|
||||
fmt.Println("hiding", windows.data)
|
||||
C.gtk_widget_hide((*C.GtkWidget)(windows.data))
|
||||
windows = windows.next
|
||||
if windows == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
hideAllWindows(m.application)
|
||||
}
|
||||
|
||||
func (m *linuxApp) show() {
|
||||
windows := C.gtk_application_get_windows((*C.GtkApplication)(m.application))
|
||||
for {
|
||||
fmt.Println("hiding", windows.data)
|
||||
C.gtk_widget_show_all((*C.GtkWidget)(windows.data))
|
||||
windows = windows.next
|
||||
if windows == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
showAllWindows(m.application)
|
||||
}
|
||||
|
||||
func (m *linuxApp) on(eventID uint) {
|
||||
// TODO: What do we need to do here?
|
||||
log.Println("linuxApp.on()", eventID)
|
||||
// TODO: Setup signal handling as appropriate
|
||||
// Note: GTK signals seem to be strings!
|
||||
}
|
||||
|
||||
func (m *linuxApp) setIcon(icon []byte) {
|
||||
/* // FIXME: WIP
|
||||
loader := C.gdk_pixbuf_loader_new()
|
||||
|
||||
if loader == nil {
|
||||
return
|
||||
}
|
||||
|
||||
loaded := C.gdk_pixbuf_loader_write(loader, (*C.guchar)(&icon[0]), (C.gsize)(len(icon)), 0)
|
||||
|
||||
if loaded == C.bool(1) && C.gdk_pixbuf_loader_close(loader, 0) {
|
||||
pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader)
|
||||
if pixbuf != nil {
|
||||
ww := m.parent.CurrentWindow()
|
||||
window := ww.impl.window
|
||||
C.gtk_window_set_icon(window, pixbuf)
|
||||
}
|
||||
}
|
||||
|
||||
C.g_object_unref(loader)
|
||||
*/
|
||||
fmt.Println("linuxApp.setIcon", "not implemented")
|
||||
}
|
||||
|
||||
func (m *linuxApp) name() string {
|
||||
// appName := C.getAppName()
|
||||
// defer C.free(unsafe.Pointer(appName))
|
||||
// return C.GoString(appName)
|
||||
return ""
|
||||
return appName()
|
||||
}
|
||||
|
||||
func (m *linuxApp) getCurrentWindowID() uint {
|
||||
// TODO: Add extra metadata to window
|
||||
window := (*C.GtkWindow)(C.gtk_application_get_active_window((*C.GtkApplication)(m.application)))
|
||||
if window == nil {
|
||||
return uint(1)
|
||||
}
|
||||
m.windowsLock.Lock()
|
||||
defer m.windowsLock.Unlock()
|
||||
identifier, ok := m.windows[window]
|
||||
if ok {
|
||||
return identifier
|
||||
}
|
||||
return uint(1)
|
||||
return getCurrentWindowID(m.application, m.windows)
|
||||
}
|
||||
|
||||
func (m *linuxApp) setApplicationMenu(menu *Menu) {
|
||||
@@ -172,20 +88,22 @@ func (m *linuxApp) run() error {
|
||||
fmt.Println("events.Mac.ApplicationDidFinishLaunching received!")
|
||||
})
|
||||
|
||||
var app C.App
|
||||
app.app = unsafe.Pointer(m)
|
||||
C.run(m.application, m.application)
|
||||
return nil
|
||||
return appRun(m.application)
|
||||
}
|
||||
|
||||
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
|
||||
func (m *linuxApp) registerWindow(window *C.GtkWindow, id uint) {
|
||||
func (m *linuxApp) registerWindow(window pointer, id uint) {
|
||||
m.windowsLock.Lock()
|
||||
m.windows[window] = id
|
||||
m.windows[windowPointer(window)] = id
|
||||
m.windowsLock.Unlock()
|
||||
}
|
||||
|
||||
@@ -194,30 +112,15 @@ func newPlatformApp(parent *App) *linuxApp {
|
||||
if name == "" {
|
||||
name = "undefined"
|
||||
}
|
||||
nameC := C.CString(fmt.Sprintf("org.wails.%s", name))
|
||||
app := &linuxApp{
|
||||
parent: parent,
|
||||
application: unsafe.Pointer(C.init(nameC)),
|
||||
// name: fmt.Sprintf("org.wails.%s", name),
|
||||
windows: map[*C.GtkWindow]uint{},
|
||||
application: appNew(name),
|
||||
windows: map[windowPointer]uint{},
|
||||
}
|
||||
C.free(unsafe.Pointer(nameC))
|
||||
return app
|
||||
}
|
||||
|
||||
// executeStartupActions is called by `activateLinux` below to execute
|
||||
// code which needs to be run after the 'activate' signal is received
|
||||
func (m *linuxApp) executeStartupActions() {
|
||||
for _, fn := range m.startupActions {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
//export activateLinux
|
||||
func activateLinux(data unsafe.Pointer) {
|
||||
getNativeApplication().executeStartupActions()
|
||||
}
|
||||
|
||||
/*
|
||||
//export processApplicationEvent
|
||||
func processApplicationEvent(eventID C.uint) {
|
||||
// TODO: add translation to Wails events
|
||||
@@ -256,7 +159,7 @@ func processDragItems(windowID C.uint, arr **C.char, length C.int) {
|
||||
}
|
||||
|
||||
//export processMenuItemClick
|
||||
func processMenuItemClick(menuID C.uint) {
|
||||
func processMenuItemClick(menuID identifier) {
|
||||
menuItemClicked <- uint(menuID)
|
||||
}
|
||||
|
||||
@@ -266,3 +169,4 @@ func setIcon(icon []byte) {
|
||||
}
|
||||
//C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
//go:build linux && purego
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
"github.com/wailsapp/wails/v2/pkg/assetserver/webview"
|
||||
)
|
||||
|
||||
const (
|
||||
gtk3 = "libgtk-3.so"
|
||||
gtk4 = "libgtk-4.so"
|
||||
)
|
||||
|
||||
var (
|
||||
gtk uintptr
|
||||
version int
|
||||
webkit uintptr
|
||||
)
|
||||
|
||||
func init() {
|
||||
// needed for GTK4 to function
|
||||
_ = os.Setenv("GDK_BACKEND", "x11")
|
||||
var err error
|
||||
/*
|
||||
gtk, err = purego.Dlopen(gtk4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
if err == nil {
|
||||
version = 4
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Failed to open GTK4: Falling back to GTK3")
|
||||
*/
|
||||
gtk, err = purego.Dlopen(gtk3, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
version = 3
|
||||
|
||||
var webkit4 string = "libwebkit2gtk-4.1.so"
|
||||
webkit, err = purego.Dlopen(webkit4, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type linuxApp struct {
|
||||
appName string
|
||||
application uintptr
|
||||
applicationMenu uintptr
|
||||
parent *App
|
||||
|
||||
// Native -> uint
|
||||
windows map[uintptr]uint
|
||||
windowsLock sync.Mutex
|
||||
}
|
||||
|
||||
func getNativeApplication() *linuxApp {
|
||||
return globalApplication.impl.(*linuxApp)
|
||||
}
|
||||
|
||||
func (m *linuxApp) hide() {
|
||||
// C.hide()
|
||||
}
|
||||
|
||||
func (m *linuxApp) show() {
|
||||
// C.show()
|
||||
}
|
||||
|
||||
func (m *linuxApp) on(eventID uint) {
|
||||
log.Println("linuxApp.on()", eventID)
|
||||
|
||||
// TODO: Setup signal handling as appropriate
|
||||
// Note: GTK signals seem to be strings!
|
||||
}
|
||||
|
||||
func (m *linuxApp) setIcon(icon []byte) {
|
||||
// C.setApplicationIcon(unsafe.Pointer(&icon[0]), C.int(len(icon)))
|
||||
}
|
||||
|
||||
func (m *linuxApp) name() string {
|
||||
return m.appName
|
||||
}
|
||||
|
||||
func (m *linuxApp) getCurrentWindowID() uint {
|
||||
var getCurrentWindow func(uintptr) uintptr
|
||||
purego.RegisterLibFunc(&getCurrentWindow, gtk, "gtk_application_get_active_window")
|
||||
window := getCurrentWindow(m.application)
|
||||
if window == 0 {
|
||||
return 1
|
||||
}
|
||||
m.windowsLock.Lock()
|
||||
defer m.windowsLock.Unlock()
|
||||
if identifier, ok := m.windows[window]; ok {
|
||||
return identifier
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func (m *linuxApp) setApplicationMenu(menu *Menu) {
|
||||
if menu == nil {
|
||||
// Create a default menu
|
||||
menu = defaultApplicationMenu()
|
||||
}
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
menu.Update()
|
||||
m.applicationMenu = (menu.impl).(*linuxMenu).native
|
||||
})
|
||||
}
|
||||
|
||||
func (m *linuxApp) activate() {
|
||||
fmt.Println("linuxApp.activated!", m.application)
|
||||
var hold func(uintptr)
|
||||
purego.RegisterLibFunc(&hold, gtk, "g_application_hold")
|
||||
|
||||
hold(m.application)
|
||||
|
||||
// time.Sleep(50 * time.Millisecond)
|
||||
// m.parent.activate()
|
||||
}
|
||||
|
||||
func (m *linuxApp) run() error {
|
||||
// Add a hook to the ApplicationDidFinishLaunching event
|
||||
// FIXME: add Wails specific events - i.e. Shouldn't platform specific ones be translated to Wails events?
|
||||
/* m.parent.On(events.Mac.ApplicationDidFinishLaunching, func() {
|
||||
// Do we need to do anything now?
|
||||
fmt.Println("ApplicationDidFinishLaunching!")
|
||||
})
|
||||
*/
|
||||
m.parent.OnWindowCreation(func(window *WebviewWindow) {
|
||||
fmt.Println("OnWindowCreation: ", window)
|
||||
|
||||
})
|
||||
|
||||
var g_signal_connect func(uintptr, string, uintptr, uintptr, bool, int) int
|
||||
purego.RegisterLibFunc(&g_signal_connect, gtk, "g_signal_connect_data")
|
||||
g_signal_connect(m.application, "activate", purego.NewCallback(m.activate), m.application, false, 0)
|
||||
|
||||
var run func(uintptr, int, []string) int
|
||||
purego.RegisterLibFunc(&run, gtk, "g_application_run")
|
||||
|
||||
// FIXME: Convert status to 'error' if needed
|
||||
status := run(m.application, 0, []string{})
|
||||
fmt.Println("status", status)
|
||||
|
||||
var release func(uintptr)
|
||||
purego.RegisterLibFunc(&release, gtk, "g_application_release")
|
||||
release(m.application)
|
||||
|
||||
purego.RegisterLibFunc(&release, gtk, "g_object_unref")
|
||||
release(m.application)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *linuxApp) destroy() {
|
||||
var quit func(uintptr)
|
||||
purego.RegisterLibFunc(&quit, gtk, "g_application_quit")
|
||||
quit(m.application)
|
||||
}
|
||||
|
||||
func (m *linuxApp) registerWindow(address uintptr, window uint) {
|
||||
m.windowsLock.Lock()
|
||||
m.windows[address] = window
|
||||
m.windowsLock.Unlock()
|
||||
}
|
||||
|
||||
func newPlatformApp(parent *App) *linuxApp {
|
||||
name := strings.ToLower(parent.options.Name)
|
||||
if name == "" {
|
||||
name = "undefined"
|
||||
}
|
||||
identifier := fmt.Sprintf("org.wails.%s", strings.Replace(name, " ", "-", -1))
|
||||
|
||||
var gtkNew func(string, uint) uintptr
|
||||
purego.RegisterLibFunc(>kNew, gtk, "gtk_application_new")
|
||||
app := &linuxApp{
|
||||
appName: identifier,
|
||||
parent: parent,
|
||||
application: gtkNew(identifier, 0),
|
||||
windows: map[uintptr]uint{},
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
func processApplicationEvent(eventID uint) {
|
||||
// TODO: add translation to Wails events
|
||||
// currently reusing Mac specific values
|
||||
applicationEvents <- eventID
|
||||
}
|
||||
|
||||
func processWindowEvent(windowID uint, eventID uint) {
|
||||
windowEvents <- &WindowEvent{
|
||||
WindowID: windowID,
|
||||
EventID: eventID,
|
||||
}
|
||||
}
|
||||
|
||||
func processMessage(windowID uint, message string) {
|
||||
windowMessageBuffer <- &windowMessage{
|
||||
windowId: windowID,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func processURLRequest(windowID uint, wkUrlSchemeTask uintptr) {
|
||||
fmt.Println("processURLRequest", windowID, wkUrlSchemeTask)
|
||||
webviewRequests <- &webViewAssetRequest{
|
||||
Request: webview.NewRequest(wkUrlSchemeTask),
|
||||
windowId: windowID,
|
||||
windowName: globalApplication.getWindowForID(windowID).Name(),
|
||||
}
|
||||
}
|
||||
|
||||
func processDragItems(windowID uint, arr []string, length int) {
|
||||
windowDragAndDropBuffer <- &dragAndDropMessage{
|
||||
windowId: windowID,
|
||||
filenames: arr,
|
||||
}
|
||||
}
|
||||
|
||||
func processMenuItemClick(menuID uint) {
|
||||
menuItemClicked <- menuID
|
||||
}
|
||||
|
||||
func setIcon(icon []byte) {
|
||||
if icon == nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("setIcon")
|
||||
/*
|
||||
GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
|
||||
if (!loader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL))
|
||||
{
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
|
||||
if (pixbuf)
|
||||
{
|
||||
gtk_window_set_icon(window, pixbuf);
|
||||
}
|
||||
}
|
||||
g_object_unref(loader);*/
|
||||
}
|
||||
@@ -20,12 +20,12 @@ func (m linuxClipboard) setText(text string) bool {
|
||||
return bool(success)
|
||||
}
|
||||
|
||||
func (m linuxClipboard) text() string {
|
||||
func (m linuxClipboard) text() (string, bool) {
|
||||
clipboardLock.RLock()
|
||||
defer clipboardLock.RUnlock()
|
||||
// clipboardText := C.getClipboardText()
|
||||
// result := C.GoString(clipboardText)
|
||||
return ""
|
||||
return "", false
|
||||
}
|
||||
|
||||
func newClipboardImpl() *linuxClipboard {
|
||||
|
||||
@@ -1,154 +1,40 @@
|
||||
//go:build linux
|
||||
|
||||
package application
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static GtkWidget* new_about_dialog(GtkWindow *parent, const gchar *msg) {
|
||||
// gtk_message_dialog_new is variadic! Can't call from cgo
|
||||
GtkWidget *dialog;
|
||||
dialog = gtk_message_dialog_new(
|
||||
parent,
|
||||
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
GTK_MESSAGE_INFO,
|
||||
GTK_BUTTONS_CLOSE,
|
||||
msg);
|
||||
|
||||
g_signal_connect_swapped (dialog,
|
||||
"response",
|
||||
G_CALLBACK (gtk_widget_destroy),
|
||||
dialog);
|
||||
return dialog;
|
||||
};
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const AlertStyleWarning = C.int(0)
|
||||
const AlertStyleInformational = C.int(1)
|
||||
const AlertStyleCritical = C.int(2)
|
||||
|
||||
var alertTypeMap = map[DialogType]C.int{
|
||||
WarningDialog: AlertStyleWarning,
|
||||
InfoDialog: AlertStyleInformational,
|
||||
ErrorDialog: AlertStyleCritical,
|
||||
QuestionDialog: AlertStyleInformational,
|
||||
}
|
||||
|
||||
func setWindowIcon(window *C.GtkWindow, icon []byte) {
|
||||
fmt.Println("setWindowIcon", len(icon))
|
||||
loader := C.gdk_pixbuf_loader_new()
|
||||
if loader == nil {
|
||||
return
|
||||
}
|
||||
written := C.gdk_pixbuf_loader_write(
|
||||
loader,
|
||||
(*C.uchar)(&icon[0]),
|
||||
C.ulong(len(icon)),
|
||||
nil)
|
||||
if written == 0 {
|
||||
fmt.Println("failed to write icon")
|
||||
return
|
||||
}
|
||||
C.gdk_pixbuf_loader_close(loader, nil)
|
||||
pixbuf := C.gdk_pixbuf_loader_get_pixbuf(loader)
|
||||
if pixbuf != nil {
|
||||
fmt.Println("gtk_window_set_icon", window)
|
||||
C.gtk_window_set_icon((*C.GtkWindow)(window), pixbuf)
|
||||
}
|
||||
C.g_object_unref(C.gpointer(loader))
|
||||
}
|
||||
|
||||
func (m *linuxApp) showAboutDialog(title string, message string, icon []byte) {
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
parent := C.gtk_application_get_active_window((*C.GtkApplication)(m.application))
|
||||
cMsg := C.CString(message)
|
||||
cTitle := C.CString(title)
|
||||
defer C.free(unsafe.Pointer(cMsg))
|
||||
defer C.free(unsafe.Pointer(cTitle))
|
||||
dialog := C.new_about_dialog(parent, cMsg)
|
||||
C.gtk_window_set_title(
|
||||
(*C.GtkWindow)(unsafe.Pointer(dialog)),
|
||||
cTitle)
|
||||
// setWindowIcon((*C.GtkWindow)(dialog), icon)
|
||||
C.gtk_dialog_run((*C.GtkDialog)(unsafe.Pointer(dialog)))
|
||||
|
||||
})
|
||||
window := globalApplication.getWindowForID(m.getCurrentWindowID())
|
||||
var parent pointer
|
||||
if window != nil {
|
||||
parent = window.impl.(*linuxWebviewWindow).window
|
||||
}
|
||||
about := newMessageDialog(InfoDialog)
|
||||
about.SetTitle(title).
|
||||
SetMessage(message).
|
||||
SetIcon(icon)
|
||||
runQuestionDialog(
|
||||
parent,
|
||||
about,
|
||||
)
|
||||
}
|
||||
|
||||
type linuxDialog struct {
|
||||
dialog *MessageDialog
|
||||
|
||||
//nsDialog unsafe.Pointer
|
||||
}
|
||||
|
||||
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
|
||||
if len(m.dialog.Buttons) > 4 {
|
||||
m.dialog.Buttons = m.dialog.Buttons[:4]
|
||||
response := runQuestionDialog(parent, m.dialog)
|
||||
if response >= 0 && response < len(m.dialog.Buttons) {
|
||||
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 {
|
||||
@@ -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) {
|
||||
openFileResponses[m.dialog.id] = make(chan string)
|
||||
// nsWindow := unsafe.Pointer(nil)
|
||||
if m.dialog.window != nil {
|
||||
// get NSWindow from window
|
||||
//nsWindow = m.dialog.window.impl.(*macosWebviewWindow).nsWindow
|
||||
}
|
||||
|
||||
// Massage filter patterns into macOS format
|
||||
// We iterate all filter patterns, tidy them up and then join them with a semicolon
|
||||
// This should produce a single string of extensions like "png;jpg;gif"
|
||||
// var filterPatterns string
|
||||
// if len(m.dialog.filters) > 0 {
|
||||
// var allPatterns []string
|
||||
// for _, filter := range m.dialog.filters {
|
||||
// patternComponents := strings.Split(filter.Pattern, ";")
|
||||
// for i, component := range patternComponents {
|
||||
// filterPattern := strings.TrimSpace(component)
|
||||
// filterPattern = strings.TrimPrefix(filterPattern, "*.")
|
||||
// patternComponents[i] = filterPattern
|
||||
// }
|
||||
// allPatterns = append(allPatterns, strings.Join(patternComponents, ";"))
|
||||
// }
|
||||
// filterPatterns = strings.Join(allPatterns, ";")
|
||||
// }
|
||||
|
||||
// C.showOpenFileDialog(C.uint(m.dialog.id),
|
||||
// C.bool(m.dialog.canChooseFiles),
|
||||
// C.bool(m.dialog.canChooseDirectories),
|
||||
// C.bool(m.dialog.canCreateDirectories),
|
||||
// C.bool(m.dialog.showHiddenFiles),
|
||||
// C.bool(m.dialog.allowsMultipleSelection),
|
||||
// C.bool(m.dialog.resolvesAliases),
|
||||
// C.bool(m.dialog.hideExtension),
|
||||
// C.bool(m.dialog.treatsFilePackagesAsDirectories),
|
||||
// C.bool(m.dialog.allowsOtherFileTypes),
|
||||
// toCString(filterPatterns),
|
||||
// C.uint(len(filterPatterns)),
|
||||
// toCString(m.dialog.message),
|
||||
// toCString(m.dialog.directory),
|
||||
// toCString(m.dialog.buttonText),
|
||||
// nsWindow)
|
||||
var result []string
|
||||
for filename := range openFileResponses[m.dialog.id] {
|
||||
result = append(result, filename)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
//export openFileDialogCallback
|
||||
func openFileDialogCallback(cid C.uint, cpath *C.char) {
|
||||
path := C.GoString(cpath)
|
||||
id := uint(cid)
|
||||
channel, ok := openFileResponses[id]
|
||||
if ok {
|
||||
channel <- path
|
||||
} else {
|
||||
panic("No channel found for open file dialog")
|
||||
}
|
||||
}
|
||||
|
||||
//export openFileDialogCallbackEnd
|
||||
func openFileDialogCallbackEnd(cid C.uint) {
|
||||
id := uint(cid)
|
||||
channel, ok := openFileResponses[id]
|
||||
if ok {
|
||||
close(channel)
|
||||
delete(openFileResponses, id)
|
||||
freeDialogID(id)
|
||||
} else {
|
||||
panic("No channel found for open file dialog")
|
||||
}
|
||||
return runOpenFileDialog(m.dialog)
|
||||
}
|
||||
|
||||
type linuxSaveFileDialog struct {
|
||||
@@ -259,42 +68,5 @@ func newSaveFileDialogImpl(d *SaveFileDialog) *linuxSaveFileDialog {
|
||||
}
|
||||
|
||||
func (m *linuxSaveFileDialog) show() (string, error) {
|
||||
saveFileResponses[m.dialog.id] = make(chan string)
|
||||
// nsWindow := unsafe.Pointer(nil)
|
||||
if m.dialog.window != nil {
|
||||
// get NSWindow from window
|
||||
// nsWindow = m.dialog.window.impl.(*linuxWebviewWindow).nsWindow
|
||||
}
|
||||
|
||||
// C.showSaveFileDialog(C.uint(m.dialog.id),
|
||||
// C.bool(m.dialog.canCreateDirectories),
|
||||
// C.bool(m.dialog.showHiddenFiles),
|
||||
// C.bool(m.dialog.canSelectHiddenExtension),
|
||||
// C.bool(m.dialog.hideExtension),
|
||||
// C.bool(m.dialog.treatsFilePackagesAsDirectories),
|
||||
// C.bool(m.dialog.allowOtherFileTypes),
|
||||
// toCString(m.dialog.message),
|
||||
// toCString(m.dialog.directory),
|
||||
// toCString(m.dialog.buttonText),
|
||||
// toCString(m.dialog.filename),
|
||||
// nsWindow)
|
||||
return <-saveFileResponses[m.dialog.id], nil
|
||||
}
|
||||
|
||||
//export saveFileDialogCallback
|
||||
func saveFileDialogCallback(cid C.uint, cpath *C.char) {
|
||||
// Covert the path to a string
|
||||
path := C.GoString(cpath)
|
||||
id := uint(cid)
|
||||
// put response on channel
|
||||
channel, ok := saveFileResponses[id]
|
||||
if ok {
|
||||
channel <- path
|
||||
close(channel)
|
||||
delete(saveFileResponses, id)
|
||||
freeDialogID(id)
|
||||
|
||||
} else {
|
||||
panic("No channel found for save file dialog")
|
||||
}
|
||||
return runSaveFileDialog(m.dialog)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,50 +2,17 @@
|
||||
|
||||
package application
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0
|
||||
|
||||
#include <stdio.h>
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
typedef struct CallbackID
|
||||
{
|
||||
unsigned int value;
|
||||
} CallbackID;
|
||||
|
||||
extern void dispatchOnMainThreadCallback(unsigned int);
|
||||
|
||||
static gboolean dispatchCallback(gpointer data) {
|
||||
struct CallbackID *args = data;
|
||||
unsigned int cid = args->value;
|
||||
dispatchOnMainThreadCallback(cid);
|
||||
free(args);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
};
|
||||
|
||||
static void dispatchOnMainThread(unsigned int id) {
|
||||
CallbackID *args = malloc(sizeof(CallbackID));
|
||||
args->value = id;
|
||||
g_idle_add((GSourceFunc)dispatchCallback, (gpointer)args);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func (m *linuxApp) dispatchOnMainThread(id uint) {
|
||||
C.dispatchOnMainThread(C.uint(id))
|
||||
dispatchOnMainThread(id)
|
||||
}
|
||||
|
||||
//export dispatchOnMainThreadCallback
|
||||
func dispatchOnMainThreadCallback(callbackID C.uint) {
|
||||
func executeOnMainThread(callbackID uint) {
|
||||
mainThreadFunctionStoreLock.RLock()
|
||||
id := uint(callbackID)
|
||||
fn := mainThreadFunctionStore[id]
|
||||
fn := mainThreadFunctionStore[callbackID]
|
||||
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()
|
||||
fn()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -2,70 +2,20 @@
|
||||
|
||||
package application
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
void handleClick(void*);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
gtkSignalHandlers map[*C.GtkWidget]C.gulong
|
||||
gtkSignalToMenuItem map[*C.GtkWidget]*MenuItem
|
||||
)
|
||||
|
||||
func init() {
|
||||
gtkSignalHandlers = map[*C.GtkWidget]C.gulong{}
|
||||
gtkSignalToMenuItem = map[*C.GtkWidget]*MenuItem{}
|
||||
}
|
||||
|
||||
//export handleClick
|
||||
func handleClick(idPtr unsafe.Pointer) {
|
||||
id := (*C.GtkWidget)(idPtr)
|
||||
item, ok := gtkSignalToMenuItem[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
//impl := (item.impl).(*linuxMenuItem)
|
||||
|
||||
switch item.itemType {
|
||||
case text, checkbox:
|
||||
processMenuItemClick(C.uint(item.id))
|
||||
case radio:
|
||||
menuItem := (item.impl).(*linuxMenuItem)
|
||||
if menuItem.isChecked() {
|
||||
processMenuItemClick(C.uint(item.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type linuxMenu struct {
|
||||
menu *Menu
|
||||
native unsafe.Pointer
|
||||
native pointer
|
||||
}
|
||||
|
||||
func newMenuImpl(menu *Menu) *linuxMenu {
|
||||
result := &linuxMenu{
|
||||
menu: menu,
|
||||
native: unsafe.Pointer(C.gtk_menu_bar_new()),
|
||||
native: menuBarNew(),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *linuxMenu) update() {
|
||||
// fmt.Println("linuxMenu.update()")
|
||||
// if m.native != nil {
|
||||
// C.gtk_widget_destroy((*C.GtkWidget)(m.native))
|
||||
// m.native = unsafe.Pointer(C.gtk_menu_new())
|
||||
// }
|
||||
m.processMenu(m.menu)
|
||||
}
|
||||
|
||||
@@ -73,15 +23,15 @@ func (m *linuxMenu) processMenu(menu *Menu) {
|
||||
if menu.impl == nil {
|
||||
menu.impl = &linuxMenu{
|
||||
menu: menu,
|
||||
native: unsafe.Pointer(C.gtk_menu_new()),
|
||||
native: menuNew(),
|
||||
}
|
||||
}
|
||||
var currentRadioGroup *C.GSList
|
||||
var currentRadioGroup GSListPointer
|
||||
|
||||
for _, item := range menu.items {
|
||||
// drop the group if we have run out of radio items
|
||||
if item.itemType != radio {
|
||||
currentRadioGroup = nil
|
||||
currentRadioGroup = nilRadioGroup
|
||||
}
|
||||
|
||||
switch item.itemType {
|
||||
@@ -99,7 +49,7 @@ func (m *linuxMenu) processMenu(menu *Menu) {
|
||||
menuItem := newRadioItemImpl(item, currentRadioGroup)
|
||||
item.impl = menuItem
|
||||
m.addMenuItem(menu, item)
|
||||
currentRadioGroup = C.gtk_radio_menu_item_get_group((*C.GtkRadioMenuItem)(menuItem.native))
|
||||
currentRadioGroup = menuGetRadioGroup(menuItem)
|
||||
case separator:
|
||||
m.addMenuSeparator(menu)
|
||||
}
|
||||
@@ -115,66 +65,30 @@ func (m *linuxMenu) processMenu(menu *Menu) {
|
||||
}
|
||||
|
||||
func (m *linuxMenu) attachHandler(item *MenuItem) {
|
||||
signal := C.CString("activate")
|
||||
defer C.free(unsafe.Pointer(signal))
|
||||
|
||||
impl := (item.impl).(*linuxMenuItem)
|
||||
widget := impl.native
|
||||
flags := C.GConnectFlags(0)
|
||||
handlerId := C.g_signal_connect_object(
|
||||
C.gpointer(widget),
|
||||
signal,
|
||||
C.GCallback(C.handleClick),
|
||||
C.gpointer(widget),
|
||||
flags)
|
||||
|
||||
id := (*C.GtkWidget)(widget)
|
||||
gtkSignalToMenuItem[id] = item
|
||||
gtkSignalHandlers[id] = handlerId
|
||||
impl.handlerId = handlerId
|
||||
attachMenuHandler(item)
|
||||
}
|
||||
|
||||
func (m *linuxMenu) addSubMenuToItem(menu *Menu, item *MenuItem) {
|
||||
if menu.impl == nil {
|
||||
menu.impl = &linuxMenu{
|
||||
menu: menu,
|
||||
native: unsafe.Pointer(C.gtk_menu_new()),
|
||||
native: menuNew(),
|
||||
}
|
||||
}
|
||||
|
||||
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?
|
||||
}
|
||||
menuSetSubmenu(item, menu)
|
||||
}
|
||||
|
||||
func (m *linuxMenu) addMenuItem(parent *Menu, menu *MenuItem) {
|
||||
// fmt.Println("addMenuIteam", fmt.Sprintf("%+v", parent), fmt.Sprintf("%+v", menu))
|
||||
C.gtk_menu_shell_append(
|
||||
(*C.GtkMenuShell)((parent.impl).(*linuxMenu).native),
|
||||
(*C.GtkWidget)((menu.impl).(*linuxMenuItem).native),
|
||||
)
|
||||
/*
|
||||
C.gtk_menu_item_set_submenu(
|
||||
(*C.struct__GtkMenuItem)((menu.impl).(*linuxMenuItem).native),
|
||||
(*C.struct__GtkWidget)((parent.impl).(*linuxMenu).native),
|
||||
)
|
||||
*/
|
||||
menuAppend(parent, menu)
|
||||
}
|
||||
|
||||
func (m *linuxMenu) addMenuSeparator(menu *Menu) {
|
||||
// fmt.Println("addMenuSeparator", fmt.Sprintf("%+v", menu))
|
||||
sep := C.gtk_separator_menu_item_new()
|
||||
native := (menu.impl).(*linuxMenu).native
|
||||
C.gtk_menu_shell_append((*C.GtkMenuShell)(native), sep)
|
||||
menuAddSeparator(menu)
|
||||
|
||||
}
|
||||
|
||||
func (m *linuxMenu) addServicesMenu(menu *Menu) {
|
||||
fmt.Println("addServicesMenu - not implemented")
|
||||
//C.addServicesMenu(unsafe.Pointer(menu.impl.(*linuxMenu).nsMenu))
|
||||
// FIXME: Should this be required?
|
||||
}
|
||||
|
||||
func (l *linuxMenu) createMenu(name string, items []*MenuItem) *Menu {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -5,48 +5,31 @@ package application
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#include <stdio.h>
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
type linuxMenuItem struct {
|
||||
menuItem *MenuItem
|
||||
native unsafe.Pointer
|
||||
handlerId C.gulong
|
||||
native pointer
|
||||
handlerId uint
|
||||
}
|
||||
|
||||
func (l linuxMenuItem) setTooltip(tooltip string) {
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
l.blockSignal()
|
||||
defer l.unBlockSignal()
|
||||
|
||||
value := C.CString(tooltip)
|
||||
C.gtk_widget_set_tooltip_text(
|
||||
(*C.GtkWidget)(l.native),
|
||||
value)
|
||||
C.free(unsafe.Pointer(value))
|
||||
menuItemSetToolTip(l.native, tooltip)
|
||||
})
|
||||
}
|
||||
|
||||
func (l linuxMenuItem) blockSignal() {
|
||||
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() {
|
||||
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() {
|
||||
l.blockSignal()
|
||||
defer l.unBlockSignal()
|
||||
value := C.CString(s)
|
||||
C.gtk_menu_item_set_label(
|
||||
(*C.GtkMenuItem)(l.native),
|
||||
value)
|
||||
C.free(unsafe.Pointer(value))
|
||||
|
||||
menuItemSetLabel(l.native, s)
|
||||
})
|
||||
}
|
||||
|
||||
func (l linuxMenuItem) isChecked() bool {
|
||||
if C.gtk_check_menu_item_get_active((*C.GtkCheckMenuItem)(l.native)) == C.int(1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return menuItemChecked(l.native)
|
||||
}
|
||||
|
||||
func (l linuxMenuItem) setDisabled(disabled bool) {
|
||||
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
l.blockSignal()
|
||||
defer l.unBlockSignal()
|
||||
|
||||
value := C.int(1)
|
||||
if disabled {
|
||||
value = C.int(0)
|
||||
}
|
||||
C.gtk_widget_set_sensitive(
|
||||
(*C.GtkWidget)(l.native),
|
||||
value)
|
||||
menuItemSetDisabled(l.native, disabled)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,15 +57,15 @@ func (l linuxMenuItem) setChecked(checked bool) {
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
l.blockSignal()
|
||||
defer l.unBlockSignal()
|
||||
menuItemSetChecked(l.native, checked)
|
||||
})
|
||||
}
|
||||
|
||||
value := C.int(0)
|
||||
if checked {
|
||||
value = C.int(1)
|
||||
}
|
||||
|
||||
C.gtk_check_menu_item_set_active(
|
||||
(*C.GtkCheckMenuItem)(l.native),
|
||||
value)
|
||||
func (l linuxMenuItem) setHidden(hidden bool) {
|
||||
globalApplication.dispatchOnMainThread(func() {
|
||||
l.blockSignal()
|
||||
defer l.unBlockSignal()
|
||||
widgetSetVisible(l.native, hidden)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,41 +87,30 @@ func newMenuItemImpl(item *MenuItem) *linuxMenuItem {
|
||||
result := &linuxMenuItem{
|
||||
menuItem: item,
|
||||
}
|
||||
cLabel := C.CString(item.label)
|
||||
switch item.itemType {
|
||||
case text:
|
||||
result.native = unsafe.Pointer(C.gtk_menu_item_new_with_label(cLabel))
|
||||
result.native = menuItemNew(item.label)
|
||||
|
||||
case checkbox:
|
||||
result.native = unsafe.Pointer(C.gtk_check_menu_item_new_with_label(cLabel))
|
||||
result.native = menuCheckItemNew(item.label)
|
||||
result.setChecked(item.checked)
|
||||
if item.itemType == checkbox || item.itemType == radio {
|
||||
// C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked))
|
||||
}
|
||||
if item.accelerator != nil {
|
||||
result.setAccelerator(item.accelerator)
|
||||
}
|
||||
case radio:
|
||||
panic("Shouldn't get here with a radio item")
|
||||
|
||||
case submenu:
|
||||
result.native = unsafe.Pointer(C.gtk_menu_item_new_with_label(cLabel))
|
||||
result.native = menuItemNew(item.label)
|
||||
|
||||
default:
|
||||
panic("WTF")
|
||||
panic(fmt.Sprintf("Unknown menu type: %v", item.itemType))
|
||||
}
|
||||
result.setDisabled(result.menuItem.disabled)
|
||||
|
||||
C.free(unsafe.Pointer(cLabel))
|
||||
return result
|
||||
}
|
||||
|
||||
func newRadioItemImpl(item *MenuItem, group *C.GSList) *linuxMenuItem {
|
||||
cLabel := C.CString(item.label)
|
||||
defer C.free(unsafe.Pointer(cLabel))
|
||||
func newRadioItemImpl(item *MenuItem, group GSListPointer) *linuxMenuItem {
|
||||
result := &linuxMenuItem{
|
||||
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.setDisabled(result.menuItem.disabled)
|
||||
@@ -182,6 +138,7 @@ func newHideMenuItem() *MenuItem {
|
||||
return newMenuItem("Hide " + globalApplication.options.Name).
|
||||
SetAccelerator("CmdOrCtrl+h").
|
||||
OnClick(func(ctx *Context) {
|
||||
|
||||
// C.hideApplication()
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user