mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
feat: screenshot/capture a web content view
This commit is contained in:
parent
9bccd1b578
commit
e5806d32bc
8 changed files with 121 additions and 11 deletions
22
v3/pkg/webcontentsview/snapshot.go
Normal file
22
v3/pkg/webcontentsview/snapshot.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package webcontentsview
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var snapshotCallbacks sync.Map
|
||||
var snapshotCallbackID uintptr
|
||||
|
||||
func registerSnapshotCallback(ch chan string) uintptr {
|
||||
id := atomic.AddUintptr(&snapshotCallbackID, 1)
|
||||
snapshotCallbacks.Store(id, ch)
|
||||
return id
|
||||
}
|
||||
|
||||
func dispatchSnapshotResult(id uintptr, data string) {
|
||||
if ch, ok := snapshotCallbacks.Load(id); ok {
|
||||
ch.(chan string) <- data
|
||||
snapshotCallbacks.Delete(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,10 @@ func (v *WebContentsView) SetURL(url string) {
|
|||
}
|
||||
|
||||
// ExecJS executes the given javascript in the WebContentsView.
|
||||
func (v *WebContentsView) ExecJS(js string) {
|
||||
v.impl.execJS(js)
|
||||
}
|
||||
|
||||
// GoBack navigates to the previous page in history.
|
||||
func (v *WebContentsView) GoBack() {
|
||||
v.impl.goBack()
|
||||
|
|
@ -55,8 +59,9 @@ func (v *WebContentsView) GetURL() string {
|
|||
return v.impl.getURL()
|
||||
}
|
||||
|
||||
func (v *WebContentsView) ExecJS(js string) {
|
||||
v.impl.execJS(js)
|
||||
// TakeSnapshot returns a base64 encoded PNG of the current view.
|
||||
func (v *WebContentsView) TakeSnapshot() string {
|
||||
return v.impl.takeSnapshot()
|
||||
}
|
||||
|
||||
// Attach binds the WebContentsView to a Wails Window.
|
||||
|
|
@ -76,7 +81,7 @@ type webContentsViewImpl interface {
|
|||
execJS(js string)
|
||||
goBack()
|
||||
getURL() string
|
||||
|
||||
takeSnapshot() string
|
||||
attach(window application.Window)
|
||||
detach()
|
||||
nativeView() unsafe.Pointer
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ func (w *androidWebContentsView) setURL(url string) {}
|
|||
func (w *androidWebContentsView) execJS(js string) {}
|
||||
func (w *androidWebContentsView) goBack() {}
|
||||
func (w *androidWebContentsView) getURL() string { return "" }
|
||||
func (w *androidWebContentsView) takeSnapshot() string { return "" }
|
||||
|
||||
|
||||
func (w *androidWebContentsView) attach(window application.Window) {}
|
||||
func (w *androidWebContentsView) detach() {}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,15 @@ func (w *macosWebContentsView) goBack() {
|
|||
C.webContentsViewGoBack(w.nsView)
|
||||
}
|
||||
|
||||
func (w *macosWebContentsView) takeSnapshot() string {
|
||||
ch := make(chan string, 1)
|
||||
id := registerSnapshotCallback(ch)
|
||||
application.InvokeSync(func() {
|
||||
C.webContentsViewTakeSnapshot(w.nsView, C.uintptr_t(id))
|
||||
})
|
||||
return <-ch
|
||||
}
|
||||
|
||||
func (w *macosWebContentsView) getURL() string {
|
||||
cUrl := C.webContentsViewGetURL(w.nsView)
|
||||
if cUrl == nil {
|
||||
|
|
@ -110,3 +119,12 @@ func (w *macosWebContentsView) detach() {
|
|||
func (w *macosWebContentsView) nativeView() unsafe.Pointer {
|
||||
return w.nsView
|
||||
}
|
||||
//export browserViewSnapshotCallback
|
||||
func browserViewSnapshotCallback(callbackID C.uintptr_t, base64 *C.char) {
|
||||
id := uintptr(callbackID)
|
||||
str := ""
|
||||
if base64 != nil {
|
||||
str = C.GoString(base64)
|
||||
}
|
||||
dispatchSnapshotResult(id, str)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ extern void* createWebContentsView(int x, int y, int w, int h, WebContentsViewPr
|
|||
extern void webContentsViewSetBounds(void* view, int x, int y, int w, int h);
|
||||
extern void webContentsViewSetURL(void* view, const char* url);
|
||||
extern void webContentsViewExecJS(void* view, const char* js);
|
||||
extern void webContentsViewGoBack(void* view);
|
||||
extern const char* webContentsViewGetURL(void* view);
|
||||
|
||||
extern void windowAddWebContentsView(void* nsWindow, void* view);
|
||||
extern void windowRemoveWebContentsView(void* nsWindow, void* view);
|
||||
|
||||
// Async snapshot
|
||||
extern void webContentsViewTakeSnapshot(void* view, uintptr_t callbackID);
|
||||
|
||||
#endif /* webcontentsview_darwin_h */
|
||||
extern void webContentsViewGoBack(void* view);
|
||||
extern const char* webContentsViewGetURL(void* view);
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ void* createWebContentsView(int x, int y, int w, int h, WebContentsViewPreferenc
|
|||
|
||||
WKWebView* webView = [[WKWebView alloc] initWithFrame:frame configuration:config];
|
||||
[webView setValue:@(NO) forKey:@"drawsTransparentBackground"];
|
||||
|
||||
if (prefs.userAgent != NULL) {
|
||||
NSString* customUA = [NSString stringWithUTF8String:prefs.userAgent];
|
||||
[webView setCustomUserAgent:customUA];
|
||||
}
|
||||
|
||||
|
||||
return webView;
|
||||
}
|
||||
|
|
@ -44,7 +44,6 @@ void webContentsViewSetBounds(void* view, int x, int y, int w, int h) {
|
|||
NSView* superview = [webView superview];
|
||||
|
||||
if (superview != nil) {
|
||||
// macOS standard coordinates: 0,0 is bottom-left
|
||||
CGFloat superHeight = superview.bounds.size.height;
|
||||
CGFloat cocoaY = superHeight - y - h;
|
||||
[webView setFrame:NSMakeRect(x, cocoaY, w, h)];
|
||||
|
|
@ -73,11 +72,7 @@ void webContentsViewExecJS(void* view, const char* js) {
|
|||
void windowAddWebContentsView(void* nsWindow, void* view) {
|
||||
NSWindow* window = (NSWindow*)nsWindow;
|
||||
WKWebView* webView = (WKWebView*)view;
|
||||
|
||||
// Add directly to the window's root view so we avoid clipping issues
|
||||
[window.contentView addSubview:webView];
|
||||
|
||||
// Force to front by giving it a high zPosition
|
||||
[webView setWantsLayer:YES];
|
||||
webView.layer.zPosition = 9999.0;
|
||||
}
|
||||
|
|
@ -113,3 +108,61 @@ const char* webContentsViewGetURL(void* view) {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
extern void browserViewSnapshotCallback(uintptr_t callbackID, const char* base64);
|
||||
|
||||
void webContentsViewTakeSnapshot(void* view, uintptr_t callbackID) {
|
||||
WKWebView* webView = (WKWebView*)view;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
@try {
|
||||
if (@available(macOS 10.13, *)) {
|
||||
WKSnapshotConfiguration *config = [[WKSnapshotConfiguration alloc] init];
|
||||
[webView takeSnapshotWithConfiguration:config completionHandler:^(NSImage *image, NSError *error) {
|
||||
if (error != nil || image == nil) {
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
@try {
|
||||
CGImageRef cgRef = [image CGImageForProposedRect:NULL context:nil hints:nil];
|
||||
if (cgRef == NULL) {
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
|
||||
if (newRep == nil) {
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
[newRep setSize:[image size]];
|
||||
NSData *pngData = [newRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
|
||||
|
||||
if (pngData == nil) {
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *base64String = [pngData base64EncodedStringWithOptions:0];
|
||||
if (base64String == nil) {
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *fullBase64 = [NSString stringWithFormat:@"data:image/png;base64,%@", base64String];
|
||||
browserViewSnapshotCallback(callbackID, [fullBase64 UTF8String]);
|
||||
} @catch (NSException *innerException) {
|
||||
NSLog(@"Error processing snapshot image: %@", innerException.reason);
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
NSLog(@"Exception in snapshot configuration: %@", e.reason);
|
||||
browserViewSnapshotCallback(callbackID, NULL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ func (w *iosWebContentsView) setURL(url string) {}
|
|||
func (w *iosWebContentsView) execJS(js string) {}
|
||||
func (w *iosWebContentsView) goBack() {}
|
||||
func (w *iosWebContentsView) getURL() string { return "" }
|
||||
func (w *iosWebContentsView) takeSnapshot() string { return "" }
|
||||
|
||||
|
||||
func (w *iosWebContentsView) attach(window application.Window) {}
|
||||
func (w *iosWebContentsView) detach() {}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,11 @@ func (w *linuxWebContentsView) goBack() {
|
|||
func (w *linuxWebContentsView) getURL() string {
|
||||
return ""
|
||||
}
|
||||
func (w *linuxWebContentsView) takeSnapshot() string {
|
||||
return ""
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *linuxWebContentsView) execJS(js string) {
|
||||
cJs := C.CString(js)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue