fit-webview-bridge 0.2.2a5__tar.gz → 0.2.3a1__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.2a5 → fit_webview_bridge-0.2.3a1}/PKG-INFO +1 -1
- fit_webview_bridge-0.2.3a1/examples/macos/wkwebview_demo.py +110 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/pyproject.toml +1 -1
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/src/macos/WKWebViewWidget.mm +266 -5
- fit_webview_bridge-0.2.2a5/examples/macos/wkwebview_demo.py +0 -43
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/.github/workflows/wheels-macos.yml +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/.gitignore +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/.vscode/settings.json +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/README.md +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/bindings/pyside6/macos/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/bindings/pyside6/macos/typesystem_wkwebview.xml +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/fit_webview_bridge/__init__.py +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/src/macos/CMakeLists.txt +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/src/macos/DownloadInfo.h +0 -0
- {fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/src/macos/WKWebViewWidget.h +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
5
|
+
sys.path[:0] = [
|
|
6
|
+
os.path.join(ROOT, "build"),
|
|
7
|
+
os.path.join(ROOT, "build", "bindings", "shiboken_out"),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
from PySide6.QtCore import QUrl
|
|
11
|
+
from PySide6.QtWidgets import (
|
|
12
|
+
QApplication,
|
|
13
|
+
QHBoxLayout,
|
|
14
|
+
QMainWindow,
|
|
15
|
+
QPushButton,
|
|
16
|
+
QVBoxLayout,
|
|
17
|
+
QWidget,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# tentativo 1: pacchetto generato da shiboken (wkwebview)
|
|
21
|
+
try:
|
|
22
|
+
import wkwebview
|
|
23
|
+
|
|
24
|
+
WKWebViewWidget = wkwebview.WKWebViewWidget
|
|
25
|
+
except Exception:
|
|
26
|
+
# tentativo 2: modulo nativo diretto
|
|
27
|
+
from _wkwebview import WKWebViewWidget
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
HOME_URL = "https://google.it"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Main(QMainWindow):
|
|
34
|
+
def __init__(self):
|
|
35
|
+
super().__init__()
|
|
36
|
+
|
|
37
|
+
central = QWidget(self)
|
|
38
|
+
root = QVBoxLayout(central)
|
|
39
|
+
self.setCentralWidget(central)
|
|
40
|
+
|
|
41
|
+
# --- toolbar semplice ---
|
|
42
|
+
bar = QHBoxLayout()
|
|
43
|
+
self.btnBack = QPushButton("◀︎ Back")
|
|
44
|
+
self.btnFwd = QPushButton("Forward ▶︎")
|
|
45
|
+
self.btnHome = QPushButton("🏠 Home")
|
|
46
|
+
bar.addWidget(self.btnBack)
|
|
47
|
+
bar.addWidget(self.btnFwd)
|
|
48
|
+
bar.addWidget(self.btnHome)
|
|
49
|
+
bar.addStretch(1)
|
|
50
|
+
root.addLayout(bar)
|
|
51
|
+
|
|
52
|
+
# --- webview ---
|
|
53
|
+
self.view = WKWebViewWidget()
|
|
54
|
+
root.addWidget(self.view)
|
|
55
|
+
|
|
56
|
+
# segnali base
|
|
57
|
+
self.view.titleChanged.connect(self.setWindowTitle)
|
|
58
|
+
self.view.loadProgress.connect(lambda p: print("progress:", p))
|
|
59
|
+
|
|
60
|
+
# abilita/disabilita i bottoni in base alla navigazione
|
|
61
|
+
self.btnBack.setEnabled(False)
|
|
62
|
+
self.btnFwd.setEnabled(False)
|
|
63
|
+
self.view.canGoBackChanged.connect(self.btnBack.setEnabled)
|
|
64
|
+
self.view.canGoForwardChanged.connect(self.btnFwd.setEnabled)
|
|
65
|
+
|
|
66
|
+
# azioni bottoni
|
|
67
|
+
self.btnBack.clicked.connect(self.view.back)
|
|
68
|
+
self.btnFwd.clicked.connect(self.view.forward)
|
|
69
|
+
self.btnHome.clicked.connect(lambda: self.view.setUrl(QUrl(HOME_URL)))
|
|
70
|
+
|
|
71
|
+
# --- eventi download: print semplici ---
|
|
72
|
+
self.view.downloadStarted.connect(
|
|
73
|
+
lambda name, path: print(f"[download] started: name='{name}' path='{path}'")
|
|
74
|
+
)
|
|
75
|
+
self.view.downloadProgress.connect(
|
|
76
|
+
lambda done, total: print(
|
|
77
|
+
f"[download] progress: {done}/{total if total >= 0 else '?'}"
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
self.view.downloadFailed.connect(
|
|
81
|
+
lambda path, err: print(f"[download] FAILED: path='{path}' err='{err}'")
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def on_finished(info):
|
|
85
|
+
# Proviamo a leggere i getter se disponibili; fallback a repr
|
|
86
|
+
try:
|
|
87
|
+
fname = info.fileName() if hasattr(info, "fileName") else None
|
|
88
|
+
directory = info.directory() if hasattr(info, "directory") else None
|
|
89
|
+
url = info.url().toString() if hasattr(info, "url") else None
|
|
90
|
+
if fname or directory or url:
|
|
91
|
+
print(
|
|
92
|
+
f"[download] finished: file='{fname}' dir='{directory}' url='{url}'"
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
print(f"[download] finished: {info}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"[download] finished (inspect error: {e}): {info}")
|
|
98
|
+
|
|
99
|
+
self.view.downloadFinished.connect(on_finished)
|
|
100
|
+
|
|
101
|
+
# carica home
|
|
102
|
+
self.view.setUrl(QUrl(HOME_URL))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
app = QApplication([])
|
|
107
|
+
m = Main()
|
|
108
|
+
m.resize(1200, 800)
|
|
109
|
+
m.show()
|
|
110
|
+
app.exec()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fit-webview-bridge"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3a1"
|
|
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"]
|
|
@@ -6,6 +6,36 @@
|
|
|
6
6
|
#include "DownloadInfo.h"
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
static inline void fit_emit_downloadStarted(WKWebViewWidget* owner,
|
|
10
|
+
const QString& name,
|
|
11
|
+
const QString& path) {
|
|
12
|
+
if (!owner) return;
|
|
13
|
+
QMetaObject::invokeMethod(owner, [owner, name, path]{
|
|
14
|
+
emit owner->downloadStarted(name, path);
|
|
15
|
+
}, Qt::QueuedConnection);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static inline void fit_emit_downloadFailed(WKWebViewWidget* owner,
|
|
19
|
+
const QString& path,
|
|
20
|
+
const QString& err) {
|
|
21
|
+
if (!owner) return;
|
|
22
|
+
QMetaObject::invokeMethod(owner, [owner, path, err]{
|
|
23
|
+
emit owner->downloadFailed(path, err);
|
|
24
|
+
}, Qt::QueuedConnection);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static inline void fit_emit_downloadFinished(WKWebViewWidget* owner,
|
|
28
|
+
const QString& fileName,
|
|
29
|
+
const QString& dir,
|
|
30
|
+
const QUrl& src) {
|
|
31
|
+
if (!owner) return;
|
|
32
|
+
QMetaObject::invokeMethod(owner, [owner, fileName, dir, src]{
|
|
33
|
+
auto *info = new DownloadInfo(fileName, dir, src, owner);
|
|
34
|
+
emit owner->downloadFinished(info);
|
|
35
|
+
}, Qt::QueuedConnection);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
9
39
|
#include <QtWidgets>
|
|
10
40
|
#include <QString>
|
|
11
41
|
#include <QUrl>
|
|
@@ -35,19 +65,230 @@ static NSURL* toNSURL(QUrl u);
|
|
|
35
65
|
// =======================
|
|
36
66
|
@interface FitUrlMsgHandler : NSObject <WKScriptMessageHandler>
|
|
37
67
|
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
68
|
+
@property(nonatomic, assign) WKWebView* webView;
|
|
69
|
+
|
|
70
|
+
- (void)_fitShowContextMenuFromPayload:(NSDictionary*)payload;
|
|
71
|
+
- (void)_fitOpenLink:(NSMenuItem*)item;
|
|
72
|
+
- (void)_fitCopyURL:(NSMenuItem*)item;
|
|
38
73
|
@end
|
|
39
74
|
|
|
75
|
+
|
|
76
|
+
static NSString* FIT_CurrentLang(void) {
|
|
77
|
+
NSString *lang = NSLocale.preferredLanguages.firstObject ?: @"en";
|
|
78
|
+
// normalizza es. "it-IT" -> "it"
|
|
79
|
+
NSRange dash = [lang rangeOfString:@"-"];
|
|
80
|
+
return (dash.location != NSNotFound) ? [lang substringToIndex:dash.location] : lang;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static NSString* FIT_T(NSString* key) {
|
|
84
|
+
static NSDictionary *en, *it;
|
|
85
|
+
static dispatch_once_t once;
|
|
86
|
+
dispatch_once(&once, ^{
|
|
87
|
+
en = @{
|
|
88
|
+
@"menu.openLink": @"Open link",
|
|
89
|
+
@"menu.copyLink": @"Copy link address",
|
|
90
|
+
@"menu.openImage": @"Open image",
|
|
91
|
+
@"menu.copyImageURL": @"Copy image URL",
|
|
92
|
+
@"menu.downloadImage":@"Download image…",
|
|
93
|
+
};
|
|
94
|
+
it = @{
|
|
95
|
+
@"menu.openLink": @"Apri link",
|
|
96
|
+
@"menu.copyLink": @"Copia indirizzo link",
|
|
97
|
+
@"menu.openImage": @"Apri immagine",
|
|
98
|
+
@"menu.copyImageURL": @"Copia URL immagine",
|
|
99
|
+
@"menu.downloadImage":@"Scarica immagine…",
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
NSString *lang = FIT_CurrentLang();
|
|
103
|
+
NSDictionary *tbl = [lang isEqualToString:@"it"] ? it : en;
|
|
104
|
+
return tbl[key] ?: en[key] ?: key;
|
|
105
|
+
}
|
|
106
|
+
|
|
40
107
|
@implementation FitUrlMsgHandler
|
|
108
|
+
|
|
109
|
+
// Helpers per sanity-check su tipi da payload
|
|
110
|
+
static inline NSString* FITStringOrNil(id obj) {
|
|
111
|
+
return [obj isKindOfClass:NSString.class] ? (NSString*)obj : nil;
|
|
112
|
+
}
|
|
113
|
+
static inline NSNumber* FITNumberOrNil(id obj) {
|
|
114
|
+
return [obj isKindOfClass:NSNumber.class] ? (NSNumber*)obj : nil;
|
|
115
|
+
}
|
|
116
|
+
|
|
41
117
|
- (void)userContentController:(WKUserContentController *)userContentController
|
|
42
|
-
didReceiveScriptMessage:(WKScriptMessage *)message
|
|
118
|
+
didReceiveScriptMessage:(WKScriptMessage *)message
|
|
119
|
+
{
|
|
43
120
|
if (!self.owner) return;
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
121
|
+
|
|
122
|
+
if ([message.name isEqualToString:@"fitUrlChanged"]) {
|
|
123
|
+
if (![message.body isKindOfClass:[NSString class]]) return;
|
|
124
|
+
QString s = QString::fromUtf8([(NSString*)message.body UTF8String]);
|
|
125
|
+
emit self.owner->urlChanged(QUrl::fromEncoded(s.toUtf8()));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if ([message.name isEqualToString:@"fitContextMenu"]) {
|
|
130
|
+
if (![message.body isKindOfClass:[NSDictionary class]]) return;
|
|
131
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
132
|
+
[self _fitShowContextMenuFromPayload:(NSDictionary*)message.body];
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
- (void)_fitShowContextMenuFromPayload:(NSDictionary*)payload
|
|
139
|
+
{
|
|
140
|
+
WKWebView* wv = self.webView;
|
|
141
|
+
if (!wv || !wv.window) return;
|
|
142
|
+
|
|
143
|
+
NSString *linkStr = FITStringOrNil(payload[@"link"]);
|
|
144
|
+
NSString *imgStr = FITStringOrNil(payload[@"image"]);
|
|
145
|
+
NSURL *linkURL = (linkStr.length ? [NSURL URLWithString:linkStr] : nil);
|
|
146
|
+
NSURL *imgURL = (imgStr.length ? [NSURL URLWithString:imgStr] : nil);
|
|
147
|
+
if (!linkURL && !imgURL) return;
|
|
148
|
+
|
|
149
|
+
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
|
|
150
|
+
|
|
151
|
+
if (linkURL) {
|
|
152
|
+
NSMenuItem *open = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.openLink")
|
|
153
|
+
action:@selector(_fitOpenLink:)
|
|
154
|
+
keyEquivalent:@""];
|
|
155
|
+
open.target = self; open.representedObject = @{@"url": linkURL};
|
|
156
|
+
[menu addItem:open];
|
|
157
|
+
|
|
158
|
+
NSMenuItem *copy = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.copyLink")
|
|
159
|
+
action:@selector(_fitCopyURL:)
|
|
160
|
+
keyEquivalent:@""];
|
|
161
|
+
copy.target = self; copy.representedObject = @{@"url": linkURL};
|
|
162
|
+
[menu addItem:copy];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (imgURL) {
|
|
166
|
+
NSMenuItem *openImg = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.openImage")
|
|
167
|
+
action:@selector(_fitOpenLink:)
|
|
168
|
+
keyEquivalent:@""];
|
|
169
|
+
openImg.target = self; openImg.representedObject = @{@"url": imgURL};
|
|
170
|
+
[menu addItem:openImg];
|
|
171
|
+
|
|
172
|
+
NSMenuItem *copyImg = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.copyImageURL")
|
|
173
|
+
action:@selector(_fitCopyURL:)
|
|
174
|
+
keyEquivalent:@""];
|
|
175
|
+
copyImg.target = self; copyImg.representedObject = @{@"url": imgURL};
|
|
176
|
+
[menu addItem:copyImg];
|
|
177
|
+
|
|
178
|
+
NSMenuItem *dlImg = [[NSMenuItem alloc] initWithTitle:FIT_T(@"menu.downloadImage")
|
|
179
|
+
action:@selector(_fitDownloadImage:)
|
|
180
|
+
keyEquivalent:@""];
|
|
181
|
+
dlImg.target = self;
|
|
182
|
+
dlImg.representedObject = @{@"url": imgURL};
|
|
183
|
+
[menu addItem:[NSMenuItem separatorItem]];
|
|
184
|
+
[menu addItem:dlImg];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
NSPoint mouseOnScreen = [NSEvent mouseLocation];
|
|
188
|
+
NSPoint inWindow = [wv.window convertPointFromScreen:mouseOnScreen];
|
|
189
|
+
NSPoint inView = [wv convertPoint:inWindow fromView:nil];
|
|
190
|
+
|
|
191
|
+
[menu popUpMenuPositioningItem:nil atLocation:inView inView:wv];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
- (void)_fitOpenLink:(NSMenuItem*)item {
|
|
195
|
+
NSURL *url = ((NSDictionary*)item.representedObject)[@"url"];
|
|
196
|
+
if (!url || !self.webView) return;
|
|
197
|
+
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
- (void)_fitCopyURL:(NSMenuItem*)item {
|
|
201
|
+
NSURL *url = ((NSDictionary*)item.representedObject)[@"url"];
|
|
202
|
+
if (!url) return;
|
|
203
|
+
NSPasteboard *pb = [NSPasteboard generalPasteboard];
|
|
204
|
+
[pb clearContents];
|
|
205
|
+
[pb setString:url.absoluteString forType:NSPasteboardTypeString];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Utility: crea nome unico in una cartella
|
|
209
|
+
static NSString* fit_uniquePath(NSString* baseDir, NSString* filename) {
|
|
210
|
+
NSString* fname = filename.length ? filename : @"download";
|
|
211
|
+
NSString* path = [baseDir stringByAppendingPathComponent:fname];
|
|
212
|
+
NSFileManager* fm = [NSFileManager defaultManager];
|
|
213
|
+
if (![fm fileExistsAtPath:path]) return path;
|
|
214
|
+
|
|
215
|
+
NSString* name = [fname stringByDeletingPathExtension];
|
|
216
|
+
NSString* ext = [fname pathExtension];
|
|
217
|
+
for (NSUInteger i = 1; i < 10000; ++i) {
|
|
218
|
+
NSString* cand = ext.length
|
|
219
|
+
? [NSString stringWithFormat:@"%@ (%lu).%@", name, (unsigned long)i, ext]
|
|
220
|
+
: [NSString stringWithFormat:@"%@ (%lu)", name, (unsigned long)i];
|
|
221
|
+
NSString* candPath = [baseDir stringByAppendingPathComponent:cand];
|
|
222
|
+
if (![fm fileExistsAtPath:candPath]) return candPath;
|
|
223
|
+
}
|
|
224
|
+
return path;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Scarica un URL (usato dall’azione immagine)
|
|
228
|
+
- (void)_fitDownloadURL:(NSURL *)url suggestedName:(NSString *)suggestedName {
|
|
229
|
+
if (!url || !self.owner) return;
|
|
230
|
+
|
|
231
|
+
// cartella destinazione da Qt
|
|
232
|
+
QString qdir = self.owner->downloadDirectory();
|
|
233
|
+
NSString *destDir = [NSString stringWithUTF8String:qdir.toUtf8().constData()];
|
|
234
|
+
if (!destDir.length) destDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Downloads"];
|
|
235
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:destDir
|
|
236
|
+
withIntermediateDirectories:YES
|
|
237
|
+
attributes:nil error:nil];
|
|
238
|
+
|
|
239
|
+
// nome iniziale
|
|
240
|
+
NSString *fname = suggestedName.length ? suggestedName : (url.lastPathComponent.length ? url.lastPathComponent : @"download");
|
|
241
|
+
NSString *tmpTarget = fit_uniquePath(destDir, fname);
|
|
242
|
+
|
|
243
|
+
// segnala start (nome provvisorio)
|
|
244
|
+
fit_emit_downloadStarted(self.owner,
|
|
245
|
+
QString::fromUtf8([tmpTarget lastPathComponent].UTF8String),
|
|
246
|
+
QString::fromUtf8(tmpTarget.UTF8String));
|
|
247
|
+
|
|
248
|
+
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
249
|
+
NSURLSession *session = [NSURLSession sessionWithConfiguration:cfg];
|
|
250
|
+
NSURLSessionDownloadTask *task =
|
|
251
|
+
[session downloadTaskWithURL:url
|
|
252
|
+
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
|
|
253
|
+
{
|
|
254
|
+
if (error) {
|
|
255
|
+
fit_emit_downloadFailed(self.owner,
|
|
256
|
+
QString::fromUtf8(tmpTarget.UTF8String),
|
|
257
|
+
QString::fromUtf8(error.localizedDescription.UTF8String));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// usa il suggerimento del server se c’è
|
|
262
|
+
NSString *serverName = response.suggestedFilename.length ? response.suggestedFilename : [tmpTarget lastPathComponent];
|
|
263
|
+
NSString *finalPath = fit_uniquePath(destDir, serverName);
|
|
264
|
+
|
|
265
|
+
NSError *mvErr = nil;
|
|
266
|
+
[[NSFileManager defaultManager] moveItemAtURL:location
|
|
267
|
+
toURL:[NSURL fileURLWithPath:finalPath]
|
|
268
|
+
error:&mvErr];
|
|
269
|
+
if (mvErr) {
|
|
270
|
+
fit_emit_downloadFailed(self.owner,
|
|
271
|
+
QString::fromUtf8(finalPath.UTF8String),
|
|
272
|
+
QString::fromUtf8(mvErr.localizedDescription.UTF8String));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
QUrl qsrc = QUrl::fromEncoded(QByteArray(url.absoluteString.UTF8String));
|
|
277
|
+
fit_emit_downloadFinished(self.owner,
|
|
278
|
+
QString::fromUtf8([finalPath lastPathComponent].UTF8String),
|
|
279
|
+
QString::fromUtf8([finalPath stringByDeletingLastPathComponent].UTF8String),
|
|
280
|
+
qsrc);
|
|
281
|
+
}];
|
|
282
|
+
[task resume];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
- (void)_fitDownloadImage:(NSMenuItem*)item {
|
|
286
|
+
NSURL *url = ((NSDictionary*)item.representedObject)[@"url"];
|
|
287
|
+
[self _fitDownloadURL:url suggestedName:nil];
|
|
48
288
|
}
|
|
49
289
|
@end
|
|
50
290
|
|
|
291
|
+
|
|
51
292
|
// ===== WKNavDelegate =====
|
|
52
293
|
@interface WKNavDelegate : NSObject <WKNavigationDelegate, WKDownloadDelegate, WKUIDelegate>
|
|
53
294
|
@property(nonatomic, assign) WKWebViewWidget* owner;
|
|
@@ -435,6 +676,7 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
|
435
676
|
d->msg = [FitUrlMsgHandler new];
|
|
436
677
|
d->msg.owner = this;
|
|
437
678
|
[d->ucc addScriptMessageHandler:d->msg name:@"fitUrlChanged"];
|
|
679
|
+
[d->ucc addScriptMessageHandler:d->msg name:@"fitContextMenu"];
|
|
438
680
|
|
|
439
681
|
NSString* js =
|
|
440
682
|
@"(function(){"
|
|
@@ -447,8 +689,25 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
|
447
689
|
@" if (!a) return; if (a.target === '_blank' || a.hasAttribute('download')) return;"
|
|
448
690
|
@" setTimeout(emit, 0);"
|
|
449
691
|
@" }, true);"
|
|
692
|
+
@"})();"
|
|
693
|
+
@"(function(){"
|
|
694
|
+
@" document.addEventListener('contextmenu', function(ev){"
|
|
695
|
+
@" var el = ev.target;"
|
|
696
|
+
@" var a = el && el.closest ? el.closest('a[href]') : null;"
|
|
697
|
+
@" var img = el && el.closest ? el.closest('img[src]') : null;"
|
|
698
|
+
@" if (!a && !img) return;" // lascia il menu nativo altrove
|
|
699
|
+
@" ev.preventDefault();"
|
|
700
|
+
@" try {"
|
|
701
|
+
@" window.webkit.messageHandlers.fitContextMenu.postMessage({"
|
|
702
|
+
@" x: ev.clientX, y: ev.clientY,"
|
|
703
|
+
@" link: a ? a.href : null,"
|
|
704
|
+
@" image: img ? img.src : null"
|
|
705
|
+
@" });"
|
|
706
|
+
@" } catch(e){}"
|
|
707
|
+
@" }, true);"
|
|
450
708
|
@"})();";
|
|
451
709
|
|
|
710
|
+
|
|
452
711
|
WKUserScript* us = [[WKUserScript alloc]
|
|
453
712
|
initWithSource:js
|
|
454
713
|
injectionTime:WKUserScriptInjectionTimeAtDocumentStart
|
|
@@ -458,6 +717,7 @@ WKWebViewWidget::WKWebViewWidget(QWidget* parent)
|
|
|
458
717
|
|
|
459
718
|
d->wk = [[WKWebView alloc] initWithFrame:nsParent.bounds configuration:cfg];
|
|
460
719
|
d->wk.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
|
720
|
+
[d->msg setWebView:d->wk];
|
|
461
721
|
[nsParent addSubview:d->wk];
|
|
462
722
|
|
|
463
723
|
d->delegate = [WKNavDelegate new];
|
|
@@ -472,6 +732,7 @@ WKWebViewWidget::~WKWebViewWidget() {
|
|
|
472
732
|
|
|
473
733
|
if (d->ucc && d->msg) {
|
|
474
734
|
@try { [d->ucc removeScriptMessageHandlerForName:@"fitUrlChanged"]; } @catch (...) {}
|
|
735
|
+
@try { [d->ucc removeScriptMessageHandlerForName:@"fitContextMenu"]; } @catch (...) {}
|
|
475
736
|
}
|
|
476
737
|
d->msg = nil;
|
|
477
738
|
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
|
|
4
|
-
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
5
|
-
sys.path[:0] = [
|
|
6
|
-
os.path.join(ROOT, "build"),
|
|
7
|
-
os.path.join(ROOT, "build", "bindings", "shiboken_out"),
|
|
8
|
-
]
|
|
9
|
-
|
|
10
|
-
from PySide6.QtCore import QUrl
|
|
11
|
-
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
|
|
12
|
-
|
|
13
|
-
# tentativo 1: pacchetto generato da shiboken (wkwebview)
|
|
14
|
-
try:
|
|
15
|
-
import wkwebview
|
|
16
|
-
|
|
17
|
-
WKWebViewWidget = wkwebview.WKWebViewWidget # accesso via attributo
|
|
18
|
-
except Exception:
|
|
19
|
-
# tentativo 2: modulo nativo diretto
|
|
20
|
-
from _wkwebview import WKWebViewWidget
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class Main(QMainWindow):
|
|
24
|
-
def __init__(self):
|
|
25
|
-
super().__init__()
|
|
26
|
-
central = QWidget(self)
|
|
27
|
-
lay = QVBoxLayout(central)
|
|
28
|
-
self.view = WKWebViewWidget()
|
|
29
|
-
lay.addWidget(self.view)
|
|
30
|
-
self.setCentralWidget(central)
|
|
31
|
-
|
|
32
|
-
# segnali utili
|
|
33
|
-
self.view.titleChanged.connect(self.setWindowTitle)
|
|
34
|
-
self.view.loadProgress.connect(lambda p: print("progress:", p))
|
|
35
|
-
|
|
36
|
-
self.view.setUrl(QUrl("https://web.whatsapp.com/"))
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
app = QApplication([])
|
|
40
|
-
m = Main()
|
|
41
|
-
m.resize(1200, 800)
|
|
42
|
-
m.show()
|
|
43
|
-
app.exec()
|
{fit_webview_bridge-0.2.2a5 → fit_webview_bridge-0.2.3a1}/.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.2a5 → fit_webview_bridge-0.2.3a1}/bindings/pyside6/macos/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|