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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fit-webview-bridge
3
- Version: 0.2.3a2
3
+ Version: 0.2.5a1
4
4
  Summary: Qt native WebView bridge with PySide6 bindings
5
5
  Author: FIT Project
6
6
  License: LGPL-3.0-or-later
@@ -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.3a2"
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
+ }