5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 23:40:43 +08:00
wails/v3/pkg/application/webview_window_darwin.go
Lea Anthony 1195464acb Improve window teardown on termination
This commit adds a robust teardown process for windows on application shutdown. It introduces a field to track the destruction state of each window and checks such before performing window operations. Also, it enhances the destroy functions within application for thorough clean up. Finally, redundant event handlers related to application termination were removed while fixing file generating challenge in go tasks.
2024-01-09 07:45:56 +11:00

1240 lines
35 KiB
Go

//go:build darwin
package application
/*
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework WebKit
#include "application_darwin.h"
#include "webview_window_darwin.h"
#include <stdlib.h>
#include "Cocoa/Cocoa.h"
#import <WebKit/WebKit.h>
#import <AppKit/AppKit.h>
#import "webview_window_darwin_drag.h"
struct WebviewPreferences {
bool *TabFocusesLinks;
bool *TextInteractionEnabled;
bool *FullscreenEnabled;
};
extern void registerListener(unsigned int event);
// Create a new Window
void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWarningEnabled, bool frameless, bool enableDragAndDrop, struct WebviewPreferences preferences) {
NSWindowStyleMask styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
if (frameless) {
styleMask = NSWindowStyleMaskBorderless | NSWindowStyleMaskResizable;
}
WebviewWindow* window = [[WebviewWindow alloc] initWithContentRect:NSMakeRect(0, 0, width-1, height-1)
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:NO];
// Create delegate
WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init];
[delegate autorelease];
// Set delegate
[window setDelegate:delegate];
delegate.windowId = id;
// Add NSView to window
NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)];
[view autorelease];
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
if( frameless ) {
[view setWantsLayer:YES];
view.layer.cornerRadius = 8.0;
}
[window setContentView:view];
// Embed wkwebview in window
NSRect frame = NSMakeRect(0, 0, width, height);
WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
[config autorelease];
// Set preferences
if (preferences.TabFocusesLinks != NULL) {
config.preferences.tabFocusesLinks = *preferences.TabFocusesLinks;
}
if (@available(macOS 11.3, *)) {
if (preferences.TextInteractionEnabled != NULL) {
config.preferences.textInteractionEnabled = *preferences.TextInteractionEnabled;
}
}
if (@available(macOS 12.3, *)) {
if (preferences.FullscreenEnabled != NULL) {
config.preferences.elementFullscreenEnabled = *preferences.FullscreenEnabled;
}
}
config.suppressesIncrementalRendering = true;
config.applicationNameForUserAgent = @"wails.io";
[config setURLSchemeHandler:delegate forURLScheme:@"wails"];
if (@available(macOS 10.15, *)) {
config.preferences.fraudulentWebsiteWarningEnabled = fraudulentWebsiteWarningEnabled;
}
// Setup user content controller
WKUserContentController* userContentController = [WKUserContentController new];
[userContentController autorelease];
[userContentController addScriptMessageHandler:delegate name:@"external"];
config.userContentController = userContentController;
WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
[webView autorelease];
[view addSubview:webView];
// support webview events
[webView setNavigationDelegate:delegate];
// Ensure webview resizes with the window
[webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
if( enableDragAndDrop ) {
WebviewDrag* dragView = [[WebviewDrag alloc] initWithFrame:NSMakeRect(0, 0, width-1, height-1)];
[dragView autorelease];
[view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[view addSubview:dragView];
dragView.windowId = id;
}
window.webView = webView;
return window;
}
void printWindowStyle(void *window) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
NSWindowStyleMask styleMask = [nsWindow styleMask];
// Get delegate
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate];
printf("Window %d style mask: ", windowDelegate.windowId);
if (styleMask & NSWindowStyleMaskTitled)
{
printf("NSWindowStyleMaskTitled ");
}
if (styleMask & NSWindowStyleMaskClosable)
{
printf("NSWindowStyleMaskClosable ");
}
if (styleMask & NSWindowStyleMaskMiniaturizable)
{
printf("NSWindowStyleMaskMiniaturizable ");
}
if (styleMask & NSWindowStyleMaskResizable)
{
printf("NSWindowStyleMaskResizable ");
}
if (styleMask & NSWindowStyleMaskFullSizeContentView)
{
printf("NSWindowStyleMaskFullSizeContentView ");
}
if (styleMask & NSWindowStyleMaskNonactivatingPanel)
{
printf("NSWindowStyleMaskNonactivatingPanel ");
}
if (styleMask & NSWindowStyleMaskFullScreen)
{
printf("NSWindowStyleMaskFullScreen ");
}
if (styleMask & NSWindowStyleMaskBorderless)
{
printf("MSWindowStyleMaskBorderless ");
}
printf("\n");
}
// setInvisibleTitleBarHeight sets the invisible title bar height
void setInvisibleTitleBarHeight(void* window, unsigned int height) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// Get delegate
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[nsWindow delegate];
// Set height
delegate.invisibleTitleBarHeight = height;
}
// Make NSWindow transparent
void windowSetTransparent(void* nsWindow) {
[(WebviewWindow*)nsWindow setOpaque:NO];
[(WebviewWindow*)nsWindow setBackgroundColor:[NSColor clearColor]];
}
void windowSetInvisibleTitleBar(void* nsWindow, unsigned int height) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate];
delegate.invisibleTitleBarHeight = height;
}
// Set the title of the NSWindow
void windowSetTitle(void* nsWindow, char* title) {
NSString* nsTitle = [NSString stringWithUTF8String:title];
[(WebviewWindow*)nsWindow setTitle:nsTitle];
free(title);
}
// Set the size of the NSWindow
void windowSetSize(void* nsWindow, int width, int height) {
// Set window size on main thread
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, width, height)].size;
[window setContentSize:contentSize];
[window setFrame:NSMakeRect(window.frame.origin.x, window.frame.origin.y, width, height) display:YES animate:YES];
}
// Set NSWindow always on top
void windowSetAlwaysOnTop(void* nsWindow, bool alwaysOnTop) {
// Set window always on top on main thread
[(WebviewWindow*)nsWindow setLevel:alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel];
}
// Load URL in NSWindow
void navigationLoadURL(void* nsWindow, char* url) {
// Load URL on main thread
NSURL* nsURL = [NSURL URLWithString:[NSString stringWithUTF8String:url]];
NSURLRequest* request = [NSURLRequest requestWithURL:nsURL];
WebviewWindow* window = (WebviewWindow*)nsWindow;
[window.webView loadRequest:request];
free(url);
}
// Set NSWindow resizable
void windowSetResizable(void* nsWindow, bool resizable) {
// Set window resizable on main thread
WebviewWindow* window = (WebviewWindow*)nsWindow;
if (resizable) {
NSWindowStyleMask styleMask = [window styleMask] | NSWindowStyleMaskResizable;
[window setStyleMask:styleMask];
} else {
NSWindowStyleMask styleMask = [window styleMask] & ~NSWindowStyleMaskResizable;
[window setStyleMask:styleMask];
}
}
// Set NSWindow min size
void windowSetMinSize(void* nsWindow, int width, int height) {
// Set window min size on main thread
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, width, height)].size;
[window setContentMinSize:contentSize];
NSSize size = { width, height };
[window setMinSize:size];
}
// Set NSWindow max size
void windowSetMaxSize(void* nsWindow, int width, int height) {
// Set window max size on main thread
NSSize size = { FLT_MAX, FLT_MAX };
size.width = width > 0 ? width : FLT_MAX;
size.height = height > 0 ? height : FLT_MAX;
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSSize contentSize = [window contentRectForFrameRect:NSMakeRect(0, 0, size.width, size.height)].size;
[window setContentMaxSize:contentSize];
[window setMaxSize:size];
}
// Enable NSWindow devtools
void windowEnableDevTools(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Enable devtools in webview
[window.webView.configuration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
}
// windowZoomReset
void windowZoomReset(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
[window.webView setMagnification:1.0];
}
// windowZoomSet
void windowZoomSet(void* nsWindow, double zoom) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Reset zoom
[window.webView setMagnification:zoom];
}
// windowZoomGet
float windowZoomGet(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Get zoom
return [window.webView magnification];
}
// windowZoomIn
void windowZoomIn(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Zoom in
[window.webView setMagnification:window.webView.magnification + 0.05];
}
// windowZoomOut
void windowZoomOut(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Zoom out
if( window.webView.magnification > 1.05 ) {
[window.webView setMagnification:window.webView.magnification - 0.05];
} else {
[window.webView setMagnification:1.0];
}
}
// set the window position relative to the screen
void windowSetRelativePosition(void* nsWindow, int x, int y) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSScreen* screen = [window screen];
if( screen == NULL ) {
screen = [NSScreen mainScreen];
}
NSRect windowFrame = [window frame];
NSRect screenFrame = [screen frame];
windowFrame.origin.x = screenFrame.origin.x + (float)x;
windowFrame.origin.y = (screenFrame.origin.y + screenFrame.size.height) - windowFrame.size.height - (float)y;
[window setFrame:windowFrame display:TRUE animate:FALSE];
}
// Execute JS in NSWindow
void windowExecJS(void* nsWindow, const char* js) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
[window.webView evaluateJavaScript:[NSString stringWithUTF8String:js] completionHandler:nil];
free((void*)js);
}
// Make NSWindow backdrop translucent
void windowSetTranslucent(void* nsWindow) {
// Get window
WebviewWindow* window = (WebviewWindow*)nsWindow;
id contentView = [window contentView];
NSVisualEffectView *effectView = [NSVisualEffectView alloc];
NSRect bounds = [contentView bounds];
[effectView initWithFrame:bounds];
[effectView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
[effectView setState:NSVisualEffectStateActive];
[contentView addSubview:effectView positioned:NSWindowBelow relativeTo:nil];
}
// Make webview background transparent
void webviewSetTransparent(void* nsWindow) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Set webview background transparent
[window.webView setValue:@NO forKey:@"drawsBackground"];
}
// Set webview background colour
void webviewSetBackgroundColour(void* nsWindow, int r, int g, int b, int alpha) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
// Set webview background color
[window.webView setValue:[NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:alpha/255.0] forKey:@"backgroundColor"];
}
// Set the window background colour
void windowSetBackgroundColour(void* nsWindow, int r, int g, int b, int alpha) {
[(WebviewWindow*)nsWindow setBackgroundColor:[NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:alpha/255.0]];
}
bool windowIsMaximised(void* nsWindow) {
return [(WebviewWindow*)nsWindow isZoomed];
}
bool windowIsFullscreen(void* nsWindow) {
return [(WebviewWindow*)nsWindow styleMask] & NSWindowStyleMaskFullScreen;
}
bool windowIsMinimised(void* nsWindow) {
return [(WebviewWindow*)nsWindow isMiniaturized];
}
bool windowIsFocused(void* nsWindow) {
return [(WebviewWindow*)nsWindow isKeyWindow];
}
// Set Window fullscreen
void windowFullscreen(void* nsWindow) {
if( windowIsFullscreen(nsWindow) ) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[(WebviewWindow*)nsWindow toggleFullScreen:nil];
});}
void windowUnFullscreen(void* nsWindow) {
if( !windowIsFullscreen(nsWindow) ) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[(WebviewWindow*)nsWindow toggleFullScreen:nil];
});
}
// restore window to normal size
void windowRestore(void* nsWindow) {
// If window is fullscreen
if([(WebviewWindow*)nsWindow styleMask] & NSWindowStyleMaskFullScreen) {
[(WebviewWindow*)nsWindow toggleFullScreen:nil];
}
// If window is maximised
if([(WebviewWindow*)nsWindow isZoomed]) {
[(WebviewWindow*)nsWindow zoom:nil];
}
// If window in minimised
if([(WebviewWindow*)nsWindow isMiniaturized]) {
[(WebviewWindow*)nsWindow deminiaturize:nil];
}
}
// disable window fullscreen button
void setFullscreenButtonEnabled(void* nsWindow, bool enabled) {
NSButton *fullscreenButton = [(WebviewWindow*)nsWindow standardWindowButton:NSWindowZoomButton];
fullscreenButton.enabled = enabled;
}
// Set the titlebar style
void windowSetTitleBarAppearsTransparent(void* nsWindow, bool transparent) {
if( transparent ) {
[(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:true];
} else {
[(WebviewWindow*)nsWindow setTitlebarAppearsTransparent:false];
}
}
// Set window fullsize content view
void windowSetFullSizeContent(void* nsWindow, bool fullSize) {
if( fullSize ) {
[(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView];
} else {
[(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView];
}
}
// Set Hide Titlebar
void windowSetHideTitleBar(void* nsWindow, bool hideTitlebar) {
if( hideTitlebar ) {
[(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] & ~NSWindowStyleMaskTitled];
} else {
[(WebviewWindow*)nsWindow setStyleMask:[(WebviewWindow*)nsWindow styleMask] | NSWindowStyleMaskTitled];
}
}
// Set Hide Title in Titlebar
void windowSetHideTitle(void* nsWindow, bool hideTitle) {
if( hideTitle ) {
[(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleHidden];
} else {
[(WebviewWindow*)nsWindow setTitleVisibility:NSWindowTitleVisible];
}
}
// Set Window use toolbar
void windowSetUseToolbar(void* nsWindow, bool useToolbar, int toolbarStyle) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
if( useToolbar ) {
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wails.toolbar"];
[toolbar autorelease];
[window setToolbar:toolbar];
// If macos 11 or higher, set toolbar style
if (@available(macOS 11.0, *)) {
[window setToolbarStyle:toolbarStyle];
}
} else {
[window setToolbar:nil];
}
}
// Set window toolbar style
void windowSetToolbarStyle(void* nsWindow, int style) {
// use @available to check if the function is available
// if not, return
if (@available(macOS 11.0, *)) {
NSToolbar* toolbar = [(WebviewWindow*)nsWindow toolbar];
[toolbar setShowsBaselineSeparator:style];
}
}
// Set Hide Toolbar Separator
void windowSetHideToolbarSeparator(void* nsWindow, bool hideSeparator) {
NSToolbar* toolbar = [(WebviewWindow*)nsWindow toolbar];
if( toolbar == nil ) {
return;
}
[toolbar setShowsBaselineSeparator:!hideSeparator];
}
// Set Window appearance type
void windowSetAppearanceTypeByName(void* nsWindow, const char *appearanceName) {
// set window appearance type by name
// Convert appearance name to NSString
NSString* appearanceNameString = [NSString stringWithUTF8String:appearanceName];
// Set appearance
[(WebviewWindow*)nsWindow setAppearance:[NSAppearance appearanceNamed:appearanceNameString]];
free((void*)appearanceName);
}
// Center window on current monitor
void windowCenter(void* nsWindow) {
[(WebviewWindow*)nsWindow center];
}
// Get the current size of the window
void windowGetSize(void* nsWindow, int* width, int* height) {
NSRect frame = [(WebviewWindow*)nsWindow frame];
*width = frame.size.width;
*height = frame.size.height;
}
// Get window width
int windowGetWidth(void* nsWindow) {
return [(WebviewWindow*)nsWindow frame].size.width;
}
// Get window height
int windowGetHeight(void* nsWindow) {
return [(WebviewWindow*)nsWindow frame].size.height;
}
// Get window position
void windowGetRelativePosition(void* nsWindow, int* x, int* y) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
NSRect frame = [window frame];
*x = frame.origin.x;
// Translate to screen coordinates so Y=0 is the top of the screen
NSScreen* screen = [window screen];
if( screen == NULL ) {
screen = [NSScreen mainScreen];
}
NSRect screenFrame = [screen frame];
*y = screenFrame.size.height - frame.origin.y - frame.size.height;
}
// Get absolute window position
void windowGetAbsolutePosition(void* nsWindow, int* x, int* y) {
NSRect frame = [(WebviewWindow*)nsWindow frame];
*x = frame.origin.x;
*y = frame.origin.y;
}
void windowSetAbsolutePosition(void* nsWindow, int x, int y) {
NSRect frame = [(WebviewWindow*)nsWindow frame];
frame.origin.x = x;
frame.origin.y = y;
[(WebviewWindow*)nsWindow setFrame:frame display:YES];
}
// Destroy window
void windowDestroy(void* nsWindow) {
[(WebviewWindow*)nsWindow close];
}
// Remove drop shadow from window
void windowSetShadow(void* nsWindow, bool hasShadow) {
[(WebviewWindow*)nsWindow setHasShadow:hasShadow];
}
// windowClose closes the current window
static void windowClose(void *window) {
[(WebviewWindow*)window close];
}
// windowZoom
static void windowZoom(void *window) {
[(WebviewWindow*)window zoom:nil];
}
// webviewRenderHTML renders the given HTML
static void windowRenderHTML(void *window, const char *html) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// get window delegate
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate];
// render html
[nsWindow.webView loadHTMLString:[NSString stringWithUTF8String:html] baseURL:nil];
}
static void windowInjectCSS(void *window, const char *css) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// inject css
[nsWindow.webView evaluateJavaScript:[NSString stringWithFormat:@"(function() { var style = document.createElement('style'); style.appendChild(document.createTextNode('%@')); document.head.appendChild(style); })();", [NSString stringWithUTF8String:css]] completionHandler:nil];
free((void*)css);
}
static void windowMinimise(void *window) {
[(WebviewWindow*)window miniaturize:nil];
}
// zoom maximizes the window to the screen dimensions
static void windowMaximise(void *window) {
[(WebviewWindow*)window zoom:nil];
}
static bool isFullScreen(void *window) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
long mask = [nsWindow styleMask];
return (mask & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen;
}
static bool isVisible(void *window) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
return (nsWindow.occlusionState & NSWindowOcclusionStateVisible) == NSWindowOcclusionStateVisible;
}
// windowSetFullScreen
static void windowSetFullScreen(void *window, bool fullscreen) {
if (isFullScreen(window)) {
return;
}
WebviewWindow* nsWindow = (WebviewWindow*)window;
windowSetMaxSize(nsWindow, 0, 0);
windowSetMinSize(nsWindow, 0, 0);
[nsWindow toggleFullScreen:nil];
}
// windowUnminimise
static void windowUnminimise(void *window) {
[(WebviewWindow*)window deminiaturize:nil];
}
// windowUnmaximise
static void windowUnmaximise(void *window) {
[(WebviewWindow*)window zoom:nil];
}
static void windowDisableSizeConstraints(void *window) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// disable size constraints
[nsWindow setContentMinSize:CGSizeZero];
[nsWindow setContentMaxSize:CGSizeZero];
}
static void windowShow(void *window) {
[(WebviewWindow*)window makeKeyAndOrderFront:nil];
}
static void windowHide(void *window) {
[(WebviewWindow*)window orderOut:nil];
}
// windowShowMenu opens an NSMenu at the given coordinates
static void windowShowMenu(void *window, void *menu, int x, int y) {
NSMenu* nsMenu = (NSMenu*)menu;
WKWebView* webView = ((WebviewWindow*)window).webView;
NSPoint point = NSMakePoint(x, y);
[nsMenu popUpMenuPositioningItem:nil atLocation:point inView:webView];
}
// windowIgnoreMouseEvents makes the window ignore mouse events
static void windowIgnoreMouseEvents(void *window, bool ignore) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
[nsWindow setIgnoresMouseEvents:ignore];
}
// Make the given window frameless
static void windowSetFrameless(void *window, bool frameless) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// set the window style to be frameless
if (frameless) {
[nsWindow setStyleMask:([nsWindow styleMask] | NSWindowStyleMaskFullSizeContentView)];
} else {
[nsWindow setStyleMask:([nsWindow styleMask] & ~NSWindowStyleMaskFullSizeContentView)];
}
}
static void startDrag(void *window) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// Get delegate
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate];
// start drag
[windowDelegate startDrag:nsWindow];
}
// Credit: https://stackoverflow.com/q/33319295
static void windowPrint(void *window) {
// Check if macOS 11.0 or newer
if (@available(macOS 11.0, *)) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
WebviewWindowDelegate* windowDelegate = (WebviewWindowDelegate*)[nsWindow delegate];
WKWebView* webView = nsWindow.webView;
// TODO: Think about whether to expose this as config
NSPrintInfo *pInfo = [NSPrintInfo sharedPrintInfo];
pInfo.horizontalPagination = NSPrintingPaginationModeAutomatic;
pInfo.verticalPagination = NSPrintingPaginationModeAutomatic;
pInfo.verticallyCentered = YES;
pInfo.horizontallyCentered = YES;
pInfo.orientation = NSPaperOrientationLandscape;
pInfo.leftMargin = 30;
pInfo.rightMargin = 30;
pInfo.topMargin = 30;
pInfo.bottomMargin = 30;
NSPrintOperation *po = [webView printOperationWithPrintInfo:pInfo];
po.showsPrintPanel = YES;
po.showsProgressPanel = YES;
// Without the next line you get an exception. Also it seems to
// completely ignore the values in the rect. I tried changing them
// in both x and y direction to include content scrolled off screen.
// It had no effect whatsoever in either direction.
po.view.frame = webView.bounds;
// [printOperation runOperation] DOES NOT WORK WITH WKWEBVIEW, use
[po runOperationModalForWindow:window delegate:windowDelegate didRunSelector:nil contextInfo:nil];
}
}
void setWindowEnabled(void *window, bool enabled) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
[nsWindow setIgnoresMouseEvents:!enabled];
}
void windowSetEnabled(void *window, bool enabled) {
// TODO: Implement
}
void windowFocus(void *window) {
WebviewWindow* nsWindow = (WebviewWindow*)window;
// If the current application is not active, activate it
if (![[NSApplication sharedApplication] isActive]) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
[nsWindow makeKeyAndOrderFront:nil];
[nsWindow makeKeyWindow];
}
*/
import "C"
import (
"net/url"
"sync"
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events"
)
var showDevTools = func(window unsafe.Pointer) {}
type macosWebviewWindow struct {
nsWindow unsafe.Pointer
parent *WebviewWindow
}
func (w *macosWebviewWindow) handleKeyEvent(acceleratorString string) {
// Parse acceleratorString
accelerator, err := parseAccelerator(acceleratorString)
if err != nil {
globalApplication.error("unable to parse accelerator: %s", err.Error())
return
}
w.parent.processKeyBinding(accelerator.String())
}
func (w *macosWebviewWindow) isFocused() bool {
return bool(C.windowIsFocused(w.nsWindow))
}
func (w *macosWebviewWindow) setAbsolutePosition(x int, y int) {
C.windowSetAbsolutePosition(w.nsWindow, C.int(x), C.int(y))
}
func (w *macosWebviewWindow) print() error {
C.windowPrint(w.nsWindow)
return nil
}
func (w *macosWebviewWindow) startResize(_ string) error {
// Never called. Handled natively by the OS.
return nil
}
func (w *macosWebviewWindow) focus() {
// Make the window key and main
C.windowFocus(w.nsWindow)
}
func (w *macosWebviewWindow) openContextMenu(menu *Menu, data *ContextMenuData) {
// Create the menu
thisMenu := newMenuImpl(menu)
thisMenu.update()
C.windowShowMenu(w.nsWindow, thisMenu.nsMenu, C.int(data.X), C.int(data.Y))
}
func (w *macosWebviewWindow) getZoom() float64 {
return float64(C.windowZoomGet(w.nsWindow))
}
func (w *macosWebviewWindow) setZoom(zoom float64) {
C.windowZoomSet(w.nsWindow, C.double(zoom))
}
func (w *macosWebviewWindow) setFrameless(frameless bool) {
C.windowSetFrameless(w.nsWindow, C.bool(frameless))
if frameless {
C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(true))
C.windowSetHideTitle(w.nsWindow, C.bool(true))
} else {
macOptions := w.parent.options.Mac
appearsTransparent := macOptions.TitleBar.AppearsTransparent
hideTitle := macOptions.TitleBar.HideTitle
C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(appearsTransparent))
C.windowSetHideTitle(w.nsWindow, C.bool(hideTitle))
}
}
func (w *macosWebviewWindow) setHasShadow(hasShadow bool) {
C.windowSetShadow(w.nsWindow, C.bool(hasShadow))
}
func (w *macosWebviewWindow) getScreen() (*Screen, error) {
return getScreenForWindow(w)
}
func (w *macosWebviewWindow) show() {
C.windowShow(w.nsWindow)
}
func (w *macosWebviewWindow) hide() {
C.windowHide(w.nsWindow)
}
func (w *macosWebviewWindow) setFullscreenButtonEnabled(enabled bool) {
C.setFullscreenButtonEnabled(w.nsWindow, C.bool(enabled))
}
func (w *macosWebviewWindow) disableSizeConstraints() {
C.windowDisableSizeConstraints(w.nsWindow)
}
func (w *macosWebviewWindow) unfullscreen() {
C.windowUnFullscreen(w.nsWindow)
}
func (w *macosWebviewWindow) fullscreen() {
C.windowFullscreen(w.nsWindow)
}
func (w *macosWebviewWindow) unminimise() {
C.windowUnminimise(w.nsWindow)
}
func (w *macosWebviewWindow) unmaximise() {
C.windowUnmaximise(w.nsWindow)
}
func (w *macosWebviewWindow) maximise() {
C.windowMaximise(w.nsWindow)
}
func (w *macosWebviewWindow) minimise() {
C.windowMinimise(w.nsWindow)
}
func (w *macosWebviewWindow) on(eventID uint) {
//C.registerListener(C.uint(eventID))
}
func (w *macosWebviewWindow) zoom() {
C.windowZoom(w.nsWindow)
}
func (w *macosWebviewWindow) windowZoom() {
C.windowZoom(w.nsWindow)
}
func (w *macosWebviewWindow) close() {
C.windowClose(w.nsWindow)
}
func (w *macosWebviewWindow) zoomIn() {
C.windowZoomIn(w.nsWindow)
}
func (w *macosWebviewWindow) zoomOut() {
C.windowZoomOut(w.nsWindow)
}
func (w *macosWebviewWindow) zoomReset() {
C.windowZoomReset(w.nsWindow)
}
func (w *macosWebviewWindow) toggleDevTools() {
showDevTools(w.nsWindow)
}
func (w *macosWebviewWindow) reload() {
//TODO: Implement
globalApplication.debug("reload called on WebviewWindow", "parentID", w.parent.id)
}
func (w *macosWebviewWindow) forceReload() {
//TODO: Implement
globalApplication.debug("force reload called on WebviewWindow", "parentID", w.parent.id)
}
func (w *macosWebviewWindow) center() {
C.windowCenter(w.nsWindow)
}
func (w *macosWebviewWindow) isMinimised() bool {
return w.syncMainThreadReturningBool(func() bool {
return bool(C.windowIsMinimised(w.nsWindow))
})
}
func (w *macosWebviewWindow) isMaximised() bool {
return w.syncMainThreadReturningBool(func() bool {
return bool(C.windowIsMaximised(w.nsWindow))
})
}
func (w *macosWebviewWindow) isFullscreen() bool {
return w.syncMainThreadReturningBool(func() bool {
return bool(C.windowIsFullscreen(w.nsWindow))
})
}
func (w *macosWebviewWindow) isNormal() bool {
return !w.isMinimised() && !w.isMaximised() && !w.isFullscreen()
}
func (w *macosWebviewWindow) isVisible() bool {
return bool(C.isVisible(w.nsWindow))
}
func (w *macosWebviewWindow) 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 *macosWebviewWindow) restore() {
// restore window to normal size
C.windowRestore(w.nsWindow)
}
func (w *macosWebviewWindow) restoreWindow() {
C.windowRestore(w.nsWindow)
}
func (w *macosWebviewWindow) setEnabled(enabled bool) {
C.windowSetEnabled(w.nsWindow, C.bool(enabled))
}
func (w *macosWebviewWindow) execJS(js string) {
InvokeAsync(func() {
C.windowExecJS(w.nsWindow, C.CString(js))
})
}
func (w *macosWebviewWindow) setURL(uri string) {
if uri != "" {
parsedURL, err := url.Parse(uri)
if err == nil && parsedURL.Scheme == "" && parsedURL.Host == "" {
// TODO handle this in a central location, the scheme and host might be platform dependant.
parsedURL.Scheme = "wails"
parsedURL.Host = "wails"
uri = parsedURL.String()
}
}
C.navigationLoadURL(w.nsWindow, C.CString(uri))
}
func (w *macosWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {
C.windowSetAlwaysOnTop(w.nsWindow, C.bool(alwaysOnTop))
}
func newWindowImpl(parent *WebviewWindow) *macosWebviewWindow {
result := &macosWebviewWindow{
parent: parent,
}
return result
}
func (w *macosWebviewWindow) setTitle(title string) {
if !w.parent.options.Frameless {
cTitle := C.CString(title)
C.windowSetTitle(w.nsWindow, cTitle)
}
}
func (w *macosWebviewWindow) flash(_ bool) {
// Not supported on macOS
}
func (w *macosWebviewWindow) setSize(width, height int) {
C.windowSetSize(w.nsWindow, C.int(width), C.int(height))
}
func (w *macosWebviewWindow) setMinSize(width, height int) {
C.windowSetMinSize(w.nsWindow, C.int(width), C.int(height))
}
func (w *macosWebviewWindow) setMaxSize(width, height int) {
C.windowSetMaxSize(w.nsWindow, C.int(width), C.int(height))
}
func (w *macosWebviewWindow) setResizable(resizable bool) {
C.windowSetResizable(w.nsWindow, C.bool(resizable))
}
func (w *macosWebviewWindow) enableDevTools() {
C.windowEnableDevTools(w.nsWindow)
}
func (w *macosWebviewWindow) size() (int, int) {
var width, height C.int
var wg sync.WaitGroup
wg.Add(1)
globalApplication.dispatchOnMainThread(func() {
C.windowGetSize(w.nsWindow, &width, &height)
wg.Done()
})
wg.Wait()
return int(width), int(height)
}
func (w *macosWebviewWindow) setRelativePosition(x, y int) {
C.windowSetRelativePosition(w.nsWindow, C.int(x), C.int(y))
}
func (w *macosWebviewWindow) width() int {
var width C.int
var wg sync.WaitGroup
wg.Add(1)
globalApplication.dispatchOnMainThread(func() {
width = C.windowGetWidth(w.nsWindow)
wg.Done()
})
wg.Wait()
return int(width)
}
func (w *macosWebviewWindow) height() int {
var height C.int
var wg sync.WaitGroup
wg.Add(1)
globalApplication.dispatchOnMainThread(func() {
height = C.windowGetHeight(w.nsWindow)
wg.Done()
})
wg.Wait()
return int(height)
}
func bool2CboolPtr(value bool) *C.bool {
v := C.bool(value)
return &v
}
func (w *macosWebviewWindow) getWebviewPreferences() C.struct_WebviewPreferences {
wvprefs := w.parent.options.Mac.WebviewPreferences
var result C.struct_WebviewPreferences
if wvprefs.TextInteractionEnabled.IsSet() {
result.TextInteractionEnabled = bool2CboolPtr(wvprefs.TextInteractionEnabled.Get())
}
if wvprefs.TabFocusesLinks.IsSet() {
result.TabFocusesLinks = bool2CboolPtr(wvprefs.TabFocusesLinks.Get())
}
if wvprefs.FullscreenEnabled.IsSet() {
result.FullscreenEnabled = bool2CboolPtr(wvprefs.FullscreenEnabled.Get())
}
return result
}
func (w *macosWebviewWindow) run() {
for eventId := range w.parent.eventListeners {
w.on(eventId)
}
globalApplication.dispatchOnMainThread(func() {
options := w.parent.options
macOptions := options.Mac
w.nsWindow = C.windowNew(C.uint(w.parent.id),
C.int(options.Width),
C.int(options.Height),
C.bool(macOptions.EnableFraudulentWebsiteWarnings),
C.bool(options.Frameless),
C.bool(options.EnableDragAndDrop),
w.getWebviewPreferences(),
)
w.setTitle(options.Title)
w.setAlwaysOnTop(options.AlwaysOnTop)
w.setResizable(!options.DisableResize)
if options.MinWidth != 0 || options.MinHeight != 0 {
w.setMinSize(options.MinWidth, options.MinHeight)
}
if options.MaxWidth != 0 || options.MaxHeight != 0 {
w.setMaxSize(options.MaxWidth, options.MaxHeight)
}
//w.setZoom(options.Zoom)
if globalApplication.isDebugMode || options.DevToolsEnabled {
w.enableDevTools()
}
w.setBackgroundColour(options.BackgroundColour)
switch macOptions.Backdrop {
case MacBackdropTransparent:
C.windowSetTransparent(w.nsWindow)
C.webviewSetTransparent(w.nsWindow)
case MacBackdropTranslucent:
C.windowSetTranslucent(w.nsWindow)
C.webviewSetTransparent(w.nsWindow)
}
if options.IgnoreMouseEvents {
C.windowIgnoreMouseEvents(w.nsWindow, C.bool(true))
}
titleBarOptions := macOptions.TitleBar
if !w.parent.options.Frameless {
C.windowSetTitleBarAppearsTransparent(w.nsWindow, C.bool(titleBarOptions.AppearsTransparent))
C.windowSetHideTitleBar(w.nsWindow, C.bool(titleBarOptions.Hide))
C.windowSetHideTitle(w.nsWindow, C.bool(titleBarOptions.HideTitle))
C.windowSetFullSizeContent(w.nsWindow, C.bool(titleBarOptions.FullSizeContent))
if titleBarOptions.UseToolbar {
C.windowSetUseToolbar(w.nsWindow, C.bool(titleBarOptions.UseToolbar), C.int(titleBarOptions.ToolbarStyle))
}
C.windowSetHideToolbarSeparator(w.nsWindow, C.bool(titleBarOptions.HideToolbarSeparator))
}
if macOptions.Appearance != "" {
C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance)))
}
if macOptions.InvisibleTitleBarHeight != 0 {
C.windowSetInvisibleTitleBar(w.nsWindow, C.uint(macOptions.InvisibleTitleBarHeight))
}
switch w.parent.options.StartState {
case WindowStateMaximised:
w.maximise()
case WindowStateMinimised:
w.minimise()
case WindowStateFullscreen:
w.fullscreen()
}
C.windowCenter(w.nsWindow)
if options.URL != "" {
w.setURL(options.URL)
}
// We need to wait for the HTML to load before we can execute the javascript
w.parent.On(events.Mac.WebViewDidFinishNavigation, func(_ *WindowEvent) {
if options.JS != "" {
w.execJS(options.JS)
}
if options.CSS != "" {
C.windowInjectCSS(w.nsWindow, C.CString(options.CSS))
}
})
// Translate ShouldClose to common WindowClosing event
w.parent.On(events.Mac.WindowShouldClose, func(_ *WindowEvent) {
w.parent.emit(events.Common.WindowClosing)
})
// Translate WindowDidResignKey to common WindowLostFocus event
w.parent.On(events.Mac.WindowDidResignKey, func(_ *WindowEvent) {
w.parent.emit(events.Common.WindowLostFocus)
})
w.parent.On(events.Mac.WindowDidResignMain, func(_ *WindowEvent) {
w.parent.emit(events.Common.WindowLostFocus)
})
if options.HTML != "" {
w.setHTML(options.HTML)
}
if options.Hidden == false {
C.windowShow(w.nsWindow)
w.setHasShadow(!options.Mac.DisableShadow)
} else {
// We have to wait until the window is shown before we can remove the shadow
var cancel func()
cancel = w.parent.On(events.Mac.WindowDidBecomeKey, func(_ *WindowEvent) {
w.setHasShadow(!options.Mac.DisableShadow)
cancel()
})
}
})
}
func (w *macosWebviewWindow) nativeWindowHandle() uintptr {
return uintptr(w.nsWindow)
}
func (w *macosWebviewWindow) setBackgroundColour(colour RGBA) {
C.windowSetBackgroundColour(w.nsWindow, C.int(colour.Red), C.int(colour.Green), C.int(colour.Blue), C.int(colour.Alpha))
}
func (w *macosWebviewWindow) relativePosition() (int, int) {
var x, y C.int
InvokeSync(func() {
C.windowGetRelativePosition(w.nsWindow, &x, &y)
})
return int(x), int(y)
}
func (w *macosWebviewWindow) absolutePosition() (int, int) {
var x, y C.int
InvokeSync(func() {
C.windowGetAbsolutePosition(w.nsWindow, &x, &y)
})
return int(x), int(y)
}
func (w *macosWebviewWindow) destroy() {
w.parent.markAsDestroyed()
C.windowDestroy(w.nsWindow)
}
func (w *macosWebviewWindow) setHTML(html string) {
// Convert HTML to C string
cHTML := C.CString(html)
// Render HTML
C.windowRenderHTML(w.nsWindow, cHTML)
}
func (w *macosWebviewWindow) startDrag() error {
C.startDrag(w.nsWindow)
return nil
}