-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathwebview_darwin.go
More file actions
269 lines (231 loc) · 8.38 KB
/
webview_darwin.go
File metadata and controls
269 lines (231 loc) · 8.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//go:build darwin
package main
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework WebKit
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#include <stdlib.h>
// ─── Window delegate: hide on close (don't destroy) ───
@interface BridgeWindowDelegate : NSObject <NSWindowDelegate>
@end
@implementation BridgeWindowDelegate
- (BOOL)windowShouldClose:(NSWindow *)sender {
// Hide the window instead of closing it
[sender orderOut:nil];
// Switch back to menu-bar-only mode (no dock icon)
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyAccessory];
return NO;
}
@end
// ─── WKUIDelegate: handle JS alert(), confirm(), prompt() in WKWebView ───
@interface BridgeUIDelegate : NSObject <WKUIDelegate>
@end
@implementation BridgeUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:message];
[alert addButtonWithTitle:@"OK"];
[alert runModal];
completionHandler();
}
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message
initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:message];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancelar"];
NSModalResponse response = [alert runModal];
completionHandler(response == NSAlertFirstButtonReturn);
}
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame
completionHandler:(void (^)(NSString *))completionHandler {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:prompt];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancelar"];
NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
[input setStringValue:defaultText ?: @""];
[alert setAccessoryView:input];
NSModalResponse response = [alert runModal];
if (response == NSAlertFirstButtonReturn) {
completionHandler([input stringValue]);
} else {
completionHandler(nil);
}
}
@end
static NSWindow *bridgeWindow = nil;
static WKWebView *bridgeWebView = nil;
static BridgeWindowDelegate *bridgeDelegate = nil;
static BridgeUIDelegate *bridgeUIDelegate = nil;
// nativeCreateWindow creates an NSWindow with WKWebView and shows it.
// Safe to call from any thread — dispatches to main queue.
void nativeCreateWindow(const char* title, const char* url, int width, int height) {
NSString *nsTitle = [NSString stringWithUTF8String:title];
NSString *nsURL = [NSString stringWithUTF8String:url];
dispatch_async(dispatch_get_main_queue(), ^{
if (bridgeWindow) {
// Window exists — just navigate and show
NSURL *u = [NSURL URLWithString:nsURL];
[bridgeWebView loadRequest:[NSURLRequest requestWithURL:u]];
[bridgeWindow setTitle:nsTitle];
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
[bridgeWindow makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
return;
}
NSRect frame = NSMakeRect(0, 0, width, height);
NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
bridgeWindow = [[NSWindow alloc] initWithContentRect:frame
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[bridgeWindow setTitle:nsTitle];
[bridgeWindow center];
[bridgeWindow setReleasedWhenClosed:NO];
// Set delegate to handle close button (hide instead of destroy)
bridgeDelegate = [[BridgeWindowDelegate alloc] init];
[bridgeWindow setDelegate:bridgeDelegate];
// Create WKWebView with UI delegate for JS dialogs (alert, confirm, prompt)
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
bridgeWebView = [[WKWebView alloc] initWithFrame:frame configuration:config];
[bridgeWebView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
bridgeUIDelegate = [[BridgeUIDelegate alloc] init];
[bridgeWebView setUIDelegate:bridgeUIDelegate];
[bridgeWindow setContentView:bridgeWebView];
// Navigate
NSURL *u = [NSURL URLWithString:nsURL];
[bridgeWebView loadRequest:[NSURLRequest requestWithURL:u]];
// Show window and activate app (shows dock icon)
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
[bridgeWindow makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
});
}
// nativeShowWindow re-shows a hidden window and optionally navigates.
void nativeShowWindow(const char* url) {
NSString *nsURL = url ? [NSString stringWithUTF8String:url] : nil;
dispatch_async(dispatch_get_main_queue(), ^{
if (!bridgeWindow) return;
if (nsURL) {
NSURL *u = [NSURL URLWithString:nsURL];
[bridgeWebView loadRequest:[NSURLRequest requestWithURL:u]];
}
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
[bridgeWindow makeKeyAndOrderFront:nil];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
});
}
// nativeSetWindowTitle updates the window title.
void nativeSetWindowTitle(const char* title) {
NSString *nsTitle = [NSString stringWithUTF8String:title];
dispatch_async(dispatch_get_main_queue(), ^{
if (bridgeWindow) {
[bridgeWindow setTitle:nsTitle];
}
});
}
// nativeDestroyWindow closes and releases the window.
void nativeDestroyWindow() {
dispatch_async(dispatch_get_main_queue(), ^{
if (bridgeWindow) {
[bridgeWindow setDelegate:nil];
[bridgeWindow close];
bridgeWindow = nil;
bridgeWebView = nil;
bridgeDelegate = nil;
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyAccessory];
}
});
}
// nativeIsWindowVisible returns 1 if the window exists and is visible.
int nativeIsWindowVisible() {
if (bridgeWindow && [bridgeWindow isVisible]) return 1;
return 0;
}
// nativeIsWindowCreated returns 1 if the window has been created (visible or hidden).
int nativeIsWindowCreated() {
return bridgeWindow != nil ? 1 : 0;
}
*/
import "C"
import (
"fmt"
"log"
"sync"
"unsafe"
)
var (
wvMu sync.Mutex
wvDashboardURL string
wvCreated bool
)
// initWebview stores the dashboard URL. On macOS, the native window is created
// lazily in showDashboard() AFTER systray.Run() starts the Cocoa event loop.
// This avoids the NSApplication conflict between webview_go and systray.
func initWebview(dashURL string) {
wvMu.Lock()
defer wvMu.Unlock()
wvDashboardURL = dashURL
log.Printf("[webview] macOS native mode — window will be created on first show")
}
// showDashboard creates or shows the native WKWebView window.
// Creates the window on first call, re-shows on subsequent calls.
func showDashboard(dashURL string) {
wvMu.Lock()
url := dashURL
if url == "" {
url = wvDashboardURL
}
created := wvCreated
wvMu.Unlock()
if url == "" {
return
}
cfg := getConfig()
title := "TSC Bridge"
if cfg.Whitelabel.Name != "" {
title = fmt.Sprintf("TSC Bridge — %s", cfg.Whitelabel.Name)
}
cTitle := C.CString(title)
cURL := C.CString(url)
defer C.free(unsafe.Pointer(cTitle))
defer C.free(unsafe.Pointer(cURL))
if !created {
log.Printf("[webview] creating native window: %s", url)
C.nativeCreateWindow(cTitle, cURL, 1100, 750)
wvMu.Lock()
wvCreated = true
wvMu.Unlock()
} else {
log.Printf("[webview] showing native window: %s", url)
C.nativeShowWindow(cURL)
}
}
// setWebviewTitle updates the window title with whitelabel branding.
func setWebviewTitle() {
cfg := getConfig()
title := "TSC Bridge"
if cfg.Whitelabel.Name != "" {
title = fmt.Sprintf("TSC Bridge — %s", cfg.Whitelabel.Name)
}
cTitle := C.CString(title)
defer C.free(unsafe.Pointer(cTitle))
C.nativeSetWindowTitle(cTitle)
}
// isWebviewActive reports whether the native window is visible.
func isWebviewActive() bool {
return C.nativeIsWindowCreated() == 1
}
// destroyWebview tears down the native window.
func destroyWebview() {
log.Printf("[webview] destroying native window")
C.nativeDestroyWindow()
wvMu.Lock()
wvCreated = false
wvMu.Unlock()
}