fit-webview-bridge 0.2.0a2__tar.gz → 0.2.1a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fit-webview-bridge might be problematic. Click here for more details.
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/PKG-INFO +1 -1
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/pyproject.toml +1 -1
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/src/macos/WKWebViewWidget.h +9 -0
- fit_webview_bridge-0.2.1a1/src/macos/WKWebViewWidget.mm +386 -0
- fit_webview_bridge-0.2.0a2/src/macos/WKWebViewWidget.mm +0 -118
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/.github/workflows/wheels-macos.yml +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/.gitignore +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/.vscode/settings.json +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/README.md +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/bindings/pyside6/macos/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/bindings/pyside6/macos/typesystem_wkwebview.xml +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/examples/macos/wkwebview_demo.py +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/fit_webview_bridge/__init__.py +0 -0
- {fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/src/macos/CMakeLists.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fit-webview-bridge"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1a1"
|
|
4
4
|
description = "Qt native WebView bridge with PySide6 bindings"
|
|
5
5
|
requires-python = ">=3.11,<3.14"
|
|
6
6
|
dependencies = ["PySide6==6.9.0", "shiboken6==6.9.0", "shiboken6-generator==6.9.0"]
|
|
@@ -22,6 +22,9 @@ public:
|
|
|
22
22
|
Q_INVOKABLE void reload();
|
|
23
23
|
Q_INVOKABLE void evaluateJavaScript(const QString& script);
|
|
24
24
|
|
|
25
|
+
Q_INVOKABLE void setDownloadDirectory(const QString& dirPath);
|
|
26
|
+
Q_INVOKABLE QString downloadDirectory() const;
|
|
27
|
+
|
|
25
28
|
signals:
|
|
26
29
|
void loadFinished(bool ok);
|
|
27
30
|
void urlChanged(const QUrl& url);
|
|
@@ -30,6 +33,12 @@ signals:
|
|
|
30
33
|
void canGoBackChanged(bool);
|
|
31
34
|
void canGoForwardChanged(bool);
|
|
32
35
|
|
|
36
|
+
|
|
37
|
+
void downloadStarted(const QString& suggestedFilename, const QString& destinationPath);
|
|
38
|
+
void downloadProgress(qint64 bytesReceived, qint64 totalBytes);
|
|
39
|
+
void downloadFinished(const QString& filePath);
|
|
40
|
+
void downloadFailed(const QString& filePath, const QString& error);
|
|
41
|
+
|
|
33
42
|
protected:
|
|
34
43
|
void showEvent(QShowEvent*) override;
|
|
35
44
|
void resizeEvent(QResizeEvent*) override;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#import <Cocoa/Cocoa.h>
|
|
2
|
+
#import <WebKit/WebKit.h>
|
|
3
|
+
|
|
4
|
+
#include "WKWebViewWidget.h"
|
|
5
|
+
|
|
6
|
+
#include <QtWidgets>
|
|
7
|
+
#include <QString>
|
|
8
|
+
#include <QUrl>
|
|
9
|
+
#include <QDir>
|
|
10
|
+
|
|
11
|
+
@class WKNavDelegate;
|
|
12
|
+
@class FitUrlMsgHandler;
|
|
13
|
+
|
|
14
|
+
// =======================
|
|
15
|
+
// Impl
|
|
16
|
+
// =======================
|
|
17
|
+
struct WKWebViewWidget::Impl {
|
|
18
|
+
WKWebView* wk = nil;
|
|
19
|
+
WKNavDelegate* delegate = nil;
|
|
20
|
+
WKUserContentController* ucc = nil;
|
|
21
|
+
FitUrlMsgHandler* msg = nil;
|
|
22
|
+
QString downloadDir; // es. ~/Downloads
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// =======================
|
|
26
|
+
// Helpers forward
|
|
27
|
+
// =======================
|
|
28
|
+
static NSURL* toNSURL(QUrl u);
|
|
29
|
+
|
|
30
|
+
// =======================
|
|
31
|
+
// SPA message handler
|
|
32
|
+
// =======================
|
|
33
|
+
@interface FitUrlMsgHandler : NSObject <WKScriptMessageHandler>
|
|
34
|
+
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@implementation FitUrlMsgHandler
|
|
38
|
+
- (void)userContentController:(WKUserContentController *)userContentController
|
|
39
|
+
didReceiveScriptMessage:(WKScriptMessage *)message {
|
|
40
|
+
if (!self.owner) return;
|
|
41
|
+
if (![message.name isEqualToString:@"fitUrlChanged"]) return;
|
|
42
|
+
if (![message.body isKindOfClass:[NSString class]]) return;
|
|
43
|
+
QString s = QString::fromUtf8([(NSString*)message.body UTF8String]);
|
|
44
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(s.toUtf8()));
|
|
45
|
+
}
|
|
46
|
+
@end
|
|
47
|
+
|
|
48
|
+
// =======================
|
|
49
|
+
// Navigation + Download delegate
|
|
50
|
+
// =======================
|
|
51
|
+
@interface WKNavDelegate : NSObject <WKNavigationDelegate, WKDownloadDelegate>
|
|
52
|
+
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
53
|
+
// stato download (nel delegate, non toccare i privati del widget)
|
|
54
|
+
@property(nonatomic, strong) NSMapTable<WKDownload*, NSString*>* downloadPaths;
|
|
55
|
+
@end
|
|
56
|
+
|
|
57
|
+
@implementation WKNavDelegate
|
|
58
|
+
|
|
59
|
+
- (instancetype)init {
|
|
60
|
+
if ((self = [super init])) {
|
|
61
|
+
_downloadPaths = [NSMapTable weakToStrongObjectsMapTable];
|
|
62
|
+
}
|
|
63
|
+
return self;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#pragma mark - Navigazione
|
|
67
|
+
|
|
68
|
+
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
|
|
69
|
+
if (!self.owner) return;
|
|
70
|
+
if (webView.URL)
|
|
71
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
72
|
+
emit self.owner->loadProgress(5);
|
|
73
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
74
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
|
|
78
|
+
if (!self.owner) return;
|
|
79
|
+
if (webView.URL)
|
|
80
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
81
|
+
emit self.owner->loadProgress(50);
|
|
82
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
83
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
|
|
87
|
+
if (!self.owner) return;
|
|
88
|
+
if (webView.URL)
|
|
89
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
|
93
|
+
if (!self.owner) return;
|
|
94
|
+
emit self.owner->loadFinished(true);
|
|
95
|
+
if (webView.URL)
|
|
96
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
97
|
+
if (webView.title)
|
|
98
|
+
emit self.owner->titleChanged(QString::fromUtf8(webView.title.UTF8String));
|
|
99
|
+
emit self.owner->loadProgress(100);
|
|
100
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
101
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
|
105
|
+
if (!self.owner) return;
|
|
106
|
+
emit self.owner->loadFinished(false);
|
|
107
|
+
emit self.owner->loadProgress(0);
|
|
108
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
109
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#pragma mark - Decide download vs render
|
|
113
|
+
|
|
114
|
+
- (void)webView:(WKWebView *)webView
|
|
115
|
+
decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
|
|
116
|
+
decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
|
|
117
|
+
{
|
|
118
|
+
if (navigationResponse.canShowMIMEType) {
|
|
119
|
+
decisionHandler(WKNavigationResponsePolicyAllow);
|
|
120
|
+
} else {
|
|
121
|
+
// API moderna: "Download" (non BecomeDownload)
|
|
122
|
+
decisionHandler(WKNavigationResponsePolicyDownload);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#pragma mark - Diventare download
|
|
127
|
+
|
|
128
|
+
- (void)webView:(WKWebView *)webView
|
|
129
|
+
navigationAction:(WKNavigationAction *)navigationAction
|
|
130
|
+
didBecomeDownload:(WKDownload *)download
|
|
131
|
+
{
|
|
132
|
+
download.delegate = self;
|
|
133
|
+
if (self.owner) {
|
|
134
|
+
emit self.owner->downloadStarted(QString(), QString());
|
|
135
|
+
}
|
|
136
|
+
// Progress via NSProgress (best effort): alcuni siti non lo popolano
|
|
137
|
+
[download.progress addObserver:self
|
|
138
|
+
forKeyPath:@"fractionCompleted"
|
|
139
|
+
options:NSKeyValueObservingOptionNew
|
|
140
|
+
context:NULL];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
- (void)webView:(WKWebView *)webView
|
|
144
|
+
navigationResponse:(WKNavigationResponse *)navigationResponse
|
|
145
|
+
didBecomeDownload:(WKDownload *)download
|
|
146
|
+
{
|
|
147
|
+
download.delegate = self;
|
|
148
|
+
|
|
149
|
+
NSString* suggested = navigationResponse.response.suggestedFilename ?: @"download";
|
|
150
|
+
if (self.owner) {
|
|
151
|
+
QString destDir = self.owner->downloadDirectory();
|
|
152
|
+
QString destPath = destDir + "/" + QString::fromUtf8(suggested.UTF8String);
|
|
153
|
+
emit self.owner->downloadStarted(QString::fromUtf8(suggested.UTF8String),
|
|
154
|
+
destPath);
|
|
155
|
+
}
|
|
156
|
+
[download.progress addObserver:self
|
|
157
|
+
forKeyPath:@"fractionCompleted"
|
|
158
|
+
options:NSKeyValueObservingOptionNew
|
|
159
|
+
context:NULL];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#pragma mark - Scegli destinazione
|
|
163
|
+
|
|
164
|
+
static NSString* uniquePath(NSString* baseDir, NSString* filename) {
|
|
165
|
+
NSString* fname = filename ?: @"download";
|
|
166
|
+
NSString* path = [baseDir stringByAppendingPathComponent:fname];
|
|
167
|
+
NSFileManager* fm = [NSFileManager defaultManager];
|
|
168
|
+
if (![fm fileExistsAtPath:path]) return path;
|
|
169
|
+
|
|
170
|
+
NSString* name = [fname stringByDeletingPathExtension];
|
|
171
|
+
NSString* ext = [fname pathExtension];
|
|
172
|
+
for (NSUInteger i = 1; i < 10000; ++i) {
|
|
173
|
+
NSString* cand = ext.length
|
|
174
|
+
? [NSString stringWithFormat:@"%@ (%lu).%@", name, (unsigned long)i, ext]
|
|
175
|
+
: [NSString stringWithFormat:@"%@ (%lu)", name, (unsigned long)i];
|
|
176
|
+
NSString* candPath = [baseDir stringByAppendingPathComponent:cand];
|
|
177
|
+
if (![fm fileExistsAtPath:candPath]) return candPath;
|
|
178
|
+
}
|
|
179
|
+
return path;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
- (void)download:(WKDownload *)download
|
|
183
|
+
decideDestinationUsingResponse:(NSURLResponse *)response
|
|
184
|
+
suggestedFilename:(NSString *)suggestedFilename
|
|
185
|
+
completionHandler:(void (^)(NSURL * _Nullable destination))completionHandler
|
|
186
|
+
{
|
|
187
|
+
if (!self.owner) { completionHandler(nil); return; }
|
|
188
|
+
|
|
189
|
+
QString qdir = self.owner->downloadDirectory();
|
|
190
|
+
NSString* dir = [NSString stringWithUTF8String:qdir.toUtf8().constData()];
|
|
191
|
+
if (!dir.length) {
|
|
192
|
+
dir = [NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:dir
|
|
196
|
+
withIntermediateDirectories:YES
|
|
197
|
+
attributes:nil error:nil];
|
|
198
|
+
|
|
199
|
+
NSString* finalPath = uniquePath(dir, suggestedFilename ?: @"download");
|
|
200
|
+
[self.downloadPaths setObject:finalPath forKey:download];
|
|
201
|
+
|
|
202
|
+
emit self.owner->downloadStarted(
|
|
203
|
+
QString::fromUtf8((suggestedFilename ?: @"download").UTF8String),
|
|
204
|
+
QString::fromUtf8(finalPath.UTF8String)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
completionHandler([NSURL fileURLWithPath:finalPath]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#pragma mark - Progress / Fine / Errore
|
|
211
|
+
|
|
212
|
+
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)obj
|
|
213
|
+
change:(NSDictionary *)change context:(void *)ctx
|
|
214
|
+
{
|
|
215
|
+
if (![keyPath isEqualToString:@"fractionCompleted"]) {
|
|
216
|
+
[super observeValueForKeyPath:keyPath ofObject:obj change:change context:ctx];
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!self.owner) return;
|
|
220
|
+
|
|
221
|
+
NSProgress* prog = (NSProgress*)obj;
|
|
222
|
+
int64_t total = prog.totalUnitCount; // può essere -1 (sconosciuto)
|
|
223
|
+
int64_t done = prog.completedUnitCount;
|
|
224
|
+
emit self.owner->downloadProgress(done, total >= 0 ? total : -1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
- (void)downloadDidFinish:(WKDownload *)download {
|
|
228
|
+
if (!self.owner) return;
|
|
229
|
+
|
|
230
|
+
@try { [download.progress removeObserver:self forKeyPath:@"fractionCompleted"]; } @catch (...) {}
|
|
231
|
+
|
|
232
|
+
NSString* finalPath = [self.downloadPaths objectForKey:download];
|
|
233
|
+
if (finalPath) {
|
|
234
|
+
emit self.owner->downloadFinished(QString::fromUtf8(finalPath.UTF8String));
|
|
235
|
+
[self.downloadPaths removeObjectForKey:download];
|
|
236
|
+
} else {
|
|
237
|
+
emit self.owner->downloadFinished(QString());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
- (void)download:(WKDownload *)download didFailWithError:(NSError *)error resumeData:(NSData *)resumeData {
|
|
242
|
+
if (!self.owner) return;
|
|
243
|
+
|
|
244
|
+
@try { [download.progress removeObserver:self forKeyPath:@"fractionCompleted"]; } @catch (...) {}
|
|
245
|
+
|
|
246
|
+
NSString* finalPath = [self.downloadPaths objectForKey:download];
|
|
247
|
+
QString qpath = finalPath ? QString::fromUtf8(finalPath.UTF8String) : QString();
|
|
248
|
+
emit self.owner->downloadFailed(qpath, QString::fromUtf8(error.localizedDescription.UTF8String));
|
|
249
|
+
if (finalPath) [self.downloadPaths removeObjectForKey:download];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@end
|
|
253
|
+
|
|
254
|
+
// =======================
|
|
255
|
+
// QUrl -> NSURL (normalizza e forza https)
|
|
256
|
+
// =======================
|
|
257
|
+
static NSURL* toNSURL(QUrl u) {
|
|
258
|
+
if (!u.isValid()) return nil;
|
|
259
|
+
|
|
260
|
+
if (u.scheme().isEmpty())
|
|
261
|
+
u = QUrl::fromUserInput(u.toString());
|
|
262
|
+
|
|
263
|
+
// Forza sempre http -> https (nessuna eccezione)
|
|
264
|
+
if (u.scheme() == "http")
|
|
265
|
+
u.setScheme("https");
|
|
266
|
+
|
|
267
|
+
if (u.isLocalFile())
|
|
268
|
+
return [NSURL fileURLWithPath:[NSString stringWithUTF8String:u.toLocalFile().toUtf8().constData()]];
|
|
269
|
+
|
|
270
|
+
const QByteArray enc = u.toString(QUrl::FullyEncoded).toUtf8();
|
|
271
|
+
return [NSURL URLWithString:[NSString stringWithUTF8String:enc.constData()]];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// =======================
|
|
275
|
+
// WKWebViewWidget
|
|
276
|
+
// =======================
|
|
277
|
+
WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
278
|
+
: QWidget(parent), d(new Impl) {
|
|
279
|
+
setAttribute(Qt::WA_NativeWindow, true);
|
|
280
|
+
(void)winId();
|
|
281
|
+
|
|
282
|
+
d->downloadDir = QDir::homePath() + "/Downloads";
|
|
283
|
+
|
|
284
|
+
NSView* nsParent = (__bridge NSView*)reinterpret_cast<void*>(winId());
|
|
285
|
+
WKWebViewConfiguration* cfg = [[WKWebViewConfiguration alloc] init];
|
|
286
|
+
if ([cfg respondsToSelector:@selector(defaultWebpagePreferences)]) {
|
|
287
|
+
cfg.defaultWebpagePreferences.allowsContentJavaScript = YES;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// SPA: intercetta pushState/replaceState/popstate/click
|
|
291
|
+
d->ucc = [WKUserContentController new];
|
|
292
|
+
d->msg = [FitUrlMsgHandler new];
|
|
293
|
+
d->msg.owner = this;
|
|
294
|
+
[d->ucc addScriptMessageHandler:d->msg name:@"fitUrlChanged"];
|
|
295
|
+
|
|
296
|
+
NSString* js =
|
|
297
|
+
@"(function(){"
|
|
298
|
+
@" function emit(){ try{ window.webkit.messageHandlers.fitUrlChanged.postMessage(location.href); }catch(e){} }"
|
|
299
|
+
@" var _ps = history.pushState; history.pushState = function(){ _ps.apply(this, arguments); emit(); };"
|
|
300
|
+
@" var _rs = history.replaceState; history.replaceState = function(){ _rs.apply(this, arguments); emit(); };"
|
|
301
|
+
@" window.addEventListener('popstate', emit, true);"
|
|
302
|
+
@" document.addEventListener('click', function(ev){"
|
|
303
|
+
@" var a = ev.target && ev.target.closest ? ev.target.closest('a[href]') : null;"
|
|
304
|
+
@" if (!a) return; if (a.target === '_blank' || a.hasAttribute('download')) return;"
|
|
305
|
+
@" setTimeout(emit, 0);"
|
|
306
|
+
@" }, true);"
|
|
307
|
+
@"})();";
|
|
308
|
+
|
|
309
|
+
WKUserScript* us = [[WKUserScript alloc]
|
|
310
|
+
initWithSource:js
|
|
311
|
+
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
312
|
+
forMainFrameOnly:YES];
|
|
313
|
+
[d->ucc addUserScript:us];
|
|
314
|
+
cfg.userContentController = d->ucc;
|
|
315
|
+
|
|
316
|
+
d->wk = [[WKWebView alloc] initWithFrame:nsParent.bounds configuration:cfg];
|
|
317
|
+
d->wk.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
318
|
+
[nsParent addSubview:d->wk];
|
|
319
|
+
|
|
320
|
+
d->delegate = [WKNavDelegate new];
|
|
321
|
+
d->delegate.owner = this;
|
|
322
|
+
[d->wk setNavigationDelegate:d->delegate];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
WKWebViewWidget::~WKWebViewWidget() {
|
|
326
|
+
if (!d) return;
|
|
327
|
+
|
|
328
|
+
if (d->ucc && d->msg) {
|
|
329
|
+
@try { [d->ucc removeScriptMessageHandlerForName:@"fitUrlChanged"]; } @catch (...) {}
|
|
330
|
+
}
|
|
331
|
+
d->msg = nil;
|
|
332
|
+
|
|
333
|
+
if (d->wk) { [d->wk removeFromSuperview]; d->wk = nil; }
|
|
334
|
+
d->delegate = nil;
|
|
335
|
+
d->ucc = nil;
|
|
336
|
+
|
|
337
|
+
delete d; d = nullptr;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
void WKWebViewWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); }
|
|
341
|
+
void WKWebViewWidget::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); }
|
|
342
|
+
|
|
343
|
+
QUrl WKWebViewWidget::url() const {
|
|
344
|
+
if (!(d && d->wk)) return QUrl();
|
|
345
|
+
NSURL* nsurl = d->wk.URL;
|
|
346
|
+
if (!nsurl) return QUrl();
|
|
347
|
+
const char* utf8 = nsurl.absoluteString.UTF8String;
|
|
348
|
+
if (!utf8) return QUrl();
|
|
349
|
+
return QUrl::fromEncoded(QByteArray(utf8));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
void WKWebViewWidget::setUrl(const QUrl& u) {
|
|
353
|
+
if (!(d && d->wk)) return;
|
|
354
|
+
NSURL* nsurl = toNSURL(u);
|
|
355
|
+
if (!nsurl) return;
|
|
356
|
+
[d->wk loadRequest:[NSURLRequest requestWithURL:nsurl]];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
void WKWebViewWidget::back() { if (d && d->wk && d->wk.canGoBack) [d->wk goBack]; }
|
|
360
|
+
void WKWebViewWidget::forward() { if (d && d->wk && d->wk.canGoForward) [d->wk goForward]; }
|
|
361
|
+
void WKWebViewWidget::stop() { if (d && d->wk) [d->wk stopLoading:nil]; }
|
|
362
|
+
void WKWebViewWidget::reload() { if (d && d->wk) [d->wk reload]; }
|
|
363
|
+
|
|
364
|
+
void WKWebViewWidget::evaluateJavaScript(const QString& script) {
|
|
365
|
+
if (!d || !d->wk) return;
|
|
366
|
+
NSString* s = [NSString stringWithUTF8String:script.toUtf8().constData()];
|
|
367
|
+
[d->wk evaluateJavaScript:s completionHandler:^(id result, NSError* error){
|
|
368
|
+
Q_UNUSED(result); Q_UNUSED(error);
|
|
369
|
+
}];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// =======================
|
|
373
|
+
// Download directory API
|
|
374
|
+
// =======================
|
|
375
|
+
QString WKWebViewWidget::downloadDirectory() const {
|
|
376
|
+
return d ? d->downloadDir : QString();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
void WKWebViewWidget::setDownloadDirectory(const QString& dirPath) {
|
|
380
|
+
if (!d) return;
|
|
381
|
+
QString p = QDir::fromNativeSeparators(dirPath);
|
|
382
|
+
if (p.endsWith('/')) p.chop(1);
|
|
383
|
+
if (p.isEmpty()) return;
|
|
384
|
+
QDir().mkpath(p);
|
|
385
|
+
d->downloadDir = p;
|
|
386
|
+
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#import <Cocoa/Cocoa.h>
|
|
2
|
-
#import <WebKit/WebKit.h>
|
|
3
|
-
|
|
4
|
-
#include "WKWebViewWidget.h"
|
|
5
|
-
|
|
6
|
-
#include <QtWidgets>
|
|
7
|
-
#include <QString>
|
|
8
|
-
#include <QUrl>
|
|
9
|
-
|
|
10
|
-
@class WKNavDelegate;
|
|
11
|
-
|
|
12
|
-
struct WKWebViewWidget::Impl {
|
|
13
|
-
WKWebView* wk = nil;
|
|
14
|
-
WKNavDelegate* delegate = nil;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
@interface WKNavDelegate : NSObject <WKNavigationDelegate>
|
|
18
|
-
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
19
|
-
@end
|
|
20
|
-
|
|
21
|
-
@implementation WKNavDelegate
|
|
22
|
-
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
|
23
|
-
if (!self.owner) return;
|
|
24
|
-
emit self.owner->loadFinished(true);
|
|
25
|
-
if (webView.URL) emit self.owner->urlChanged(QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String)));
|
|
26
|
-
if (webView.title) emit self.owner->titleChanged(QString::fromUtf8(webView.title.UTF8String));
|
|
27
|
-
emit self.owner->loadProgress(100);
|
|
28
|
-
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
29
|
-
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
30
|
-
}
|
|
31
|
-
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
|
32
|
-
if (!self.owner) return;
|
|
33
|
-
emit self.owner->loadFinished(false);
|
|
34
|
-
emit self.owner->loadProgress(0);
|
|
35
|
-
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
36
|
-
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
37
|
-
}
|
|
38
|
-
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
|
|
39
|
-
if (self.owner) emit self.owner->loadProgress(5);
|
|
40
|
-
}
|
|
41
|
-
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
|
|
42
|
-
if (self.owner) emit self.owner->loadProgress(50);
|
|
43
|
-
}
|
|
44
|
-
@end
|
|
45
|
-
|
|
46
|
-
static NSURL* toNSURL(QUrl u) {
|
|
47
|
-
if (!u.isValid()) return nil;
|
|
48
|
-
|
|
49
|
-
if (u.scheme().isEmpty())
|
|
50
|
-
u = QUrl::fromUserInput(u.toString());
|
|
51
|
-
|
|
52
|
-
if (u.scheme() == "http")
|
|
53
|
-
u.setScheme("https");
|
|
54
|
-
|
|
55
|
-
if (u.isLocalFile())
|
|
56
|
-
return [NSURL fileURLWithPath:[NSString stringWithUTF8String:u.toLocalFile().toUtf8().constData()]];
|
|
57
|
-
|
|
58
|
-
const QByteArray enc = u.toString(QUrl::FullyEncoded).toUtf8();
|
|
59
|
-
return [NSURL URLWithString:[NSString stringWithUTF8String:enc.constData()]];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
64
|
-
: QWidget(parent), d(new Impl) {
|
|
65
|
-
setAttribute(Qt::WA_NativeWindow, true);
|
|
66
|
-
(void)winId();
|
|
67
|
-
|
|
68
|
-
NSView* nsParent = (__bridge NSView*)reinterpret_cast<void*>(winId());
|
|
69
|
-
WKWebViewConfiguration* cfg = [[WKWebViewConfiguration alloc] init];
|
|
70
|
-
if ([cfg respondsToSelector:@selector(defaultWebpagePreferences)]) {
|
|
71
|
-
cfg.defaultWebpagePreferences.allowsContentJavaScript = YES;
|
|
72
|
-
}
|
|
73
|
-
d->wk = [[WKWebView alloc] initWithFrame:nsParent.bounds configuration:cfg];
|
|
74
|
-
d->wk.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
75
|
-
[nsParent addSubview:d->wk];
|
|
76
|
-
|
|
77
|
-
d->delegate = [WKNavDelegate new];
|
|
78
|
-
d->delegate.owner = this;
|
|
79
|
-
[d->wk setNavigationDelegate:d->delegate];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
WKWebViewWidget::~WKWebViewWidget() {
|
|
83
|
-
if (!d) return;
|
|
84
|
-
if (d->wk) { [d->wk removeFromSuperview]; d->wk = nil; }
|
|
85
|
-
d->delegate = nil;
|
|
86
|
-
delete d; d = nullptr;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
void WKWebViewWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); }
|
|
90
|
-
void WKWebViewWidget::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); }
|
|
91
|
-
|
|
92
|
-
QUrl WKWebViewWidget::url() const {
|
|
93
|
-
if (!(d && d->wk)) return QUrl();
|
|
94
|
-
NSURL* nsurl = d->wk.URL;
|
|
95
|
-
if (!nsurl) return QUrl();
|
|
96
|
-
const char* utf8 = nsurl.absoluteString.UTF8String;
|
|
97
|
-
if (!utf8) return QUrl();
|
|
98
|
-
return QUrl::fromEncoded(QByteArray(utf8));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
void WKWebViewWidget::setUrl(const QUrl& u) {
|
|
102
|
-
if (!(d && d->wk)) return;
|
|
103
|
-
NSURL* nsurl = toNSURL(u);
|
|
104
|
-
if (!nsurl) return;
|
|
105
|
-
[d->wk loadRequest:[NSURLRequest requestWithURL:nsurl]];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
void WKWebViewWidget::back() { if (d && d->wk && d->wk.canGoBack) [d->wk goBack]; }
|
|
109
|
-
void WKWebViewWidget::forward() { if (d && d->wk && d->wk.canGoForward) [d->wk goForward]; }
|
|
110
|
-
void WKWebViewWidget::stop() { if (d && d->wk) [d->wk stopLoading:nil]; }
|
|
111
|
-
void WKWebViewWidget::reload() { if (d && d->wk) [d->wk reload]; }
|
|
112
|
-
void WKWebViewWidget::evaluateJavaScript(const QString& script) {
|
|
113
|
-
if (!d || !d->wk) return;
|
|
114
|
-
NSString* s = [NSString stringWithUTF8String:script.toUtf8().constData()];
|
|
115
|
-
[d->wk evaluateJavaScript:s completionHandler:^(id result, NSError* error){
|
|
116
|
-
Q_UNUSED(result); Q_UNUSED(error);
|
|
117
|
-
}];
|
|
118
|
-
}
|
{fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/.github/workflows/wheels-macos.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fit_webview_bridge-0.2.0a2 → fit_webview_bridge-0.2.1a1}/bindings/pyside6/macos/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|