mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 20:21:01 +08:00

* Tidy up runtime JS * Initial implementation of runtime over http * Update runtime deps. Fix test task. * Support Clipboard. Message Processor refactor. * Add `Window.Screen()` Clipboard `GetText` -> `Text` * Support most dialogs Better JS->Go object mapping Implement Go->JS callback mechanism Rename `window.runtime` -> `window.wails` to better reflect the Go API * Support SaveFile dialog * Remove go.work * Tidy up * Event->CustomEvent to prevent potential clash with native JS Event object Support Eventing * Support application calls * Support logging * Support named windows Remove debug info * Update v3 changes
621 lines
15 KiB
Go
621 lines
15 KiB
Go
package application
|
|
|
|
/*
|
|
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
|
|
#cgo LDFLAGS: -framework Cocoa -framework WebKit
|
|
|
|
#include "Cocoa/Cocoa.h"
|
|
#include "menuitem.h"
|
|
#include "application.h"
|
|
|
|
#define unicode(input) [NSString stringWithFormat:@"%C", input]
|
|
|
|
// Create menu item
|
|
void* newMenuItem(unsigned int menuItemID, char *label, bool disabled, char* tooltip) {
|
|
MenuItem *menuItem = [MenuItem new];
|
|
|
|
// Label
|
|
menuItem.title = [NSString stringWithUTF8String:label];
|
|
|
|
if( disabled ) {
|
|
[menuItem setTarget:nil];
|
|
} else {
|
|
[menuItem setTarget:menuItem];
|
|
}
|
|
menuItem.menuItemID = menuItemID;
|
|
menuItem.action = @selector(handleClick);
|
|
menuItem.enabled = !disabled;
|
|
|
|
// Tooltip
|
|
if( tooltip != NULL ) {
|
|
menuItem.toolTip = [NSString stringWithUTF8String:tooltip];
|
|
free(tooltip);
|
|
}
|
|
|
|
// Set the tag
|
|
[menuItem setTag:menuItemID];
|
|
|
|
return (void*)menuItem;
|
|
}
|
|
|
|
// set menu item label
|
|
void setMenuItemLabel(void* nsMenuItem, char *label) {
|
|
MenuItem *menuItem = (MenuItem *)nsMenuItem;
|
|
menuItem.title = [NSString stringWithUTF8String:label];
|
|
}
|
|
|
|
// set menu item disabled
|
|
void setMenuItemDisabled(void* nsMenuItem, bool disabled) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
MenuItem *menuItem = (MenuItem *)nsMenuItem;
|
|
[menuItem setEnabled:!disabled];
|
|
// remove target
|
|
if( disabled ) {
|
|
[menuItem setTarget:nil];
|
|
} else {
|
|
[menuItem setTarget:menuItem];
|
|
}
|
|
});
|
|
}
|
|
|
|
// set menu item tooltip
|
|
void setMenuItemTooltip(void* nsMenuItem, char *tooltip) {
|
|
MenuItem *menuItem = (MenuItem *)nsMenuItem;
|
|
menuItem.toolTip = [NSString stringWithUTF8String:tooltip];
|
|
}
|
|
|
|
// Check menu item
|
|
void setMenuItemChecked(void* nsMenuItem, bool checked) {
|
|
MenuItem *menuItem = (MenuItem *)nsMenuItem;
|
|
menuItem.state = checked ? NSControlStateValueOn : NSControlStateValueOff;
|
|
}
|
|
|
|
NSString* translateKey(NSString* key) {
|
|
|
|
// Guard against no accelerator key
|
|
if( key == NULL ) {
|
|
return @"";
|
|
}
|
|
|
|
if( [key isEqualToString:@"backspace"] ) {
|
|
return unicode(0x0008);
|
|
}
|
|
if( [key isEqualToString:@"tab"] ) {
|
|
return unicode(0x0009);
|
|
}
|
|
if( [key isEqualToString:@"return"] ) {
|
|
return unicode(0x000d);
|
|
}
|
|
if( [key isEqualToString:@"enter"] ) {
|
|
return unicode(0x000d);
|
|
}
|
|
if( [key isEqualToString:@"escape"] ) {
|
|
return unicode(0x001b);
|
|
}
|
|
if( [key isEqualToString:@"left"] ) {
|
|
return unicode(0x001c);
|
|
}
|
|
if( [key isEqualToString:@"right"] ) {
|
|
return unicode(0x001d);
|
|
}
|
|
if( [key isEqualToString:@"up"] ) {
|
|
return unicode(0x001e);
|
|
}
|
|
if( [key isEqualToString:@"down"] ) {
|
|
return unicode(0x001f);
|
|
}
|
|
if( [key isEqualToString:@"space"] ) {
|
|
return unicode(0x0020);
|
|
}
|
|
if( [key isEqualToString:@"delete"] ) {
|
|
return unicode(0x007f);
|
|
}
|
|
if( [key isEqualToString:@"home"] ) {
|
|
return unicode(0x2196);
|
|
}
|
|
if( [key isEqualToString:@"end"] ) {
|
|
return unicode(0x2198);
|
|
}
|
|
if( [key isEqualToString:@"page up"] ) {
|
|
return unicode(0x21de);
|
|
}
|
|
if( [key isEqualToString:@"page down"] ) {
|
|
return unicode(0x21df);
|
|
}
|
|
if( [key isEqualToString:@"f1"] ) {
|
|
return unicode(0xf704);
|
|
}
|
|
if( [key isEqualToString:@"f2"] ) {
|
|
return unicode(0xf705);
|
|
}
|
|
if( [key isEqualToString:@"f3"] ) {
|
|
return unicode(0xf706);
|
|
}
|
|
if( [key isEqualToString:@"f4"] ) {
|
|
return unicode(0xf707);
|
|
}
|
|
if( [key isEqualToString:@"f5"] ) {
|
|
return unicode(0xf708);
|
|
}
|
|
if( [key isEqualToString:@"f6"] ) {
|
|
return unicode(0xf709);
|
|
}
|
|
if( [key isEqualToString:@"f7"] ) {
|
|
return unicode(0xf70a);
|
|
}
|
|
if( [key isEqualToString:@"f8"] ) {
|
|
return unicode(0xf70b);
|
|
}
|
|
if( [key isEqualToString:@"f9"] ) {
|
|
return unicode(0xf70c);
|
|
}
|
|
if( [key isEqualToString:@"f10"] ) {
|
|
return unicode(0xf70d);
|
|
}
|
|
if( [key isEqualToString:@"f11"] ) {
|
|
return unicode(0xf70e);
|
|
}
|
|
if( [key isEqualToString:@"f12"] ) {
|
|
return unicode(0xf70f);
|
|
}
|
|
if( [key isEqualToString:@"f13"] ) {
|
|
return unicode(0xf710);
|
|
}
|
|
if( [key isEqualToString:@"f14"] ) {
|
|
return unicode(0xf711);
|
|
}
|
|
if( [key isEqualToString:@"f15"] ) {
|
|
return unicode(0xf712);
|
|
}
|
|
if( [key isEqualToString:@"f16"] ) {
|
|
return unicode(0xf713);
|
|
}
|
|
if( [key isEqualToString:@"f17"] ) {
|
|
return unicode(0xf714);
|
|
}
|
|
if( [key isEqualToString:@"f18"] ) {
|
|
return unicode(0xf715);
|
|
}
|
|
if( [key isEqualToString:@"f19"] ) {
|
|
return unicode(0xf716);
|
|
}
|
|
if( [key isEqualToString:@"f20"] ) {
|
|
return unicode(0xf717);
|
|
}
|
|
if( [key isEqualToString:@"f21"] ) {
|
|
return unicode(0xf718);
|
|
}
|
|
if( [key isEqualToString:@"f22"] ) {
|
|
return unicode(0xf719);
|
|
}
|
|
if( [key isEqualToString:@"f23"] ) {
|
|
return unicode(0xf71a);
|
|
}
|
|
if( [key isEqualToString:@"f24"] ) {
|
|
return unicode(0xf71b);
|
|
}
|
|
if( [key isEqualToString:@"f25"] ) {
|
|
return unicode(0xf71c);
|
|
}
|
|
if( [key isEqualToString:@"f26"] ) {
|
|
return unicode(0xf71d);
|
|
}
|
|
if( [key isEqualToString:@"f27"] ) {
|
|
return unicode(0xf71e);
|
|
}
|
|
if( [key isEqualToString:@"f28"] ) {
|
|
return unicode(0xf71f);
|
|
}
|
|
if( [key isEqualToString:@"f29"] ) {
|
|
return unicode(0xf720);
|
|
}
|
|
if( [key isEqualToString:@"f30"] ) {
|
|
return unicode(0xf721);
|
|
}
|
|
if( [key isEqualToString:@"f31"] ) {
|
|
return unicode(0xf722);
|
|
}
|
|
if( [key isEqualToString:@"f32"] ) {
|
|
return unicode(0xf723);
|
|
}
|
|
if( [key isEqualToString:@"f33"] ) {
|
|
return unicode(0xf724);
|
|
}
|
|
if( [key isEqualToString:@"f34"] ) {
|
|
return unicode(0xf725);
|
|
}
|
|
if( [key isEqualToString:@"f35"] ) {
|
|
return unicode(0xf726);
|
|
}
|
|
if( [key isEqualToString:@"numLock"] ) {
|
|
return unicode(0xf739);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
// Set the menuitem key equivalent
|
|
void setMenuItemKeyEquivalent(void* nsMenuItem, char *key, int modifier) {
|
|
MenuItem *menuItem = (MenuItem *)nsMenuItem;
|
|
NSString *nskey = [NSString stringWithUTF8String:key];
|
|
menuItem.keyEquivalent = translateKey(nskey);
|
|
menuItem.keyEquivalentModifierMask = modifier;
|
|
free(key);
|
|
}
|
|
|
|
// Call the copy selector on the pasteboard
|
|
static void copyToPasteboard(char *text) {
|
|
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
|
[pasteboard clearContents];
|
|
[pasteboard setString:[NSString stringWithUTF8String:text] forType:NSPasteboardTypeString];
|
|
}
|
|
|
|
// Call the paste selector on the pasteboard
|
|
static char *pasteFromPasteboard(void) {
|
|
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
|
|
NSString *text = [pasteboard stringForType:NSPasteboardTypeString];
|
|
if( text == nil ) {
|
|
return NULL;
|
|
}
|
|
return strdup([text UTF8String]);
|
|
}
|
|
|
|
// Call paste selector to paste text
|
|
static void paste(void) {
|
|
[NSApp sendAction:@selector(paste:) to:nil from:nil];
|
|
}
|
|
|
|
// Call copy selector to copy text
|
|
static void copy(void) {
|
|
[NSApp sendAction:@selector(copy:) to:nil from:nil];
|
|
}
|
|
|
|
// Call cut selector to cut text
|
|
static void cut(void) {
|
|
[NSApp sendAction:@selector(cut:) to:nil from:nil];
|
|
}
|
|
|
|
// Call selectAll selector to select all text
|
|
static void selectAll(void) {
|
|
[NSApp sendAction:@selector(selectAll:) to:nil from:nil];
|
|
}
|
|
|
|
// Call delete selector to delete text
|
|
static void delete(void) {
|
|
[NSApp sendAction:@selector(delete:) to:nil from:nil];
|
|
}
|
|
|
|
// Call undo selector to undo text
|
|
static void undo(void) {
|
|
[NSApp sendAction:@selector(undo:) to:nil from:nil];
|
|
}
|
|
|
|
// Call redo selector to redo text
|
|
static void redo(void) {
|
|
[NSApp sendAction:@selector(redo:) to:nil from:nil];
|
|
}
|
|
|
|
// Call startSpeaking selector to start speaking text
|
|
static void startSpeaking(void) {
|
|
[NSApp sendAction:@selector(startSpeaking:) to:nil from:nil];
|
|
}
|
|
|
|
// Call stopSpeaking selector to stop speaking text
|
|
static void stopSpeaking(void) {
|
|
[NSApp sendAction:@selector(stopSpeaking:) to:nil from:nil];
|
|
}
|
|
|
|
static void pasteAndMatchStyle(void) {
|
|
[NSApp sendAction:@selector(pasteAndMatchStyle:) to:nil from:nil];
|
|
}
|
|
|
|
static void hideApplication(void) {
|
|
[[NSApplication sharedApplication] hide:nil];
|
|
}
|
|
|
|
// hideOthers hides all other applications
|
|
static void hideOthers(void) {
|
|
[[NSApplication sharedApplication] hideOtherApplications:nil];
|
|
}
|
|
|
|
// showAll shows all hidden applications
|
|
static void showAll(void) {
|
|
[[NSApplication sharedApplication] unhideAllApplications:nil];
|
|
}
|
|
|
|
*/
|
|
import "C"
|
|
import (
|
|
"runtime"
|
|
"unsafe"
|
|
)
|
|
|
|
type macosMenuItem struct {
|
|
menuItem *MenuItem
|
|
|
|
nsMenuItem unsafe.Pointer
|
|
}
|
|
|
|
func (m macosMenuItem) setTooltip(tooltip string) {
|
|
C.setMenuItemTooltip(m.nsMenuItem, C.CString(tooltip))
|
|
}
|
|
|
|
func (m macosMenuItem) setLabel(s string) {
|
|
C.setMenuItemLabel(m.nsMenuItem, C.CString(s))
|
|
}
|
|
|
|
func (m macosMenuItem) setDisabled(disabled bool) {
|
|
C.setMenuItemDisabled(m.nsMenuItem, C.bool(disabled))
|
|
}
|
|
|
|
func (m macosMenuItem) setChecked(checked bool) {
|
|
C.setMenuItemChecked(m.nsMenuItem, C.bool(checked))
|
|
}
|
|
|
|
func (m macosMenuItem) setAccelerator(accelerator *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) *macosMenuItem {
|
|
result := &macosMenuItem{
|
|
menuItem: item,
|
|
}
|
|
|
|
switch item.itemType {
|
|
case text, checkbox, submenu, radio:
|
|
result.nsMenuItem = unsafe.Pointer(C.newMenuItem(C.uint(item.id), C.CString(item.label), C.bool(item.disabled), C.CString(item.tooltip)))
|
|
if item.itemType == checkbox || item.itemType == radio {
|
|
C.setMenuItemChecked(result.nsMenuItem, C.bool(item.checked))
|
|
}
|
|
if item.accelerator != nil {
|
|
result.setAccelerator(item.accelerator)
|
|
}
|
|
default:
|
|
panic("WTF")
|
|
}
|
|
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()
|
|
}
|
|
})
|
|
}
|