mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-17 01:19:29 +08:00
[linux/purego] initial port
This commit is contained in:
parent
769662d77a
commit
93a4b823c8
226
v3/pkg/application/application_linux_purego.go
Normal file
226
v3/pkg/application/application_linux_purego.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
//go:build linux && purego
|
||||||
|
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
fmt.Println("getCurrentWindowID")
|
||||||
|
var getCurrentWindow func(uintptr) uintptr
|
||||||
|
purego.RegisterLibFunc(&getCurrentWindow, gtk, "gtk_application_get_active_window")
|
||||||
|
// window := getCurrentWindow(m.application)
|
||||||
|
// if window != 0 {
|
||||||
|
// webview := (*WebviewWindow)(window)
|
||||||
|
// return webview.id
|
||||||
|
// }
|
||||||
|
return uint(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *linuxApp) setApplicationMenu(menu *Menu) {
|
||||||
|
if menu == nil {
|
||||||
|
// Create a default menu
|
||||||
|
menu = defaultApplicationMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
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!")
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
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 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),
|
||||||
|
}
|
||||||
|
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);*/
|
||||||
|
}
|
220
v3/pkg/application/dialogs_linux_purego.go
Normal file
220
v3/pkg/application/dialogs_linux_purego.go
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
//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")
|
||||||
|
}
|
||||||
|
}
|
30
v3/pkg/application/mainthread_linux_purego.go
Normal file
30
v3/pkg/application/mainthread_linux_purego.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//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()
|
||||||
|
}
|
168
v3/pkg/application/menu_linux_purego.go
Normal file
168
v3/pkg/application/menu_linux_purego.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
//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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
396
v3/pkg/application/menuitem_linux_purego.go
Normal file
396
v3/pkg/application/menuitem_linux_purego.go
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
//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:
|
||||||
|
fmt.Println("text", item.label)
|
||||||
|
result.native = newWithLabel(item.label)
|
||||||
|
|
||||||
|
case checkbox:
|
||||||
|
fmt.Println("cb", item.label)
|
||||||
|
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:
|
||||||
|
fmt.Println("submenu", item.label)
|
||||||
|
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.Minimize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newZoomMenuItem() *MenuItem {
|
||||||
|
return newMenuItem("Zoom").
|
||||||
|
OnClick(func(ctx *Context) {
|
||||||
|
currentWindow := globalApplication.CurrentWindow()
|
||||||
|
if currentWindow != nil {
|
||||||
|
currentWindow.Zoom()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
92
v3/pkg/application/screen_linux_purego.go
Normal file
92
v3/pkg/application/screen_linux_purego.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
//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()
|
||||||
|
}
|
725
v3/pkg/application/webview_window_linux_purego.go
Normal file
725
v3/pkg/application/webview_window_linux_purego.go
Normal file
@ -0,0 +1,725 @@
|
|||||||
|
//go:build linux && purego
|
||||||
|
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/ebitengine/purego"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
registered bool = false // avoid 'already registered message'
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkwindow.h#L121
|
||||||
|
GDK_HINT_MIN_SIZE = 1 << 1
|
||||||
|
GDK_HINT_MAX_SIZE = 1 << 2
|
||||||
|
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkevents.h#L512
|
||||||
|
GDK_WINDOW_STATE_ICONIFIED = 1 << 1
|
||||||
|
GDK_WINDOW_STATE_MAXIMIZED = 1 << 2
|
||||||
|
GDK_WINDOW_STATE_FULLSCREEN = 1 << 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type GdkGeometry struct {
|
||||||
|
minWidth int32
|
||||||
|
minHeight int32
|
||||||
|
maxWidth int32
|
||||||
|
maxHeight int32
|
||||||
|
baseWidth int32
|
||||||
|
baseHeight int32
|
||||||
|
widthInc int32
|
||||||
|
heightInc int32
|
||||||
|
padding int32
|
||||||
|
minAspect float64
|
||||||
|
maxAspect float64
|
||||||
|
GdkGravity int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type linuxWebviewWindow struct {
|
||||||
|
application uintptr
|
||||||
|
window uintptr
|
||||||
|
webview uintptr
|
||||||
|
parent *WebviewWindow
|
||||||
|
menubar uintptr
|
||||||
|
vbox uintptr
|
||||||
|
menu *menu.Menu
|
||||||
|
accels uintptr
|
||||||
|
minWidth, minHeight, maxWidth, maxHeight int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *linuxWebviewWindow) newWebview(gpuPolicy int) uintptr {
|
||||||
|
var newContentMgr func() uintptr
|
||||||
|
purego.RegisterLibFunc(
|
||||||
|
&newContentMgr,
|
||||||
|
webkit,
|
||||||
|
"webkit_user_content_manager_new")
|
||||||
|
var registerScriptMessageHandler func(uintptr, string)
|
||||||
|
purego.RegisterLibFunc(®isterScriptMessageHandler, webkit, "webkit_user_content_manager_register_script_message_handler")
|
||||||
|
var newWebview func(uintptr) uintptr
|
||||||
|
purego.RegisterLibFunc(&newWebview, webkit, "webkit_web_view_new_with_user_content_manager")
|
||||||
|
|
||||||
|
manager := newContentMgr()
|
||||||
|
registerScriptMessageHandler(manager, "external")
|
||||||
|
webview := newWebview(manager)
|
||||||
|
if !registered {
|
||||||
|
var registerUriScheme func(uintptr, string, uintptr, uintptr, uintptr)
|
||||||
|
purego.RegisterLibFunc(®isterUriScheme, webkit, "webkit_web_context_register_uri_scheme")
|
||||||
|
cb := purego.NewCallback(func(request uintptr) {
|
||||||
|
processURLRequest(w.parent.id, request)
|
||||||
|
})
|
||||||
|
var defaultContext func() uintptr
|
||||||
|
purego.RegisterLibFunc(&defaultContext, webkit, "webkit_web_context_get_default")
|
||||||
|
registerUriScheme(defaultContext(), "wails", cb, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var g_signal_connect func(uintptr, string, uintptr, uintptr, bool, int) int
|
||||||
|
purego.RegisterLibFunc(&g_signal_connect, gtk, "g_signal_connect_data")
|
||||||
|
|
||||||
|
loadChanged := purego.NewCallback(func(window uintptr) {
|
||||||
|
fmt.Println("loadChanged", window)
|
||||||
|
})
|
||||||
|
g_signal_connect(webview, "load-changed", loadChanged, 0, false, 0)
|
||||||
|
|
||||||
|
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) 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.setPosition(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() {
|
||||||
|
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.setPosition(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: (globalApplication.impl).(*linuxApp).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) setPosition(x, y int) {
|
||||||
|
var windowMove func(uintptr, int, int)
|
||||||
|
purego.RegisterLibFunc(&windowMove, gtk, "gtk_window_move")
|
||||||
|
mx, my, _, _, _ := w.getCurrentMonitorGeometry()
|
||||||
|
fmt.Println("setPosition", 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 := (globalApplication.impl).(*linuxApp)
|
||||||
|
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()
|
||||||
|
|
||||||
|
}
|
||||||
|
w.center()
|
||||||
|
|
||||||
|
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.setPosition(w.parent.options.X, w.parent.options.Y)
|
||||||
|
} else {
|
||||||
|
fmt.Println("attempting to set in the center")
|
||||||
|
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 == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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) position() (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)
|
||||||
|
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) {
|
||||||
|
fmt.Println("setHTML")
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user