fit-webview-bridge 0.2.3a2__tar.gz → 0.2.5a1__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.3a2 → fit_webview_bridge-0.2.5a1}/PKG-INFO +1 -1
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/examples/macos/wkwebview_demo.py +3 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/pyproject.toml +1 -1
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/src/macos/WKWebViewWidget.h +11 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/src/macos/WKWebViewWidget.mm +190 -2
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/.github/workflows/wheels-macos.yml +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/.gitignore +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/.vscode/settings.json +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/README.md +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/bindings/pyside6/macos/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/bindings/pyside6/macos/typesystem_wkwebview.xml +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/fit_webview_bridge/__init__.py +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/src/macos/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/src/macos/DownloadInfo.h +0 -0
|
@@ -63,6 +63,9 @@ class Main(QMainWindow):
|
|
|
63
63
|
# segnali base
|
|
64
64
|
self.view.titleChanged.connect(self.setWindowTitle)
|
|
65
65
|
self.view.loadProgress.connect(lambda p: print("progress:", p))
|
|
66
|
+
self.view.setUserAgent(
|
|
67
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
|
|
68
|
+
)
|
|
66
69
|
|
|
67
70
|
# abilita/disabilita i bottoni in base alla navigazione
|
|
68
71
|
self.btnBack.setEnabled(False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fit-webview-bridge"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5a1"
|
|
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"]
|
|
@@ -26,6 +26,13 @@ public:
|
|
|
26
26
|
|
|
27
27
|
Q_INVOKABLE void setDownloadDirectory(const QString& dirPath);
|
|
28
28
|
Q_INVOKABLE QString downloadDirectory() const;
|
|
29
|
+
void renderErrorPage(const QUrl& url, const QString& reason, int httpStatus);
|
|
30
|
+
|
|
31
|
+
// ==== USER AGENT ====
|
|
32
|
+
Q_INVOKABLE void setUserAgent(const QString& ua); // UA completo
|
|
33
|
+
Q_INVOKABLE QString userAgent() const; // restituisce l’override (se presente)
|
|
34
|
+
Q_INVOKABLE void resetUserAgent(); // rimuove l’override
|
|
35
|
+
Q_INVOKABLE void setApplicationNameForUserAgent(const QString& appName); // opzionale
|
|
29
36
|
|
|
30
37
|
signals:
|
|
31
38
|
void loadFinished(bool ok);
|
|
@@ -34,6 +41,7 @@ signals:
|
|
|
34
41
|
void loadProgress(int percent);
|
|
35
42
|
void canGoBackChanged(bool);
|
|
36
43
|
void canGoForwardChanged(bool);
|
|
44
|
+
|
|
37
45
|
|
|
38
46
|
|
|
39
47
|
void downloadStarted(const QString& suggestedFilename, const QString& destinationPath);
|
|
@@ -47,4 +55,7 @@ protected:
|
|
|
47
55
|
|
|
48
56
|
private:
|
|
49
57
|
struct Impl; Impl* d = nullptr;
|
|
58
|
+
|
|
59
|
+
// --- NEW: helper che applica UA
|
|
60
|
+
void applyUserAgent();
|
|
50
61
|
};
|
|
@@ -53,6 +53,10 @@ struct WKWebViewWidget::Impl {
|
|
|
53
53
|
WKUserContentController* ucc = nil;
|
|
54
54
|
FitUrlMsgHandler* msg = nil;
|
|
55
55
|
QString downloadDir; // es. ~/Downloads
|
|
56
|
+
|
|
57
|
+
// --- UA ---
|
|
58
|
+
QString customUA; // override UA (se non vuoto)
|
|
59
|
+
QString appUA; // suffix via configuration (opzionale)
|
|
56
60
|
};
|
|
57
61
|
|
|
58
62
|
// =======================
|
|
@@ -92,6 +96,10 @@ static NSString* FIT_T(NSString* key) {
|
|
|
92
96
|
@"menu.openImage": @"Open image",
|
|
93
97
|
@"menu.copyImageURL": @"Copy image URL",
|
|
94
98
|
@"menu.downloadImage":@"Download image…",
|
|
99
|
+
@"error.title": @"Can’t load this page",
|
|
100
|
+
@"error.reason": @"The site may not exist or be temporarily unreachable.",
|
|
101
|
+
@"error.retry": @"Retry",
|
|
102
|
+
@"error.close": @"Close",
|
|
95
103
|
};
|
|
96
104
|
it = @{
|
|
97
105
|
@"menu.openLink": @"Apri link",
|
|
@@ -99,6 +107,10 @@ static NSString* FIT_T(NSString* key) {
|
|
|
99
107
|
@"menu.openImage": @"Apri immagine",
|
|
100
108
|
@"menu.copyImageURL": @"Copia URL immagine",
|
|
101
109
|
@"menu.downloadImage":@"Scarica immagine…",
|
|
110
|
+
@"error.title": @"Impossibile caricare la pagina",
|
|
111
|
+
@"error.reason": @"Il sito potrebbe essere inesistente o momentaneamente non raggiungibile.",
|
|
112
|
+
@"error.retry": @"Riprova",
|
|
113
|
+
@"error.close": @"Chiudi",
|
|
102
114
|
};
|
|
103
115
|
});
|
|
104
116
|
NSString *lang = FIT_CurrentLang();
|
|
@@ -343,7 +355,7 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
|
|
|
343
355
|
// salva coppia padre/figlio per il “ritorno” post-download
|
|
344
356
|
self.pendingPopupParentURL = parent;
|
|
345
357
|
self.pendingPopupChildURL = child;
|
|
346
|
-
|
|
358
|
+
|
|
347
359
|
if (child) {
|
|
348
360
|
[webView loadRequest:navigationAction.request]; // apri nella stessa webview
|
|
349
361
|
}
|
|
@@ -353,6 +365,26 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
|
|
|
353
365
|
}
|
|
354
366
|
|
|
355
367
|
|
|
368
|
+
- (void)webView:(WKWebView *)webView
|
|
369
|
+
didFailProvisionalNavigation:(WKNavigation *)navigation
|
|
370
|
+
withError:(NSError *)error
|
|
371
|
+
{
|
|
372
|
+
if (!self.owner) return;
|
|
373
|
+
emit self.owner->loadFinished(false);
|
|
374
|
+
emit self.owner->loadProgress(0);
|
|
375
|
+
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
376
|
+
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
377
|
+
|
|
378
|
+
QUrl qurl = webView.URL
|
|
379
|
+
? QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String))
|
|
380
|
+
: QUrl();
|
|
381
|
+
self.owner->renderErrorPage(qurl,
|
|
382
|
+
QString::fromUtf8(error.localizedDescription.UTF8String),
|
|
383
|
+
/*httpStatus*/ 0);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
356
388
|
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
|
|
357
389
|
if (!self.owner) return;
|
|
358
390
|
if (webView.URL)
|
|
@@ -395,6 +427,13 @@ createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration
|
|
|
395
427
|
emit self.owner->loadProgress(0);
|
|
396
428
|
emit self.owner->canGoBackChanged(webView.canGoBack);
|
|
397
429
|
emit self.owner->canGoForwardChanged(webView.canGoForward);
|
|
430
|
+
// Mostra pagina d'errore interna
|
|
431
|
+
QUrl qurl = webView.URL
|
|
432
|
+
? QUrl::fromEncoded(QByteArray(webView.URL.absoluteString.UTF8String))
|
|
433
|
+
: QUrl();
|
|
434
|
+
self.owner->renderErrorPage(qurl,
|
|
435
|
+
QString::fromUtf8(error.localizedDescription.UTF8String),
|
|
436
|
+
/*httpStatus*/ 0);
|
|
398
437
|
}
|
|
399
438
|
|
|
400
439
|
#pragma mark - Decide download vs render
|
|
@@ -413,6 +452,15 @@ decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
|
|
|
413
452
|
if (cd && [[cd lowercaseString] containsString:@"attachment"]) {
|
|
414
453
|
isAttachment = YES;
|
|
415
454
|
}
|
|
455
|
+
// 🔎 Se è main frame e status HTTP >= 400, mostra pagina d'errore custom
|
|
456
|
+
if (navigationResponse.isForMainFrame && http.statusCode >= 400 && self.owner) {
|
|
457
|
+
QUrl qurl = QUrl::fromEncoded(QByteArray(url.absoluteString.UTF8String));
|
|
458
|
+
self.owner->renderErrorPage(qurl,
|
|
459
|
+
QStringLiteral(""), // reason generica localizzata dal template
|
|
460
|
+
(int)http.statusCode);
|
|
461
|
+
decisionHandler(WKNavigationResponsePolicyCancel);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
416
464
|
}
|
|
417
465
|
|
|
418
466
|
if (isAttachment) {
|
|
@@ -810,6 +858,8 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
|
810
858
|
d->delegate.webView = d->wk;
|
|
811
859
|
[d->wk setNavigationDelegate:d->delegate];
|
|
812
860
|
[d->wk setUIDelegate:d->delegate];
|
|
861
|
+
|
|
862
|
+
applyUserAgent();
|
|
813
863
|
}
|
|
814
864
|
|
|
815
865
|
WKWebViewWidget::~WKWebViewWidget() {
|
|
@@ -874,4 +924,142 @@ void WKWebViewWidget::setDownloadDirectory(const QString& dirPath) {
|
|
|
874
924
|
if (p.isEmpty()) return;
|
|
875
925
|
QDir().mkpath(p);
|
|
876
926
|
d->downloadDir = p;
|
|
877
|
-
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
void WKWebViewWidget::renderErrorPage(const QUrl& url,
|
|
930
|
+
const QString& reason,
|
|
931
|
+
int httpStatus)
|
|
932
|
+
{
|
|
933
|
+
if (!(d && d->wk)) return;
|
|
934
|
+
|
|
935
|
+
// Template HTML minimale, con testo bilingue (IT/EN tramite FIT_T).
|
|
936
|
+
// Segnaposti: {url}, {reason}, {status}, {title}, {retry}, {close}
|
|
937
|
+
QString html = QString::fromUtf8(
|
|
938
|
+
R"FWB(<!doctype html><html lang="it"><meta charset="utf-8">
|
|
939
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
940
|
+
<title>{title}</title>
|
|
941
|
+
<style>
|
|
942
|
+
:root { color-scheme: light dark; }
|
|
943
|
+
html,body{height:100%}
|
|
944
|
+
body{
|
|
945
|
+
font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;
|
|
946
|
+
margin:0;background:#fff;color:#000
|
|
947
|
+
}
|
|
948
|
+
@media (prefers-color-scheme: dark){
|
|
949
|
+
body{background:#000;color:#fff}
|
|
950
|
+
}
|
|
951
|
+
.card{
|
|
952
|
+
max-width:720px;margin:8vh auto;padding:28px;border-radius:16px;
|
|
953
|
+
background:#fff;color:#000;box-shadow:0 6px 24px rgba(0,0,0,.18)
|
|
954
|
+
}
|
|
955
|
+
@media (prefers-color-scheme: dark){
|
|
956
|
+
.card{background:#111;color:#eee;box-shadow:0 6px 24px rgba(255,255,255,.05)}
|
|
957
|
+
}
|
|
958
|
+
h1{font-size:22px;margin:0 0 6px}
|
|
959
|
+
p{line-height:1.5}
|
|
960
|
+
code{
|
|
961
|
+
background:#eee;color:#000;padding:2px 6px;border-radius:6px
|
|
962
|
+
}
|
|
963
|
+
@media (prefers-color-scheme: dark){
|
|
964
|
+
code{background:#222;color:#fff}
|
|
965
|
+
}
|
|
966
|
+
.actions{margin-top:18px;display:flex;gap:10px;flex-wrap:wrap}
|
|
967
|
+
button,a{
|
|
968
|
+
padding:10px 14px;border-radius:10px;border:1px solid currentColor;
|
|
969
|
+
cursor:pointer;text-decoration:none;background:transparent;color:inherit
|
|
970
|
+
}
|
|
971
|
+
button.primary{
|
|
972
|
+
background:#000;color:#fff;border-color:#000
|
|
973
|
+
}
|
|
974
|
+
@media (prefers-color-scheme: dark){
|
|
975
|
+
button.primary{background:#fff;color:#000;border-color:#fff}
|
|
976
|
+
}
|
|
977
|
+
small{opacity:.7}
|
|
978
|
+
</style>
|
|
979
|
+
<div class="card">
|
|
980
|
+
<h1>{title}</h1>
|
|
981
|
+
<p>URL: <code>{url}</code></p>
|
|
982
|
+
<p>{reason} <small>{status}</small></p>
|
|
983
|
+
<div class="actions">
|
|
984
|
+
<button class="primary" onclick="location.reload()">{retry}</button>
|
|
985
|
+
<a class="ghost" href="about:blank">{close}</a>
|
|
986
|
+
</div>
|
|
987
|
+
</div>)FWB"
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
// Localizza con FIT_T
|
|
993
|
+
QString title = QString::fromUtf8([FIT_T(@"error.title") UTF8String]);
|
|
994
|
+
QString reasonText = reason.isEmpty()
|
|
995
|
+
? QString::fromUtf8([FIT_T(@"error.reason") UTF8String])
|
|
996
|
+
: reason;
|
|
997
|
+
QString retry = QString::fromUtf8([FIT_T(@"error.retry") UTF8String]);
|
|
998
|
+
QString close = QString::fromUtf8([FIT_T(@"error.close") UTF8String]);
|
|
999
|
+
|
|
1000
|
+
html.replace("{title}", title);
|
|
1001
|
+
html.replace("{url}", url.toString());
|
|
1002
|
+
html.replace("{reason}", reasonText);
|
|
1003
|
+
html.replace("{status}", httpStatus > 0 ? QString("HTTP %1").arg(httpStatus) : QString());
|
|
1004
|
+
html.replace("{retry}", retry);
|
|
1005
|
+
html.replace("{close}", close);
|
|
1006
|
+
|
|
1007
|
+
// Carica l'HTML direttamente nella webview
|
|
1008
|
+
[d->wk loadHTMLString:[NSString stringWithUTF8String:html.toUtf8().constData()]
|
|
1009
|
+
baseURL:[NSURL URLWithString:@"about:blank"]];
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// --- NEW: metodo privato
|
|
1013
|
+
void WKWebViewWidget::applyUserAgent() {
|
|
1014
|
+
if (!(d && d->wk)) return;
|
|
1015
|
+
@autoreleasepool {
|
|
1016
|
+
// Suffix via configuration.applicationNameForUserAgent
|
|
1017
|
+
if (d->appUA.isEmpty()) {
|
|
1018
|
+
@try { [d->wk.configuration setValue:nil forKey:@"applicationNameForUserAgent"]; } @catch(...) {}
|
|
1019
|
+
} else {
|
|
1020
|
+
NSString* s = [NSString stringWithUTF8String:d->appUA.toUtf8().constData()];
|
|
1021
|
+
@try {
|
|
1022
|
+
if ([d->wk.configuration respondsToSelector:@selector(setApplicationNameForUserAgent:)]) {
|
|
1023
|
+
d->wk.configuration.applicationNameForUserAgent = s;
|
|
1024
|
+
} else {
|
|
1025
|
+
[d->wk.configuration setValue:s forKey:@"applicationNameForUserAgent"];
|
|
1026
|
+
}
|
|
1027
|
+
} @catch(...) {}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Override totale via customUserAgent
|
|
1031
|
+
if (d->customUA.isEmpty()) {
|
|
1032
|
+
@try { d->wk.customUserAgent = nil; } @catch(...) {
|
|
1033
|
+
@try { [d->wk setValue:nil forKey:@"customUserAgent"]; } @catch(...) {}
|
|
1034
|
+
}
|
|
1035
|
+
} else {
|
|
1036
|
+
NSString* ua = [NSString stringWithUTF8String:d->customUA.toUtf8().constData()];
|
|
1037
|
+
@try { d->wk.customUserAgent = ua; } @catch(...) {
|
|
1038
|
+
@try { [d->wk setValue:ua forKey:@"customUserAgent"]; } @catch(...) {}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// --- API pubblica UA
|
|
1045
|
+
void WKWebViewWidget::setUserAgent(const QString& ua) {
|
|
1046
|
+
if (!d) return;
|
|
1047
|
+
d->customUA = ua.trimmed();
|
|
1048
|
+
applyUserAgent();
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
QString WKWebViewWidget::userAgent() const {
|
|
1052
|
+
return d ? d->customUA : QString();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
void WKWebViewWidget::resetUserAgent() {
|
|
1056
|
+
if (!d) return;
|
|
1057
|
+
d->customUA.clear();
|
|
1058
|
+
applyUserAgent();
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
void WKWebViewWidget::setApplicationNameForUserAgent(const QString& appName) {
|
|
1062
|
+
if (!d) return;
|
|
1063
|
+
d->appUA = appName.trimmed();
|
|
1064
|
+
applyUserAgent();
|
|
1065
|
+
}
|
{fit_webview_bridge-0.2.3a2 → fit_webview_bridge-0.2.5a1}/.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.3a2 → fit_webview_bridge-0.2.5a1}/bindings/pyside6/macos/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|