[linux/purego] initial port

This commit is contained in:
Travis McLane
2023-05-10 09:10:03 -05:00
parent 769662d77a
commit 93a4b823c8
7 changed files with 1857 additions and 0 deletions
@@ -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(&gtkNew, 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
View 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")
}
}
@@ -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
View 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
View 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
View 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()
}
File diff suppressed because it is too large Load Diff