mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
* fix(v3/macos): guard InvisibleTitleBarHeight and fix top-corner resize shaking (#4960) - Only apply InvisibleTitleBarHeight when the native drag area is actually hidden (frameless window or transparent title bar presets like HiddenInset). Previously it was applied unconditionally, which could swallow clicks near the top of standard windows. - Skip drag initiation when the click is near the left/right window edges within the invisible title bar zone. This prevents conflict between dragging and native top-corner resizing, which caused window content to shake/jitter. Fixes #4960 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update InvisibleTitleBarHeight docs and changelog (#4960) Document that InvisibleTitleBarHeight only applies to frameless or transparent title bar windows, and add changelog entries for the guard and edge-detection fixes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1026 lines
42 KiB
Objective-C
1026 lines
42 KiB
Objective-C
//go:build darwin && !ios
|
|
#import <Foundation/Foundation.h>
|
|
#import <Cocoa/Cocoa.h>
|
|
#import <QuartzCore/QuartzCore.h>
|
|
#import "webview_window_darwin.h"
|
|
#import "../events/events_darwin.h"
|
|
extern void processMessage(unsigned int, const char*, const char *, bool);
|
|
extern void processURLRequest(unsigned int, void *);
|
|
extern void processDragItems(unsigned int windowId, char** arr, int length, int x, int y);
|
|
extern void processWindowKeyDownEvent(unsigned int, const char*);
|
|
extern bool hasListeners(unsigned int);
|
|
extern bool windowShouldUnconditionallyClose(unsigned int);
|
|
extern bool windowIsHidden(unsigned int);
|
|
// Define custom glass effect style constants (these match the Go constants)
|
|
typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) {
|
|
LiquidGlassStyleAutomatic = 0,
|
|
LiquidGlassStyleLight = 1,
|
|
LiquidGlassStyleDark = 2,
|
|
LiquidGlassStyleVibrant = 3
|
|
};
|
|
@implementation WebviewWindow
|
|
- (WebviewWindow*) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation;
|
|
{
|
|
self = [super initWithContentRect:contentRect styleMask:windowStyle backing:bufferingType defer:deferCreation];
|
|
[self setAlphaValue:1.0];
|
|
[self setBackgroundColor:[NSColor clearColor]];
|
|
[self setOpaque:NO];
|
|
[self setMovableByWindowBackground:YES];
|
|
return self;
|
|
}
|
|
- (void)keyDown:(NSEvent *)event {
|
|
NSUInteger modifierFlags = event.modifierFlags;
|
|
// Create an array to hold the modifier strings
|
|
NSMutableArray *modifierStrings = [NSMutableArray array];
|
|
// Check for modifier flags and add corresponding strings to the array
|
|
if (modifierFlags & NSEventModifierFlagShift) {
|
|
[modifierStrings addObject:@"shift"];
|
|
}
|
|
if (modifierFlags & NSEventModifierFlagControl) {
|
|
[modifierStrings addObject:@"ctrl"];
|
|
}
|
|
if (modifierFlags & NSEventModifierFlagOption) {
|
|
[modifierStrings addObject:@"option"];
|
|
}
|
|
if (modifierFlags & NSEventModifierFlagCommand) {
|
|
[modifierStrings addObject:@"cmd"];
|
|
}
|
|
NSString *keyString = [self keyStringFromEvent:event];
|
|
if (keyString.length > 0) {
|
|
[modifierStrings addObject:keyString];
|
|
}
|
|
// Combine the modifier strings with the key character
|
|
NSString *keyEventString = [modifierStrings componentsJoinedByString:@"+"];
|
|
const char* utf8String = [keyEventString UTF8String];
|
|
WebviewWindowDelegate *delegate = (WebviewWindowDelegate*)self.delegate;
|
|
processWindowKeyDownEvent(delegate.windowId, utf8String);
|
|
}
|
|
- (NSString *)keyStringFromEvent:(NSEvent *)event {
|
|
// Get the pressed key
|
|
// Check for special keys like escape and tab
|
|
NSString *characters = [event characters];
|
|
if (characters.length == 0) {
|
|
return @"";
|
|
}
|
|
if ([characters isEqualToString:@"\r"]) {
|
|
return @"enter";
|
|
}
|
|
if ([characters isEqualToString:@"\b"]) {
|
|
return @"backspace";
|
|
}
|
|
if ([characters isEqualToString:@"\e"]) {
|
|
return @"escape";
|
|
}
|
|
// page down
|
|
if ([characters isEqualToString:@"\x0B"]) {
|
|
return @"page down";
|
|
}
|
|
// page up
|
|
if ([characters isEqualToString:@"\x0E"]) {
|
|
return @"page up";
|
|
}
|
|
// home
|
|
if ([characters isEqualToString:@"\x01"]) {
|
|
return @"home";
|
|
}
|
|
// end
|
|
if ([characters isEqualToString:@"\x04"]) {
|
|
return @"end";
|
|
}
|
|
// clear
|
|
if ([characters isEqualToString:@"\x0C"]) {
|
|
return @"clear";
|
|
}
|
|
switch ([event keyCode]) {
|
|
// Function keys
|
|
case 122: return @"f1";
|
|
case 120: return @"f2";
|
|
case 99: return @"f3";
|
|
case 118: return @"f4";
|
|
case 96: return @"f5";
|
|
case 97: return @"f6";
|
|
case 98: return @"f7";
|
|
case 100: return @"f8";
|
|
case 101: return @"f9";
|
|
case 109: return @"f10";
|
|
case 103: return @"f11";
|
|
case 111: return @"f12";
|
|
case 105: return @"f13";
|
|
case 107: return @"f14";
|
|
case 113: return @"f15";
|
|
case 106: return @"f16";
|
|
case 64: return @"f17";
|
|
case 79: return @"f18";
|
|
case 80: return @"f19";
|
|
case 90: return @"f20";
|
|
// Letter keys
|
|
case 0: return @"a";
|
|
case 11: return @"b";
|
|
case 8: return @"c";
|
|
case 2: return @"d";
|
|
case 14: return @"e";
|
|
case 3: return @"f";
|
|
case 5: return @"g";
|
|
case 4: return @"h";
|
|
case 34: return @"i";
|
|
case 38: return @"j";
|
|
case 40: return @"k";
|
|
case 37: return @"l";
|
|
case 46: return @"m";
|
|
case 45: return @"n";
|
|
case 31: return @"o";
|
|
case 35: return @"p";
|
|
case 12: return @"q";
|
|
case 15: return @"r";
|
|
case 1: return @"s";
|
|
case 17: return @"t";
|
|
case 32: return @"u";
|
|
case 9: return @"v";
|
|
case 13: return @"w";
|
|
case 7: return @"x";
|
|
case 16: return @"y";
|
|
case 6: return @"z";
|
|
// Number keys
|
|
case 29: return @"0";
|
|
case 18: return @"1";
|
|
case 19: return @"2";
|
|
case 20: return @"3";
|
|
case 21: return @"4";
|
|
case 23: return @"5";
|
|
case 22: return @"6";
|
|
case 26: return @"7";
|
|
case 28: return @"8";
|
|
case 25: return @"9";
|
|
// Other special keys
|
|
case 51: return @"delete";
|
|
case 117: return @"forward delete";
|
|
case 123: return @"left";
|
|
case 124: return @"right";
|
|
case 126: return @"up";
|
|
case 125: return @"down";
|
|
case 48: return @"tab";
|
|
case 53: return @"escape";
|
|
case 49: return @"space";
|
|
// Punctuation and other keys (for a standard US layout)
|
|
case 33: return @"[";
|
|
case 30: return @"]";
|
|
case 43: return @",";
|
|
case 27: return @"-";
|
|
case 39: return @"'";
|
|
case 44: return @"/";
|
|
case 47: return @".";
|
|
case 41: return @";";
|
|
case 24: return @"=";
|
|
case 50: return @"`";
|
|
case 42: return @"\\";
|
|
default: return @"";
|
|
}
|
|
}
|
|
- (BOOL)canBecomeKeyWindow {
|
|
return YES;
|
|
}
|
|
- (BOOL) canBecomeMainWindow {
|
|
return YES;
|
|
}
|
|
- (BOOL) acceptsFirstResponder {
|
|
return YES;
|
|
}
|
|
- (BOOL) becomeFirstResponder {
|
|
return YES;
|
|
}
|
|
- (BOOL) resignFirstResponder {
|
|
return YES;
|
|
}
|
|
- (void) setDelegate:(id<NSWindowDelegate>) delegate {
|
|
[delegate retain];
|
|
[super setDelegate: delegate];
|
|
// If the delegate is our WebviewWindowDelegate (which handles NSDraggingDestination)
|
|
if ([delegate isKindOfClass:[WebviewWindowDelegate class]]) {
|
|
[self registerForDraggedTypes:@[NSFilenamesPboardType]]; // 'self' is the WebviewWindow instance
|
|
}
|
|
}
|
|
- (void) dealloc {
|
|
// Remove the script handler, otherwise WebviewWindowDelegate won't get deallocated
|
|
// See: https://stackoverflow.com/questions/26383031/wkwebview-causes-my-view-controller-to-leak
|
|
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"external"];
|
|
if (self.delegate) {
|
|
[self.delegate release];
|
|
}
|
|
[super dealloc];
|
|
}
|
|
- (void)windowDidZoom:(NSNotification *)notification {
|
|
NSWindow *window = notification.object;
|
|
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate];
|
|
if ([window isZoomed]) {
|
|
if (hasListeners(EventWindowMaximise)) {
|
|
processWindowEvent(delegate.windowId, EventWindowMaximise);
|
|
}
|
|
} else {
|
|
if (hasListeners(EventWindowUnMaximise)) {
|
|
processWindowEvent(delegate.windowId, EventWindowUnMaximise);
|
|
}
|
|
}
|
|
}
|
|
- (void)performZoomIn:(id)sender {
|
|
[super zoom:sender];
|
|
if (hasListeners(EventWindowZoomIn)) {
|
|
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate];
|
|
processWindowEvent(delegate.windowId, EventWindowZoomIn);
|
|
}
|
|
}
|
|
- (void)performZoomOut:(id)sender {
|
|
[super zoom:sender];
|
|
if (hasListeners(EventWindowZoomOut)) {
|
|
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate];
|
|
processWindowEvent(delegate.windowId, EventWindowZoomOut);
|
|
}
|
|
}
|
|
- (void)performZoomReset:(id)sender {
|
|
[self setFrame:[self frameRectForContentRect:[[self screen] visibleFrame]] display:YES];
|
|
if (hasListeners(EventWindowZoomReset)) {
|
|
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate];
|
|
processWindowEvent(delegate.windowId, EventWindowZoomReset);
|
|
}
|
|
}
|
|
@end
|
|
@implementation WebviewWindowDelegate
|
|
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
|
|
NSPasteboard *pasteboard = [sender draggingPasteboard];
|
|
if ([[pasteboard types] containsObject:NSFilenamesPboardType]) {
|
|
if (hasListeners(EventWindowFileDraggingEntered)) {
|
|
processWindowEvent(self.windowId, EventWindowFileDraggingEntered);
|
|
}
|
|
return NSDragOperationCopy;
|
|
}
|
|
return NSDragOperationNone;
|
|
}
|
|
- (void)draggingExited:(id<NSDraggingInfo>)sender {
|
|
if (hasListeners(EventWindowFileDraggingExited)) {
|
|
processWindowEvent(self.windowId, EventWindowFileDraggingExited);
|
|
}
|
|
}
|
|
- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender {
|
|
return YES;
|
|
}
|
|
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
|
|
NSPasteboard *pasteboard = [sender draggingPasteboard];
|
|
if (hasListeners(EventWindowFileDraggingPerformed)) {
|
|
processWindowEvent(self.windowId, EventWindowFileDraggingPerformed);
|
|
}
|
|
if ([[pasteboard types] containsObject:NSFilenamesPboardType]) {
|
|
NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType];
|
|
NSUInteger count = [files count];
|
|
if (count == 0) {
|
|
return NO;
|
|
}
|
|
char** cArray = (char**)malloc(count * sizeof(char*));
|
|
if (cArray == NULL) {
|
|
return NO;
|
|
}
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
NSString* str = files[i];
|
|
cArray[i] = (char*)[str UTF8String];
|
|
}
|
|
// Get the WebviewWindow instance, which is the dragging destination
|
|
WebviewWindow *window = (WebviewWindow *)[sender draggingDestinationWindow];
|
|
WKWebView *webView = window.webView; // Get the webView from the window
|
|
NSPoint dropPointInWindow = [sender draggingLocation];
|
|
NSPoint dropPointInView = [webView convertPoint:dropPointInWindow fromView:nil]; // Convert to webView's coordinate system
|
|
CGFloat viewHeight = webView.frame.size.height;
|
|
int x = (int)dropPointInView.x;
|
|
int y = (int)(viewHeight - dropPointInView.y); // Flip Y for web coordinate system
|
|
processDragItems(self.windowId, cArray, (int)count, x, y); // self.windowId is from the delegate
|
|
free(cArray);
|
|
return NO;
|
|
}
|
|
return NO;
|
|
}
|
|
// Original WebviewWindowDelegate methods continue here...
|
|
- (BOOL)windowShouldClose:(NSWindow *)sender {
|
|
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[sender delegate];
|
|
// Check if this window should close unconditionally (called from Close() method)
|
|
if (windowShouldUnconditionallyClose(delegate.windowId)) {
|
|
return true;
|
|
}
|
|
// If the window is hidden (via Hide()), don't trigger close events.
|
|
// This fixes issue #4389 where hiding the last window with
|
|
// ApplicationShouldTerminateAfterLastWindowClosed=true would incorrectly
|
|
// trigger the close event sequence and destroy the window.
|
|
if (windowIsHidden(delegate.windowId)) {
|
|
return false;
|
|
}
|
|
// For user-initiated closes, emit WindowClosing event and let the application decide
|
|
processWindowEvent(delegate.windowId, EventWindowShouldClose);
|
|
return false;
|
|
}
|
|
- (void) dealloc {
|
|
// Makes sure to remove the retained properties so the reference counter of the retains are decreased
|
|
self.leftMouseEvent = nil;
|
|
[super dealloc];
|
|
}
|
|
- (void) startDrag:(WebviewWindow*)window {
|
|
[window performWindowDragWithEvent:self.leftMouseEvent];
|
|
}
|
|
// Handle script messages from the external bridge
|
|
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
|
|
// Get the origin from the message's frame
|
|
NSString *origin = nil;
|
|
if (message.frameInfo && message.frameInfo.request && message.frameInfo.request.URL) {
|
|
NSURL *url = message.frameInfo.request.URL;
|
|
if (url.scheme && url.host) {
|
|
origin = [url absoluteString];
|
|
}
|
|
}
|
|
id body = message.body;
|
|
NSString *m = [body isKindOfClass:[NSString class]] ? (NSString *)body : [body description];
|
|
const char *_m = [m UTF8String];
|
|
const char *_origin = origin ? [origin UTF8String] : "";
|
|
processMessage(self.windowId, _m, _origin, message.frameInfo.isMainFrame);
|
|
}
|
|
- (void)handleLeftMouseDown:(NSEvent *)event {
|
|
self.leftMouseEvent = event;
|
|
NSWindow *window = [event window];
|
|
WebviewWindowDelegate* delegate = (WebviewWindowDelegate*)[window delegate];
|
|
if( self.invisibleTitleBarHeight > 0 ) {
|
|
NSPoint location = [event locationInWindow];
|
|
NSRect frame = [window frame];
|
|
if( location.y > frame.size.height - self.invisibleTitleBarHeight ) {
|
|
// Skip drag if the click is near a window edge (resize zone).
|
|
// This prevents conflict between dragging and native top-corner resizing,
|
|
// which causes window content to shake/jitter (#4960).
|
|
CGFloat resizeThreshold = 5.0;
|
|
BOOL nearLeftEdge = location.x < resizeThreshold;
|
|
BOOL nearRightEdge = location.x > frame.size.width - resizeThreshold;
|
|
if( nearLeftEdge || nearRightEdge ) {
|
|
return;
|
|
}
|
|
[window performWindowDragWithEvent:event];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
- (void)handleLeftMouseUp:(NSWindow *)window {
|
|
self.leftMouseEvent = nil;
|
|
}
|
|
- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
|
|
NSURL *url = urlSchemeTask.request.URL;
|
|
fflush(stdout);
|
|
if ([url.path hasSuffix:@".css"] || [url.path containsString:@"style"]) {
|
|
fflush(stdout);
|
|
}
|
|
processURLRequest(self.windowId, urlSchemeTask);
|
|
}
|
|
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask {
|
|
NSInputStream *stream = urlSchemeTask.request.HTTPBodyStream;
|
|
if (stream) {
|
|
NSStreamStatus status = stream.streamStatus;
|
|
if (status != NSStreamStatusClosed && status != NSStreamStatusNotOpen) {
|
|
[stream close];
|
|
}
|
|
}
|
|
}
|
|
- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions {
|
|
if (self.showToolbarWhenFullscreen) {
|
|
return proposedOptions;
|
|
} else {
|
|
return proposedOptions | NSApplicationPresentationAutoHideToolbar;
|
|
}
|
|
}
|
|
- (void)windowDidChangeOcclusionState:(NSNotification *)notification {
|
|
NSWindow *window = notification.object;
|
|
BOOL isVisible = ([window occlusionState] & NSWindowOcclusionStateVisible) != 0;
|
|
if (hasListeners(isVisible ? EventWindowShow : EventWindowHide)) {
|
|
processWindowEvent(self.windowId, isVisible ? EventWindowShow : EventWindowHide);
|
|
}
|
|
}
|
|
- (void)windowDidBecomeKey:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidBecomeKey) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidBecomeKey);
|
|
}
|
|
}
|
|
- (void)windowDidBecomeMain:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidBecomeMain) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidBecomeMain);
|
|
}
|
|
}
|
|
- (void)windowDidBeginSheet:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidBeginSheet) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidBeginSheet);
|
|
}
|
|
}
|
|
- (void)windowDidChangeAlpha:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeAlpha) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeAlpha);
|
|
}
|
|
}
|
|
- (void)windowDidChangeBackingLocation:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeBackingLocation) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeBackingLocation);
|
|
}
|
|
}
|
|
- (void)windowDidChangeBackingProperties:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeBackingProperties) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeBackingProperties);
|
|
}
|
|
}
|
|
- (void)windowDidChangeCollectionBehavior:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeCollectionBehavior) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeCollectionBehavior);
|
|
}
|
|
}
|
|
- (void)windowDidChangeEffectiveAppearance:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeEffectiveAppearance) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeEffectiveAppearance);
|
|
}
|
|
}
|
|
- (void)windowDidChangeOrderingMode:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeOrderingMode) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeOrderingMode);
|
|
}
|
|
}
|
|
- (void)windowDidChangeScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeScreen);
|
|
}
|
|
}
|
|
- (void)windowDidChangeScreenParameters:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeScreenParameters) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeScreenParameters);
|
|
}
|
|
}
|
|
- (void)windowDidChangeScreenProfile:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeScreenProfile) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeScreenProfile);
|
|
}
|
|
}
|
|
- (void)windowDidChangeScreenSpace:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeScreenSpace) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeScreenSpace);
|
|
}
|
|
}
|
|
- (void)windowDidChangeScreenSpaceProperties:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeScreenSpaceProperties) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeScreenSpaceProperties);
|
|
}
|
|
}
|
|
- (void)windowDidChangeSharingType:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeSharingType) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeSharingType);
|
|
}
|
|
}
|
|
- (void)windowDidChangeSpace:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeSpace) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeSpace);
|
|
}
|
|
}
|
|
- (void)windowDidChangeSpaceOrderingMode:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeSpaceOrderingMode) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeSpaceOrderingMode);
|
|
}
|
|
}
|
|
- (void)windowDidChangeTitle:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeTitle) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeTitle);
|
|
}
|
|
}
|
|
- (void)windowDidChangeToolbar:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidChangeToolbar) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidChangeToolbar);
|
|
}
|
|
}
|
|
- (void)windowDidDeminiaturize:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidDeminiaturize) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidDeminiaturize);
|
|
}
|
|
}
|
|
- (void)windowDidEndSheet:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidEndSheet) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidEndSheet);
|
|
}
|
|
}
|
|
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidEnterFullScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidEnterFullScreen);
|
|
}
|
|
}
|
|
- (void)windowMaximise:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowMaximise) ) {
|
|
processWindowEvent(self.windowId, EventWindowMaximise);
|
|
}
|
|
}
|
|
- (void)windowUnMaximise:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowUnMaximise) ) {
|
|
processWindowEvent(self.windowId, EventWindowUnMaximise);
|
|
}
|
|
}
|
|
- (void)windowDidEnterVersionBrowser:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidEnterVersionBrowser) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidEnterVersionBrowser);
|
|
}
|
|
}
|
|
- (void)windowDidExitFullScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidExitFullScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidExitFullScreen);
|
|
}
|
|
}
|
|
- (void)windowDidExitVersionBrowser:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidExitVersionBrowser) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidExitVersionBrowser);
|
|
}
|
|
}
|
|
- (void)windowDidExpose:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidExpose) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidExpose);
|
|
}
|
|
}
|
|
- (void)windowDidFocus:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidFocus) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidFocus);
|
|
}
|
|
}
|
|
- (void)windowDidMiniaturize:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidMiniaturize) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidMiniaturize);
|
|
}
|
|
}
|
|
- (void)windowDidMove:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidMove) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidMove);
|
|
}
|
|
}
|
|
- (void)windowDidOrderOffScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidOrderOffScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidOrderOffScreen);
|
|
}
|
|
}
|
|
- (void)windowDidOrderOnScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidOrderOnScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidOrderOnScreen);
|
|
}
|
|
}
|
|
- (void)windowDidResignKey:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidResignKey) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidResignKey);
|
|
}
|
|
}
|
|
- (void)windowDidResignMain:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidResignMain) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidResignMain);
|
|
}
|
|
}
|
|
- (void)windowDidResize:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidResize) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidResize);
|
|
}
|
|
}
|
|
- (void)windowDidUpdate:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdate) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdate);
|
|
}
|
|
}
|
|
- (void)windowDidUpdateAlpha:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdateAlpha) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdateAlpha);
|
|
}
|
|
}
|
|
- (void)windowDidUpdateCollectionBehavior:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdateCollectionBehavior) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdateCollectionBehavior);
|
|
}
|
|
}
|
|
- (void)windowDidUpdateCollectionProperties:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdateCollectionProperties) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdateCollectionProperties);
|
|
}
|
|
}
|
|
- (void)windowDidUpdateShadow:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdateShadow) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdateShadow);
|
|
}
|
|
}
|
|
- (void)windowDidUpdateTitle:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdateTitle) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdateTitle);
|
|
}
|
|
}
|
|
- (void)windowDidUpdateToolbar:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowDidUpdateToolbar) ) {
|
|
processWindowEvent(self.windowId, EventWindowDidUpdateToolbar);
|
|
}
|
|
}
|
|
- (void)windowWillBecomeKey:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillBecomeKey) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillBecomeKey);
|
|
}
|
|
}
|
|
- (void)windowWillBecomeMain:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillBecomeMain) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillBecomeMain);
|
|
}
|
|
}
|
|
- (void)windowWillBeginSheet:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillBeginSheet) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillBeginSheet);
|
|
}
|
|
}
|
|
- (void)windowWillChangeOrderingMode:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillChangeOrderingMode) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillChangeOrderingMode);
|
|
}
|
|
}
|
|
- (void)windowWillClose:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillClose) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillClose);
|
|
}
|
|
}
|
|
- (void)windowWillDeminiaturize:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillDeminiaturize) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillDeminiaturize);
|
|
}
|
|
}
|
|
- (void)windowWillEnterFullScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillEnterFullScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillEnterFullScreen);
|
|
}
|
|
}
|
|
- (void)windowWillEnterVersionBrowser:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillEnterVersionBrowser) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillEnterVersionBrowser);
|
|
}
|
|
}
|
|
- (void)windowWillExitFullScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillExitFullScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillExitFullScreen);
|
|
}
|
|
}
|
|
- (void)windowWillExitVersionBrowser:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillExitVersionBrowser) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillExitVersionBrowser);
|
|
}
|
|
}
|
|
- (void)windowWillFocus:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillFocus) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillFocus);
|
|
}
|
|
}
|
|
- (void)windowWillMiniaturize:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillMiniaturize) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillMiniaturize);
|
|
}
|
|
}
|
|
- (void)windowWillMove:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillMove) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillMove);
|
|
}
|
|
}
|
|
- (void)windowWillOrderOffScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillOrderOffScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillOrderOffScreen);
|
|
}
|
|
}
|
|
- (void)windowWillOrderOnScreen:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillOrderOnScreen) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillOrderOnScreen);
|
|
}
|
|
}
|
|
- (void)windowWillResignMain:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillResignMain) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillResignMain);
|
|
}
|
|
}
|
|
- (void)windowWillResize:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillResize) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillResize);
|
|
}
|
|
}
|
|
- (void)windowWillUnfocus:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUnfocus) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUnfocus);
|
|
}
|
|
}
|
|
- (void)windowWillUpdate:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdate) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdate);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateAlpha:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateAlpha) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateAlpha);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateCollectionBehavior:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateCollectionBehavior) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateCollectionBehavior);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateCollectionProperties:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateCollectionProperties) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateCollectionProperties);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateShadow:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateShadow) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateShadow);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateTitle:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateTitle) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateTitle);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateToolbar:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateToolbar) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateToolbar);
|
|
}
|
|
}
|
|
- (void)windowWillUpdateVisibility:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUpdateVisibility) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUpdateVisibility);
|
|
}
|
|
}
|
|
- (void)windowWillUseStandardFrame:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowWillUseStandardFrame) ) {
|
|
processWindowEvent(self.windowId, EventWindowWillUseStandardFrame);
|
|
}
|
|
}
|
|
- (void)windowFileDraggingEntered:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowFileDraggingEntered) ) {
|
|
processWindowEvent(self.windowId, EventWindowFileDraggingEntered);
|
|
}
|
|
}
|
|
- (void)windowFileDraggingPerformed:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowFileDraggingPerformed) ) {
|
|
processWindowEvent(self.windowId, EventWindowFileDraggingPerformed);
|
|
}
|
|
}
|
|
- (void)windowFileDraggingExited:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowFileDraggingExited) ) {
|
|
processWindowEvent(self.windowId, EventWindowFileDraggingExited);
|
|
}
|
|
}
|
|
- (void)windowShow:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowShow) ) {
|
|
processWindowEvent(self.windowId, EventWindowShow);
|
|
}
|
|
}
|
|
- (void)windowHide:(NSNotification *)notification {
|
|
if( hasListeners(EventWindowHide) ) {
|
|
processWindowEvent(self.windowId, EventWindowHide);
|
|
}
|
|
}
|
|
- (void)webView:(nonnull WKWebView *)webview didStartProvisionalNavigation:(WKNavigation *)navigation {
|
|
if( hasListeners(EventWebViewDidStartProvisionalNavigation) ) {
|
|
processWindowEvent(self.windowId, EventWebViewDidStartProvisionalNavigation);
|
|
}
|
|
}
|
|
- (void)webView:(nonnull WKWebView *)webview didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
|
|
if( hasListeners(EventWebViewDidReceiveServerRedirectForProvisionalNavigation) ) {
|
|
processWindowEvent(self.windowId, EventWebViewDidReceiveServerRedirectForProvisionalNavigation);
|
|
}
|
|
}
|
|
- (void)webView:(nonnull WKWebView *)webview didFinishNavigation:(WKNavigation *)navigation {
|
|
if( hasListeners(EventWebViewDidFinishNavigation) ) {
|
|
processWindowEvent(self.windowId, EventWebViewDidFinishNavigation);
|
|
}
|
|
}
|
|
- (void)webView:(nonnull WKWebView *)webview didCommitNavigation:(WKNavigation *)navigation {
|
|
if( hasListeners(EventWebViewDidCommitNavigation) ) {
|
|
processWindowEvent(self.windowId, EventWebViewDidCommitNavigation);
|
|
}
|
|
}
|
|
// GENERATED EVENTS END
|
|
// WKUIDelegate - Handle file input element clicks
|
|
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters
|
|
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * URLs))completionHandler {
|
|
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
|
openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;
|
|
if (@available(macOS 10.14, *)) {
|
|
openPanel.canChooseDirectories = parameters.allowsDirectories;
|
|
}
|
|
[openPanel beginSheetModalForWindow:webView.window
|
|
completionHandler:^(NSInteger result) {
|
|
if (result == NSModalResponseOK)
|
|
completionHandler(openPanel.URLs);
|
|
else
|
|
completionHandler(nil);
|
|
}];
|
|
}
|
|
@end
|
|
void windowSetScreen(void* window, void* screen, int yOffset) {
|
|
WebviewWindow* nsWindow = (WebviewWindow*)window;
|
|
NSScreen* nsScreen = (NSScreen*)screen;
|
|
// Get current frame
|
|
NSRect frame = [nsWindow frame];
|
|
// Convert frame to screen coordinates
|
|
NSRect screenFrame = [nsScreen frame];
|
|
NSRect currentScreenFrame = [[nsWindow screen] frame];
|
|
// Calculate the menubar height for the target screen
|
|
NSRect visibleFrame = [nsScreen visibleFrame];
|
|
CGFloat menubarHeight = screenFrame.size.height - visibleFrame.size.height;
|
|
// Calculate the distance from the top of the current screen
|
|
CGFloat topOffset = currentScreenFrame.origin.y + currentScreenFrame.size.height - frame.origin.y;
|
|
// Position relative to new screen's top, accounting for menubar
|
|
frame.origin.x = screenFrame.origin.x + (frame.origin.x - currentScreenFrame.origin.x);
|
|
frame.origin.y = screenFrame.origin.y + screenFrame.size.height - topOffset - menubarHeight - yOffset;
|
|
// Set the frame which moves the window to the new screen
|
|
[nsWindow setFrame:frame display:YES];
|
|
}
|
|
// Check if Liquid Glass is supported on this system
|
|
bool isLiquidGlassSupported() {
|
|
// Check for macOS 26.0+ and NSGlassEffectView availability
|
|
if (@available(macOS 26.0, *)) {
|
|
return NSClassFromString(@"NSGlassEffectView") != nil;
|
|
}
|
|
return false;
|
|
}
|
|
// Remove any existing visual effects from the window
|
|
void windowRemoveVisualEffects(void* nsWindow) {
|
|
WebviewWindow* window = (WebviewWindow*)nsWindow;
|
|
NSView* contentView = [window contentView];
|
|
// Get NSGlassEffectView class if available (avoid hard reference)
|
|
Class glassEffectViewClass = nil;
|
|
if (@available(macOS 26.0, *)) {
|
|
glassEffectViewClass = NSClassFromString(@"NSGlassEffectView");
|
|
}
|
|
// Remove all NSVisualEffectView and NSGlassEffectView subviews
|
|
NSArray* subviews = [contentView subviews];
|
|
for (NSView* subview in subviews) {
|
|
if ([subview isKindOfClass:[NSVisualEffectView class]] ||
|
|
(glassEffectViewClass && [subview isKindOfClass:glassEffectViewClass])) {
|
|
[subview removeFromSuperview];
|
|
}
|
|
}
|
|
}
|
|
// Configure WebView for liquid glass effect
|
|
void configureWebViewForLiquidGlass(void* nsWindow) {
|
|
WebviewWindow* window = (WebviewWindow*)nsWindow;
|
|
WKWebView* webView = window.webView;
|
|
// Make WebView background transparent
|
|
[webView setValue:@NO forKey:@"drawsBackground"];
|
|
if (@available(macOS 10.12, *)) {
|
|
[webView setValue:[NSColor clearColor] forKey:@"backgroundColor"];
|
|
}
|
|
// Ensure WebView is above glass layer
|
|
if (webView.layer) {
|
|
webView.layer.zPosition = 1.0;
|
|
webView.layer.shouldRasterize = YES;
|
|
webView.layer.rasterizationScale = [[NSScreen mainScreen] backingScaleFactor];
|
|
}
|
|
}
|
|
// Apply Liquid Glass effect to window
|
|
void windowSetLiquidGlass(void* nsWindow, int style, int material, double cornerRadius,
|
|
int r, int g, int b, int a,
|
|
const char* groupID, double groupSpacing) {
|
|
WebviewWindow* window = (WebviewWindow*)nsWindow;
|
|
// Ensure we're on the main thread for UI operations
|
|
if (![NSThread isMainThread]) {
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
windowSetLiquidGlass(nsWindow, style, material, cornerRadius, r, g, b, a, groupID, groupSpacing);
|
|
});
|
|
return;
|
|
}
|
|
// Remove any existing visual effects
|
|
windowRemoveVisualEffects(nsWindow);
|
|
// Try to use NSGlassEffectView if available
|
|
NSView* glassView = nil;
|
|
if (@available(macOS 26.0, *)) {
|
|
Class NSGlassEffectViewClass = NSClassFromString(@"NSGlassEffectView");
|
|
if (NSGlassEffectViewClass) {
|
|
// Create NSGlassEffectView (autoreleased)
|
|
glassView = [[[NSGlassEffectViewClass alloc] init] autorelease];
|
|
// Set corner radius if the property exists
|
|
if (cornerRadius > 0 && [glassView respondsToSelector:@selector(setCornerRadius:)]) {
|
|
[glassView setValue:@(cornerRadius) forKey:@"cornerRadius"];
|
|
}
|
|
// Set tint color if the property exists and color is specified
|
|
if (a > 0 && [glassView respondsToSelector:@selector(setTintColor:)]) {
|
|
NSColor* tintColor = [NSColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a/255.0];
|
|
// Use performSelector to safely set tintColor if the setter exists
|
|
[glassView performSelector:@selector(setTintColor:) withObject:tintColor];
|
|
}
|
|
// Set style if the property exists
|
|
if ([glassView respondsToSelector:@selector(setStyle:)]) {
|
|
// For vibrant style, try to use Light style for a lighter effect
|
|
int lightStyle = (style == LiquidGlassStyleVibrant) ? LiquidGlassStyleLight : style;
|
|
[glassView setValue:@(lightStyle) forKey:@"style"];
|
|
}
|
|
// Set group identifier if the property exists and groupID is specified
|
|
if (groupID && strlen(groupID) > 0) {
|
|
if ([glassView respondsToSelector:@selector(setGroupIdentifier:)]) {
|
|
NSString* groupIDString = [NSString stringWithUTF8String:groupID];
|
|
[glassView performSelector:@selector(setGroupIdentifier:) withObject:groupIDString];
|
|
} else if ([glassView respondsToSelector:@selector(setGroupName:)]) {
|
|
NSString* groupIDString = [NSString stringWithUTF8String:groupID];
|
|
[glassView performSelector:@selector(setGroupName:) withObject:groupIDString];
|
|
}
|
|
}
|
|
// Set group spacing if the property exists and spacing is specified
|
|
if (groupSpacing > 0 && [glassView respondsToSelector:@selector(setGroupSpacing:)]) {
|
|
[glassView setValue:@(groupSpacing) forKey:@"groupSpacing"];
|
|
}
|
|
}
|
|
}
|
|
// Fallback to NSVisualEffectView if NSGlassEffectView is not available
|
|
if (!glassView) {
|
|
NSVisualEffectView* effectView = [[[NSVisualEffectView alloc] init] autorelease];
|
|
glassView = effectView; // Use effectView as glassView for the rest of the function
|
|
// If a custom material is specified, use it directly
|
|
if (material >= 0) {
|
|
[effectView setMaterial:(NSVisualEffectMaterial)material];
|
|
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
|
} else {
|
|
// Configure the visual effect based on style
|
|
switch(style) {
|
|
case 1: // Light
|
|
if (@available(macOS 15.0, *)) {
|
|
[effectView setMaterial:NSVisualEffectMaterialUnderPageBackground];
|
|
} else if (@available(macOS 10.14, *)) {
|
|
[effectView setMaterial:NSVisualEffectMaterialHUDWindow];
|
|
} else {
|
|
[effectView setMaterial:NSVisualEffectMaterialLight];
|
|
}
|
|
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
|
break;
|
|
case 2: // Dark
|
|
if (@available(macOS 15.0, *)) {
|
|
[effectView setMaterial:NSVisualEffectMaterialHeaderView];
|
|
} else if (@available(macOS 10.14, *)) {
|
|
[effectView setMaterial:NSVisualEffectMaterialFullScreenUI];
|
|
} else {
|
|
[effectView setMaterial:NSVisualEffectMaterialDark];
|
|
}
|
|
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
|
break;
|
|
case 3: // Vibrant
|
|
if (@available(macOS 11.0, *)) {
|
|
// Use the lightest material available - similar to dock
|
|
[effectView setMaterial:NSVisualEffectMaterialHUDWindow];
|
|
} else if (@available(macOS 10.14, *)) {
|
|
[effectView setMaterial:NSVisualEffectMaterialSheet];
|
|
} else {
|
|
[effectView setMaterial:NSVisualEffectMaterialLight];
|
|
}
|
|
// Use behind window for true transparency
|
|
[effectView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
|
break;
|
|
default: // Automatic
|
|
if (@available(macOS 10.14, *)) {
|
|
// Use content background for lighter automatic effect
|
|
[effectView setMaterial:NSVisualEffectMaterialContentBackground];
|
|
} else {
|
|
[effectView setMaterial:NSVisualEffectMaterialAppearanceBased];
|
|
}
|
|
[effectView setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
|
|
break;
|
|
}
|
|
}
|
|
// Use followsWindowActiveState for automatic adjustment
|
|
[effectView setState:NSVisualEffectStateFollowsWindowActiveState];
|
|
// Don't emphasize - it makes the effect too dark
|
|
if (@available(macOS 10.12, *)) {
|
|
[effectView setEmphasized:NO];
|
|
}
|
|
// Apply corner radius if specified
|
|
if (cornerRadius > 0) {
|
|
[effectView setWantsLayer:YES];
|
|
effectView.layer.cornerRadius = cornerRadius;
|
|
effectView.layer.masksToBounds = YES;
|
|
}
|
|
}
|
|
// Get the content view
|
|
NSView* contentView = [window contentView];
|
|
// Set up the glass view
|
|
[glassView setFrame:contentView.bounds];
|
|
[glassView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
|
// Check if this is a real NSGlassEffectView with contentView property
|
|
BOOL hasContentView = [glassView respondsToSelector:@selector(contentView)];
|
|
if (hasContentView) {
|
|
// NSGlassEffectView: Add it to window and webView goes in its contentView
|
|
[contentView addSubview:glassView positioned:NSWindowBelow relativeTo:nil];
|
|
// Safely reparent the webView to the glass view's contentView
|
|
WKWebView* webView = window.webView;
|
|
NSView* glassContentView = [glassView valueForKey:@"contentView"];
|
|
// Only proceed if both webView and glassContentView are non-nil
|
|
if (webView && glassContentView) {
|
|
// Always remove from current superview to avoid exceptions
|
|
[webView removeFromSuperview];
|
|
// Add to the glass view's contentView
|
|
[glassContentView addSubview:webView];
|
|
[webView setFrame:glassContentView.bounds];
|
|
[webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
|
}
|
|
} else {
|
|
// NSVisualEffectView: Add glass as bottom layer, webView on top
|
|
[contentView addSubview:glassView positioned:NSWindowBelow relativeTo:nil];
|
|
WKWebView* webView = window.webView;
|
|
if (webView) {
|
|
[webView removeFromSuperview];
|
|
[contentView addSubview:webView positioned:NSWindowAbove relativeTo:glassView];
|
|
}
|
|
}
|
|
// Configure WebView for liquid glass
|
|
configureWebViewForLiquidGlass(nsWindow);
|
|
// Make window transparent
|
|
[window setOpaque:NO];
|
|
[window setBackgroundColor:[NSColor clearColor]];
|
|
}
|