stupidpythonbrowser 1.0.1__tar.gz → 1.0.3__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.
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/PKG-INFO +1 -1
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/__init__.py +2 -2
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/browser.py +225 -88
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/settings.py +107 -25
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/terminal.py +1 -1
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pyproject.toml +1 -1
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/PKG-INFO +1 -1
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/LICENSE +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/README.md +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/doh_server.py +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/incognito.py +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/main.py +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/pybrowser/task_manager.py +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/setup.cfg +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/SOURCES.txt +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/dependency_links.txt +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/entry_points.txt +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/requires.txt +0 -0
- {stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ PyBrowser: Native macOS Chromium/Material 3 Web Browser.
|
|
|
3
3
|
Package Root.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.0.
|
|
6
|
+
__version__ = "1.0.3"
|
|
7
7
|
__all__ = [
|
|
8
8
|
"browser",
|
|
9
9
|
"settings",
|
|
@@ -11,4 +11,4 @@ __all__ = [
|
|
|
11
11
|
"incognito",
|
|
12
12
|
"task_manager",
|
|
13
13
|
"terminal",
|
|
14
|
-
]
|
|
14
|
+
]
|
|
@@ -35,7 +35,8 @@ from pybrowser.settings import (
|
|
|
35
35
|
SETTINGS, UA_PRESETS, DEFAULT_URL, DATA_DIR, SESSION_FILE, BOOKMARKS_FILE,
|
|
36
36
|
SETTINGS_FILE, HISTORY_FILE, ADBLOCK_CACHE_FILE, DNS_CACHE_FILE, USERSCRIPTS_DIR,
|
|
37
37
|
FILTER_LIST_URL, LOCAL_PROXY_PORT, BASE_ADBLOCK_DOMAINS, M3_COLORS, GROUP_COLORS,
|
|
38
|
-
validate_and_get_download_dir, get_settings_html, sanitize_suggested_filename
|
|
38
|
+
validate_and_get_download_dir, get_settings_html, sanitize_suggested_filename,
|
|
39
|
+
sanitize_for_json
|
|
39
40
|
)
|
|
40
41
|
from pybrowser.doh_server import (
|
|
41
42
|
DNS_CACHE, DNS_LOCK, STEVENBLACK_DOMAINS, BASE_ADBLOCK_DOMAINS, ADBLOCK_LOCK,
|
|
@@ -83,11 +84,14 @@ def get_m3_color(token, view, is_incognito=False):
|
|
|
83
84
|
return hex_to_nscolor(colors[1 if is_dark else 0])
|
|
84
85
|
|
|
85
86
|
def atomic_write_json(filepath, data):
|
|
87
|
+
"""Safely writes JSON to disk with recursive PyObjC sanitization to prevent TypeErrors."""
|
|
86
88
|
try:
|
|
87
89
|
dir_name = os.path.dirname(filepath) or "."
|
|
90
|
+
os.makedirs(dir_name, exist_ok=True)
|
|
91
|
+
sanitized = sanitize_for_json(data)
|
|
88
92
|
fd, temp_path = tempfile.mkstemp(dir=dir_name)
|
|
89
93
|
with os.fdopen(fd, 'w', encoding='utf-8') as f:
|
|
90
|
-
json.dump(
|
|
94
|
+
json.dump(sanitized, f, indent=2)
|
|
91
95
|
os.replace(temp_path, filepath)
|
|
92
96
|
except Exception as e:
|
|
93
97
|
print(f"[Atomic JSON Write Error] {e}")
|
|
@@ -398,7 +402,7 @@ class FaviconCache:
|
|
|
398
402
|
try:
|
|
399
403
|
url = f"https://www.google.com/s2/favicons?domain={urllib.parse.quote(host)}&sz=32"
|
|
400
404
|
req = urllib.request.Request(url, headers={"User-Agent": SETTINGS.get_user_agent()})
|
|
401
|
-
with urllib.request.urlopen(req, timeout=
|
|
405
|
+
with urllib.request.urlopen(req, timeout=3.0) as resp:
|
|
402
406
|
raw = resp.read()
|
|
403
407
|
if raw:
|
|
404
408
|
img = Cocoa.NSImage.alloc().initWithData_(Foundation.NSData.dataWithBytes_length_(raw, len(raw)))
|
|
@@ -413,14 +417,58 @@ class FaviconCache:
|
|
|
413
417
|
# ============================================================================
|
|
414
418
|
# INJECTED SCRIPTS & FULLSCREEN UTILITIES
|
|
415
419
|
# ============================================================================
|
|
420
|
+
COMMAND_CLICK_JS = """
|
|
421
|
+
(function() {
|
|
422
|
+
function findAnchor(el) {
|
|
423
|
+
while (el && el !== document) {
|
|
424
|
+
if (el.tagName === 'A' && el.href) return el;
|
|
425
|
+
el = el.parentElement;
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
document.addEventListener('click', function(e) {
|
|
430
|
+
if (e.metaKey || e.ctrlKey || e.button === 1) {
|
|
431
|
+
const a = findAnchor(e.target);
|
|
432
|
+
if (a && a.href && !a.href.startsWith('javascript:')) {
|
|
433
|
+
e.preventDefault();
|
|
434
|
+
e.stopPropagation();
|
|
435
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {
|
|
436
|
+
window.webkit.messageHandlers.bridge.postMessage({cmd: 'open_url', url: a.href});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}, true);
|
|
441
|
+
document.addEventListener('auxclick', function(e) {
|
|
442
|
+
if (e.button === 1) {
|
|
443
|
+
const a = findAnchor(e.target);
|
|
444
|
+
if (a && a.href && !a.href.startsWith('javascript:')) {
|
|
445
|
+
e.preventDefault();
|
|
446
|
+
e.stopPropagation();
|
|
447
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {
|
|
448
|
+
window.webkit.messageHandlers.bridge.postMessage({cmd: 'open_url', url: a.href});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}, true);
|
|
453
|
+
})();
|
|
454
|
+
"""
|
|
455
|
+
|
|
416
456
|
AUDIO_MONITOR_JS = """
|
|
417
457
|
document.addEventListener('play', (e) => {
|
|
418
|
-
if (e.target && !e.target.muted
|
|
458
|
+
if (e.target && !e.target.muted && window.webkit?.messageHandlers?.audio) {
|
|
459
|
+
window.webkit.messageHandlers.audio.postMessage({cmd:'audio_play'});
|
|
460
|
+
}
|
|
461
|
+
}, true);
|
|
462
|
+
document.addEventListener('pause', () => {
|
|
463
|
+
if (window.webkit?.messageHandlers?.audio) window.webkit.messageHandlers.audio.postMessage({cmd:'audio_pause'});
|
|
464
|
+
}, true);
|
|
465
|
+
document.addEventListener('ended', () => {
|
|
466
|
+
if (window.webkit?.messageHandlers?.audio) window.webkit.messageHandlers.audio.postMessage({cmd:'audio_pause'});
|
|
419
467
|
}, true);
|
|
420
|
-
document.addEventListener('pause', () => window.webkit.messageHandlers.audio.postMessage({cmd:'audio_pause'}), true);
|
|
421
|
-
document.addEventListener('ended', () => window.webkit.messageHandlers.audio.postMessage({cmd:'audio_pause'}), true);
|
|
422
468
|
document.addEventListener('volumechange', (e) => {
|
|
423
|
-
if (e.target && e.target.muted
|
|
469
|
+
if (e.target && e.target.muted && window.webkit?.messageHandlers?.audio) {
|
|
470
|
+
window.webkit.messageHandlers.audio.postMessage({cmd:'audio_pause'});
|
|
471
|
+
}
|
|
424
472
|
}, true);
|
|
425
473
|
"""
|
|
426
474
|
|
|
@@ -450,7 +498,7 @@ document.addEventListener('submit', (e) => {
|
|
|
450
498
|
if (!e.target || !e.target.querySelector) return;
|
|
451
499
|
let pw = e.target.querySelector('input[type="password"]');
|
|
452
500
|
let user = e.target.querySelector('input[type="text"], input[type="email"]');
|
|
453
|
-
if (pw && pw.value) {
|
|
501
|
+
if (pw && pw.value && window.webkit?.messageHandlers?.bridge) {
|
|
454
502
|
window.webkit.messageHandlers.bridge.postMessage({
|
|
455
503
|
cmd: 'prompt_save_password', host: location.hostname,
|
|
456
504
|
username: user ? user.value : 'unknown', password: pw.value
|
|
@@ -841,8 +889,7 @@ class BookmarksBarView(Cocoa.NSView):
|
|
|
841
889
|
if not isinstance(b, dict) or not b.get("url"):
|
|
842
890
|
continue
|
|
843
891
|
title = b.get("title", b["url"])[:24]
|
|
844
|
-
|
|
845
|
-
btn_w = max(40, min(140, len(title) * 7.5 + 14))
|
|
892
|
+
btn_w = max(50, min(160, len(title) * 7.5 + 26))
|
|
846
893
|
btn = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(x, 2, btn_w, h - 4))
|
|
847
894
|
btn.setTitle_(title)
|
|
848
895
|
btn.setBezelStyle_(Cocoa.NSBezelStyleInline)
|
|
@@ -850,10 +897,29 @@ class BookmarksBarView(Cocoa.NSView):
|
|
|
850
897
|
btn.setTarget_(self)
|
|
851
898
|
btn.setAction_("onBookmarkClick:")
|
|
852
899
|
btn.setToolTip_(b.get("url", ""))
|
|
900
|
+
|
|
901
|
+
# Dynamically query and bind favicon
|
|
902
|
+
try:
|
|
903
|
+
host = urllib.parse.urlparse(b.get("url", "")).netloc
|
|
904
|
+
if host:
|
|
905
|
+
FaviconCache.get_favicon(host, lambda img, b_ref=btn: self._apply_button_icon(b_ref, img))
|
|
906
|
+
except Exception:
|
|
907
|
+
pass
|
|
908
|
+
|
|
853
909
|
self.addSubview_(btn)
|
|
854
910
|
x += btn_w + 4.0
|
|
855
911
|
self.setNeedsDisplay_(True)
|
|
856
912
|
|
|
913
|
+
def _apply_button_icon(self, btn, img):
|
|
914
|
+
if img and btn:
|
|
915
|
+
try:
|
|
916
|
+
scaled = img.copy()
|
|
917
|
+
scaled.setSize_(Cocoa.NSMakeSize(14, 14))
|
|
918
|
+
btn.setImage_(scaled)
|
|
919
|
+
btn.setImagePosition_(Cocoa.NSImageLeft)
|
|
920
|
+
except Exception:
|
|
921
|
+
pass
|
|
922
|
+
|
|
857
923
|
def onBookmarkClick_(self, sender):
|
|
858
924
|
url = sender.toolTip()
|
|
859
925
|
if url and self.controller:
|
|
@@ -968,7 +1034,6 @@ class InternalSchemeHandler(Cocoa.NSObject):
|
|
|
968
1034
|
self.active_tasks.discard(task)
|
|
969
1035
|
|
|
970
1036
|
def get_terminal_html(self):
|
|
971
|
-
# Fullscreen responsive xterm with ResizeObserver to ensure vim/less utilize entire viewport
|
|
972
1037
|
bg = "#1e1e2e"
|
|
973
1038
|
fg = "#c792ea"
|
|
974
1039
|
return f"""<!DOCTYPE html><html><head><meta charset='utf-8'>
|
|
@@ -992,6 +1057,19 @@ const fit = new FitAddon.FitAddon();
|
|
|
992
1057
|
term.loadAddon(fit);
|
|
993
1058
|
term.open(document.getElementById('t'));
|
|
994
1059
|
|
|
1060
|
+
// Guarantee Esc (0x1b) is sent directly to child PTY (vim/nano/fzf) without being swallowed
|
|
1061
|
+
term.attachCustomKeyEventHandler(e => {{
|
|
1062
|
+
if (e.key === 'Escape' || e.keyCode === 27) {{
|
|
1063
|
+
if (e.type === 'keydown') {{
|
|
1064
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.terminalInput) {{
|
|
1065
|
+
window.webkit.messageHandlers.terminalInput.postMessage('\\x1b');
|
|
1066
|
+
}}
|
|
1067
|
+
}}
|
|
1068
|
+
return false;
|
|
1069
|
+
}}
|
|
1070
|
+
return true;
|
|
1071
|
+
}});
|
|
1072
|
+
|
|
995
1073
|
const postResize = () => {{
|
|
996
1074
|
try {{
|
|
997
1075
|
fit.fit();
|
|
@@ -1001,9 +1079,7 @@ const postResize = () => {{
|
|
|
1001
1079
|
}} catch(e) {{}}
|
|
1002
1080
|
}};
|
|
1003
1081
|
|
|
1004
|
-
const ro = new ResizeObserver(() => {{
|
|
1005
|
-
postResize();
|
|
1006
|
-
}});
|
|
1082
|
+
const ro = new ResizeObserver(() => {{ postResize(); }});
|
|
1007
1083
|
ro.observe(document.getElementById('t'));
|
|
1008
1084
|
|
|
1009
1085
|
setTimeout(postResize, 30);
|
|
@@ -1038,22 +1114,24 @@ window.writeToTerm = b => {{
|
|
|
1038
1114
|
u = b["url"]
|
|
1039
1115
|
t = b.get("title", u)
|
|
1040
1116
|
safe_u = json.dumps(u)
|
|
1041
|
-
|
|
1042
|
-
|
|
1117
|
+
host = urllib.parse.urlparse(u).netloc
|
|
1118
|
+
fav_tag = f"<img src='https://www.google.com/s2/favicons?domain={urllib.parse.quote(host)}&sz=32' width='16' height='16' style='vertical-align:middle;margin-right:8px;border-radius:3px;' onerror='this.style.display=\"none\"'>" if host else ""
|
|
1119
|
+
del_call = f"if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({{cmd: 'delete_bookmark', url: {safe_u}}}); setTimeout(() => location.reload(), 100);"
|
|
1120
|
+
items.append(f"<li style='display:flex;align-items:center;padding:6px 0;'><span style='flex:1;display:flex;align-items:center;'>{fav_tag}<a href='{html.escape(u)}' style='color:#1a73e8;text-decoration:none;'>{html.escape(t)}</a></span> <button onclick='{del_call}' style='margin-left:12px;padding:3px 8px;'>Delete</button></li>")
|
|
1043
1121
|
rows = "".join(items)
|
|
1044
|
-
return f"<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>Bookmarks</h2><div style='margin-bottom:15px;'><input id='t' placeholder='Title'><input id='u' placeholder='https://...'><button onclick='window.webkit.messageHandlers.bridge.postMessage({{cmd:\"add_bookmark\",title:document.getElementById(\"t\").value,url:document.getElementById(\"u\").value}});setTimeout(()=>location.reload(),100)'>Add</button></div><ul>{rows or '<li>No bookmarks</li>'}</ul></body></html>"
|
|
1122
|
+
return f"<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>Bookmarks</h2><div style='margin-bottom:15px;'><input id='t' placeholder='Title' style='padding:4px 8px;margin-right:8px;'><input id='u' placeholder='https://...' style='padding:4px 8px;margin-right:8px;width:260px;'><button onclick='if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({{cmd:\"add_bookmark\",title:document.getElementById(\"t\").value,url:document.getElementById(\"u\").value}});setTimeout(()=>location.reload(),100)' style='padding:4px 12px;'>Add</button></div><ul style='list-style:none;padding:0;'>{rows or '<li>No bookmarks</li>'}</ul></body></html>"
|
|
1045
1123
|
|
|
1046
1124
|
def get_history_html(self):
|
|
1047
1125
|
records = query_history()
|
|
1048
1126
|
items = []
|
|
1049
1127
|
for r in records:
|
|
1050
|
-
del_call = f"window.webkit.messageHandlers.bridge.postMessage({{cmd: 'delete_history_item', id: {r[0]}}}); this.closest('tr').remove();"
|
|
1128
|
+
del_call = f"if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({{cmd: 'delete_history_item', id: {r[0]}}}); this.closest('tr').remove();"
|
|
1051
1129
|
items.append(f"<tr><td>{r[3]}</td><td><a href='{html.escape(r[1])}'>{html.escape(r[2])}</a></td><td><button onclick='{del_call}'>✕</button></td></tr>")
|
|
1052
1130
|
rows = "".join(items)
|
|
1053
|
-
return f"<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>History</h2><button onclick=\"window.webkit.messageHandlers.bridge.postMessage({{cmd:'clear_browsing_data',targets:['history']}});setTimeout(()=>location.reload(),100)\">Clear All</button><table style='width:100%;margin-top:15px;'>{rows or '<tr><td>No history</td></tr>'}</table></body></html>"
|
|
1131
|
+
return f"<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>History</h2><button onclick=\"if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({{cmd:'clear_browsing_data',targets:['history']}});setTimeout(()=>location.reload(),100)\">Clear All</button><table style='width:100%;margin-top:15px;'>{rows or '<tr><td>No history</td></tr>'}</table></body></html>"
|
|
1054
1132
|
|
|
1055
1133
|
def get_downloads_html(self):
|
|
1056
|
-
return "<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>Downloads</h2><div id='dl-list'>No active downloads.</div><script>const dls={};window.updateDownload=(id,name,path,status,progress)=>{dls[id]={name,path,status,progress};render()};function render(){const el=document.getElementById('dl-list');el.innerHTML=Object.values(dls).map(d=>`<div style='border:1px solid #ccc;padding:10px;margin-bottom:8px;'><b>${d.name}</b> (${d.status}) <button onclick=\"window.webkit.messageHandlers.bridge.postMessage({cmd:'dl_show',path:'${d.path}'})\">Show in Finder</button></div>`).join('')};window.webkit.messageHandlers.bridge.postMessage({cmd:'dl_request_sync'});</script></body></html>"
|
|
1134
|
+
return "<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>Downloads</h2><div id='dl-list'>No active downloads.</div><script>const dls={};window.updateDownload=(id,name,path,status,progress)=>{dls[id]={name,path,status,progress};render()};function render(){const el=document.getElementById('dl-list');el.innerHTML=Object.values(dls).map(d=>`<div style='border:1px solid #ccc;padding:10px;margin-bottom:8px;'><b>${d.name}</b> (${d.status}) <button onclick=\"if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({cmd:'dl_show',path:'${d.path}'})\">Show in Finder</button></div>`).join('')};if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({cmd:'dl_request_sync'});</script></body></html>"
|
|
1057
1135
|
|
|
1058
1136
|
def get_extensions_html(self):
|
|
1059
1137
|
scripts = get_installed_userscripts()
|
|
@@ -1062,8 +1140,8 @@ window.writeToTerm = b => {{
|
|
|
1062
1140
|
f = s["file"]
|
|
1063
1141
|
safe_f = json.dumps(f)
|
|
1064
1142
|
checked = "checked" if s["enabled"] else ""
|
|
1065
|
-
toggle_call = f"window.webkit.messageHandlers.bridge.postMessage({{cmd: 'toggle_script', file: {safe_f}, enabled: this.checked}})"
|
|
1066
|
-
del_call = f"if(confirm('Delete?')){{window.webkit.messageHandlers.bridge.postMessage({{cmd: 'delete_script', file: {safe_f}}}); setTimeout(() => location.reload(), 100);}}"
|
|
1143
|
+
toggle_call = f"if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({{cmd: 'toggle_script', file: {safe_f}, enabled: this.checked}})"
|
|
1144
|
+
del_call = f"if(confirm('Delete?')){{if(window.webkit?.messageHandlers?.bridge) window.webkit.messageHandlers.bridge.postMessage({{cmd: 'delete_script', file: {safe_f}}}); setTimeout(() => location.reload(), 100);}}"
|
|
1067
1145
|
items.append(
|
|
1068
1146
|
f"<div style='border:1px solid #ccc;padding:10px;margin-bottom:8px;'>"
|
|
1069
1147
|
f"<b>{html.escape(s['name'])}</b> ({html.escape(f)}) "
|
|
@@ -1072,7 +1150,7 @@ window.writeToTerm = b => {{
|
|
|
1072
1150
|
f"</div>"
|
|
1073
1151
|
)
|
|
1074
1152
|
rows = "".join(items)
|
|
1075
|
-
return f"<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>Extensions & Userscripts</h2><button onclick='let id=prompt(\"CRX ID or URL:\");if(id)window.webkit.messageHandlers.bridge.postMessage({{cmd:\"install_crx\",id:id}})'>Install CRX</button><div style='margin-top:15px;'>{rows or 'No userscripts installed.'}</div></body></html>"
|
|
1153
|
+
return f"<!DOCTYPE html><html><body style='font-family:-apple-system,sans-serif;padding:30px;'><h2>Extensions & Userscripts</h2><button onclick='let id=prompt(\"CRX ID or URL:\");if(id && window.webkit?.messageHandlers?.bridge)window.webkit.messageHandlers.bridge.postMessage({{cmd:\"install_crx\",id:id}})'>Install CRX</button><div style='margin-top:15px;'>{rows or 'No userscripts installed.'}</div></body></html>"
|
|
1076
1154
|
|
|
1077
1155
|
# ============================================================================
|
|
1078
1156
|
# WEBKIT SUBCLASS & IMAGE CONTEXT MENU
|
|
@@ -1200,7 +1278,7 @@ class OmniboxSuggestionView(Cocoa.NSView):
|
|
|
1200
1278
|
opts = Cocoa.NSTrackingMouseMoved | Cocoa.NSTrackingActiveAlways | Cocoa.NSTrackingInVisibleRect
|
|
1201
1279
|
self.tracking_area = Cocoa.NSTrackingArea.alloc().initWithRect_options_owner_userInfo_(Cocoa.NSZeroRect, opts, self, None)
|
|
1202
1280
|
self.addTrackingArea_(self.tracking_area)
|
|
1203
|
-
objc.super(
|
|
1281
|
+
objc.super(ChromeTabView, self).updateTrackingAreas() if hasattr(objc.super(ChromeTabView, self), 'updateTrackingAreas') else None
|
|
1204
1282
|
|
|
1205
1283
|
def mouseMoved_(self, event):
|
|
1206
1284
|
loc = self.convertPoint_fromView_(event.locationInWindow(), None)
|
|
@@ -1274,9 +1352,10 @@ class OmniboxPanelCoordinator:
|
|
|
1274
1352
|
|
|
1275
1353
|
def fetch_remote():
|
|
1276
1354
|
try:
|
|
1277
|
-
|
|
1355
|
+
# Secured endpoint via HTTPS to avoid keystroke sniffing
|
|
1356
|
+
url = f"https://suggestqueries.google.com/complete/search?client=chrome&q={urllib.parse.quote(query_str)}"
|
|
1278
1357
|
req = urllib.request.Request(url, headers={"User-Agent": UA_PRESETS["chrome_mac"]})
|
|
1279
|
-
with urllib.request.urlopen(req, timeout=0
|
|
1358
|
+
with urllib.request.urlopen(req, timeout=1.0) as resp:
|
|
1280
1359
|
data = json.loads(resp.read().decode('utf-8'))
|
|
1281
1360
|
remote = [{"type": "search", "text": s, "url": s} for s in data[1][:5]]
|
|
1282
1361
|
if req_id == self.current_req_id:
|
|
@@ -1551,7 +1630,6 @@ class QuickSwitcherController(Cocoa.NSWindowController):
|
|
|
1551
1630
|
elif sel == "insertNewline:":
|
|
1552
1631
|
if self.filtered_tabs and self.selected_index >= 0:
|
|
1553
1632
|
t = self.filtered_tabs[self.selected_index]
|
|
1554
|
-
# Fixed: Resolve current tab index dynamically by ID to avoid desync/crash
|
|
1555
1633
|
cur_idx = t["wc"].get_tab_index_by_id(t["id"])
|
|
1556
1634
|
if cur_idx is not None:
|
|
1557
1635
|
t["wc"].window().makeKeyAndOrderFront_(None)
|
|
@@ -1601,7 +1679,6 @@ class PiPPanelController(Cocoa.NSWindowController):
|
|
|
1601
1679
|
self.window().makeKeyAndOrderFront_(None)
|
|
1602
1680
|
|
|
1603
1681
|
def exec_js(self, js):
|
|
1604
|
-
# Fixed: Prefer active window controller or stored browser controller
|
|
1605
1682
|
app_delegate = Cocoa.NSApp.delegate()
|
|
1606
1683
|
wc = None
|
|
1607
1684
|
key_win = Cocoa.NSApp.keyWindow()
|
|
@@ -1770,7 +1847,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1770
1847
|
self.home_btn.setHidden_(not SETTINGS.settings.get("show_home_button", False))
|
|
1771
1848
|
|
|
1772
1849
|
omni_x = 132 if SETTINGS.settings.get("show_home_button", False) else 102
|
|
1773
|
-
# Clean uniform 30px step for toolbar buttons, omnibox expands dynamically
|
|
1774
1850
|
self.omnibox_container = Cocoa.NSView.alloc().initWithFrame_(Cocoa.NSMakeRect(omni_x, 3, max(180, w - omni_x - 162), 28))
|
|
1775
1851
|
self.toolbar.addSubview_(self.omnibox_container)
|
|
1776
1852
|
|
|
@@ -1798,7 +1874,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1798
1874
|
self.star_btn.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
|
|
1799
1875
|
self.omnibox_container.addSubview_(self.star_btn)
|
|
1800
1876
|
|
|
1801
|
-
# Fixed: Buttons pushed cleanly to the right edge with uniform 30px spacing
|
|
1802
1877
|
self.pip_btn = btn("⧉", w - 154, "onTogglePiP:", "Media Controls")
|
|
1803
1878
|
self.dl_btn = btn("⬇", w - 124, "onShowDownloads:", "Downloads")
|
|
1804
1879
|
self.share_btn = btn("↗", w - 94, "onShareCurrentPage:", "Share Page")
|
|
@@ -1853,7 +1928,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1853
1928
|
omni_x = 132 if show_home else 102
|
|
1854
1929
|
tb_w = self.toolbar.bounds().size.width
|
|
1855
1930
|
|
|
1856
|
-
# Fixed: Expand Omnibox to fill empty space, eliminated gap between zen and menu buttons
|
|
1857
1931
|
if hasattr(self, 'omnibox_container'):
|
|
1858
1932
|
box_w = max(180, tb_w - omni_x - 162)
|
|
1859
1933
|
self.omnibox_container.setFrame_(Cocoa.NSMakeRect(omni_x, 3, box_w, 28))
|
|
@@ -1878,7 +1952,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1878
1952
|
sub.removeFromSuperview()
|
|
1879
1953
|
w = self.tab_strip.bounds().size.width
|
|
1880
1954
|
is_vert = SETTINGS.settings.get("vertical_tabs_enabled", False)
|
|
1881
|
-
# Because traffic lights sit in the title bar above, horizontal tabs start cleanly at x = 8.0
|
|
1882
1955
|
x = 8.0
|
|
1883
1956
|
tab_h = 28 if not is_vert else 32
|
|
1884
1957
|
y = self.tab_strip.bounds().size.height - 38 - tab_h if is_vert else 3
|
|
@@ -1965,6 +2038,8 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1965
2038
|
uc.addScriptMessageHandler_name_(self, "terminalInput")
|
|
1966
2039
|
uc.addScriptMessageHandler_name_(self, "terminalResize")
|
|
1967
2040
|
|
|
2041
|
+
# Injected scripts: Command+click capture, audio monitoring, context menus, and fullscreen handlers
|
|
2042
|
+
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(COMMAND_CLICK_JS, WebKit.WKUserScriptInjectionTimeAtDocumentStart, False))
|
|
1968
2043
|
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(AUDIO_MONITOR_JS, WebKit.WKUserScriptInjectionTimeAtDocumentEnd, False))
|
|
1969
2044
|
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(IMAGE_CONTEXT_MENU_JS, WebKit.WKUserScriptInjectionTimeAtDocumentStart, False))
|
|
1970
2045
|
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(KEYCHAIN_AUTOSAVE_JS, WebKit.WKUserScriptInjectionTimeAtDocumentEnd, False))
|
|
@@ -2009,7 +2084,7 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2009
2084
|
except Exception:
|
|
2010
2085
|
pass
|
|
2011
2086
|
|
|
2012
|
-
initial_zoom = float(SETTINGS.settings.get("default_zoom", 0
|
|
2087
|
+
initial_zoom = float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2013
2088
|
tab = {
|
|
2014
2089
|
"id": str(uuid.uuid4()), "webview": wv, "url": url, "title": "New Tab",
|
|
2015
2090
|
"favicon": None, "pinned": pinned, "group_id": None, "zoom": initial_zoom,
|
|
@@ -2049,8 +2124,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2049
2124
|
self.wake_tab(index)
|
|
2050
2125
|
wv = tab["webview"]
|
|
2051
2126
|
|
|
2052
|
-
# Fixed: Keep webviews attached and toggle visibility with setHidden_
|
|
2053
|
-
# This completely preserves DOM focus, text inputs, and render state
|
|
2054
2127
|
for i, t in enumerate(self.tabs):
|
|
2055
2128
|
twv = t.get("webview")
|
|
2056
2129
|
if twv:
|
|
@@ -2066,7 +2139,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2066
2139
|
self.update_progress_for_webview(wv)
|
|
2067
2140
|
self.relayout_tabs()
|
|
2068
2141
|
|
|
2069
|
-
# Fixed: Restore keyboard focus and text cursor to the active input element automatically
|
|
2070
2142
|
self.window().makeFirstResponder_(wv)
|
|
2071
2143
|
wv.evaluateJavaScript_completionHandler_(
|
|
2072
2144
|
"if (document.activeElement && typeof document.activeElement.focus === 'function') { document.activeElement.focus(); }",
|
|
@@ -2258,7 +2330,7 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2258
2330
|
def apply_zoom_to_webview(self, wv, tab=None):
|
|
2259
2331
|
if not wv:
|
|
2260
2332
|
return
|
|
2261
|
-
target_zoom = tab.get("zoom", float(SETTINGS.settings.get("default_zoom", 0
|
|
2333
|
+
target_zoom = tab.get("zoom", float(SETTINGS.settings.get("default_zoom", 1.0))) if tab else float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2262
2334
|
target_zoom = max(0.5, min(2.0, target_zoom))
|
|
2263
2335
|
try:
|
|
2264
2336
|
if hasattr(wv, "setAllowsMagnification_"): wv.setAllowsMagnification_(False)
|
|
@@ -2268,18 +2340,19 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2268
2340
|
pass
|
|
2269
2341
|
|
|
2270
2342
|
def restore_normal_zoom(self):
|
|
2343
|
+
if self.is_active_tab_terminal():
|
|
2344
|
+
return
|
|
2271
2345
|
wv = self.get_active_webview()
|
|
2272
2346
|
if wv:
|
|
2273
|
-
self.
|
|
2347
|
+
tab = self.tabs[self.active_tab_index] if (0 <= self.active_tab_index < len(self.tabs)) else None
|
|
2348
|
+
self.apply_zoom_to_webview(wv, tab)
|
|
2274
2349
|
wv.evaluateJavaScript_completionHandler_(REFLOW_RESTORE_JS, None)
|
|
2275
2350
|
|
|
2276
2351
|
def handle_fullscreen_enter(self, auto_hide_bars=True):
|
|
2277
2352
|
if auto_hide_bars:
|
|
2278
2353
|
if not self.tab_bar_visible:
|
|
2279
|
-
# Tab was already in Zen mode before entering fullscreen
|
|
2280
2354
|
self.zen_auto_entered = False
|
|
2281
2355
|
else:
|
|
2282
|
-
# Tab was in normal mode, auto-enter Zen mode for fullscreen
|
|
2283
2356
|
self.zen_auto_entered = True
|
|
2284
2357
|
self.tab_bar_visible = False
|
|
2285
2358
|
self.relayout_views()
|
|
@@ -2292,22 +2365,27 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2292
2365
|
pass
|
|
2293
2366
|
|
|
2294
2367
|
def handle_fullscreen_exit(self, restore_bars=True):
|
|
2295
|
-
if restore_bars:
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
self.zen_auto_entered = False
|
|
2300
|
-
self.relayout_views()
|
|
2301
|
-
# If tab was already in Zen mode before fullscreen, keep Zen mode on!
|
|
2368
|
+
if restore_bars and (self.zen_auto_entered or not self.tab_bar_visible):
|
|
2369
|
+
self.tab_bar_visible = True
|
|
2370
|
+
self.zen_auto_entered = False
|
|
2371
|
+
self.relayout_views()
|
|
2302
2372
|
wv = self.get_active_webview()
|
|
2303
2373
|
if wv:
|
|
2304
2374
|
wv.evaluateJavaScript_completionHandler_("if (window.setTerminalFullscreen) window.setTerminalFullscreen(false);", None)
|
|
2305
2375
|
self.restore_normal_zoom()
|
|
2306
2376
|
|
|
2377
|
+
def is_active_tab_terminal(self):
|
|
2378
|
+
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2379
|
+
url = self.tabs[self.active_tab_index].get("url") or ""
|
|
2380
|
+
return "browser://terminal" in url
|
|
2381
|
+
return False
|
|
2382
|
+
|
|
2307
2383
|
def handle_escape_key(self):
|
|
2308
2384
|
if self.find_bar and not self.find_bar.isHidden():
|
|
2309
2385
|
self.find_bar.setHidden_(True)
|
|
2310
2386
|
return
|
|
2387
|
+
if self.is_active_tab_terminal():
|
|
2388
|
+
return
|
|
2311
2389
|
wv = self.get_active_webview()
|
|
2312
2390
|
if not self.tab_bar_visible:
|
|
2313
2391
|
self.handle_fullscreen_exit(restore_bars=True)
|
|
@@ -2339,21 +2417,21 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2339
2417
|
def onZoomIn_(self, sender=None):
|
|
2340
2418
|
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2341
2419
|
tab = self.tabs[self.active_tab_index]
|
|
2342
|
-
z = min(2.0, round(tab.get("zoom", 0
|
|
2420
|
+
z = min(2.0, round(tab.get("zoom", 1.0) + 0.1, 2))
|
|
2343
2421
|
tab["zoom"] = z
|
|
2344
2422
|
self.apply_zoom_to_webview(tab.get("webview"), tab)
|
|
2345
2423
|
|
|
2346
2424
|
def onZoomOut_(self, sender=None):
|
|
2347
2425
|
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2348
2426
|
tab = self.tabs[self.active_tab_index]
|
|
2349
|
-
z = max(0.5, round(tab.get("zoom", 0
|
|
2427
|
+
z = max(0.5, round(tab.get("zoom", 1.0) - 0.1, 2))
|
|
2350
2428
|
tab["zoom"] = z
|
|
2351
2429
|
self.apply_zoom_to_webview(tab.get("webview"), tab)
|
|
2352
2430
|
|
|
2353
2431
|
def onResetZoom_(self, sender=None):
|
|
2354
2432
|
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2355
2433
|
tab = self.tabs[self.active_tab_index]
|
|
2356
|
-
tab["zoom"] = float(SETTINGS.settings.get("default_zoom", 0
|
|
2434
|
+
tab["zoom"] = float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2357
2435
|
self.apply_zoom_to_webview(tab.get("webview"), tab)
|
|
2358
2436
|
self.restore_normal_zoom()
|
|
2359
2437
|
|
|
@@ -2377,7 +2455,9 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2377
2455
|
menu.addItemWithTitle_action_keyEquivalent_("Bookmark Current Page", "onBookmarkCurrentPage:", "d").setTarget_(self)
|
|
2378
2456
|
bm_text = "Hide Bookmarks Bar" if SETTINGS.settings.get("bookmarks_bar_visible", True) else "Show Bookmarks Bar"
|
|
2379
2457
|
menu.addItemWithTitle_action_keyEquivalent_(bm_text, "toggleBookmarksBar:", "b").setTarget_(self)
|
|
2380
|
-
menu.addItemWithTitle_action_keyEquivalent_("Toggle Zen Mode", "onToggleZenMode:", "")
|
|
2458
|
+
zen_item = menu.addItemWithTitle_action_keyEquivalent_("Toggle Zen Mode", "onToggleZenMode:", "\x1b")
|
|
2459
|
+
zen_item.setKeyEquivalentModifierMask_(Cocoa.NSEventModifierFlagOption)
|
|
2460
|
+
zen_item.setTarget_(self)
|
|
2381
2461
|
menu.addItemWithTitle_action_keyEquivalent_("Find in Page", "onFindInPage:", "f").setTarget_(self)
|
|
2382
2462
|
menu.addItem_(Cocoa.NSMenuItem.separatorItem())
|
|
2383
2463
|
menu.addItemWithTitle_action_keyEquivalent_("Settings", "openSettings:", ",").setTarget_(self)
|
|
@@ -2613,10 +2693,14 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2613
2693
|
return
|
|
2614
2694
|
elif name == "terminalResize":
|
|
2615
2695
|
b = message.body()
|
|
2616
|
-
TERMINAL_MANAGER.get_or_create(
|
|
2696
|
+
TERMINAL_MANAGER.get_or_create(
|
|
2697
|
+
message.webView(),
|
|
2698
|
+
b.get("cols") if hasattr(b, "get") else None,
|
|
2699
|
+
b.get("rows") if hasattr(b, "get") else None
|
|
2700
|
+
)
|
|
2617
2701
|
return
|
|
2618
2702
|
elif name == "audio":
|
|
2619
|
-
b = message.body() if
|
|
2703
|
+
b = message.body() if hasattr(message.body(), "get") else {}
|
|
2620
2704
|
has_a = (b.get("cmd") == "audio_play")
|
|
2621
2705
|
for t in self.tabs:
|
|
2622
2706
|
if t.get("webview") == message.webView():
|
|
@@ -2626,11 +2710,18 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2626
2710
|
return
|
|
2627
2711
|
|
|
2628
2712
|
body = message.body()
|
|
2629
|
-
cmd =
|
|
2713
|
+
cmd = None
|
|
2714
|
+
if hasattr(body, "get"):
|
|
2715
|
+
cmd = body.get("cmd")
|
|
2716
|
+
elif isinstance(body, dict):
|
|
2717
|
+
cmd = body.get("cmd")
|
|
2718
|
+
if cmd is not None:
|
|
2719
|
+
cmd = str(cmd)
|
|
2630
2720
|
|
|
2631
2721
|
if cmd == "open_url":
|
|
2632
|
-
|
|
2633
|
-
|
|
2722
|
+
target_url = body.get("url")
|
|
2723
|
+
if target_url:
|
|
2724
|
+
self.add_tab(target_url)
|
|
2634
2725
|
elif cmd == "toggle_fullscreen_terminal":
|
|
2635
2726
|
self.toggle_tab_bar()
|
|
2636
2727
|
elif cmd == "context_target_image":
|
|
@@ -2676,11 +2767,16 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2676
2767
|
if wv:
|
|
2677
2768
|
wv.evaluateJavaScript_completionHandler_(f"let pw = document.querySelector('input[type=password]'); let user = document.querySelector('input[type=text], input[type=email]'); if(pw) pw.value = '{pwd}'; if(user) user.value = '{saved_user}';", None)
|
|
2678
2769
|
elif cmd == "save_setting":
|
|
2679
|
-
|
|
2770
|
+
raw_key, raw_val = body.get("key"), body.get("val")
|
|
2771
|
+
if raw_key is None:
|
|
2772
|
+
return
|
|
2773
|
+
key = str(raw_key)
|
|
2774
|
+
val = sanitize_for_json(raw_val)
|
|
2680
2775
|
if key == "download_path":
|
|
2681
2776
|
val = os.path.expanduser(str(val).strip())
|
|
2682
2777
|
SETTINGS.settings[key] = val
|
|
2683
2778
|
SETTINGS.save()
|
|
2779
|
+
|
|
2684
2780
|
if key in ("bookmarks_bar_visible", "show_home_button", "vertical_tabs_enabled"):
|
|
2685
2781
|
self.relayout_views()
|
|
2686
2782
|
elif key == "theme_mode":
|
|
@@ -2693,21 +2789,35 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2693
2789
|
for sub in c.tab_strip.subviews():
|
|
2694
2790
|
sub.setNeedsDisplay_(True)
|
|
2695
2791
|
elif key in ("default_zoom",):
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2792
|
+
new_zoom = float(val)
|
|
2793
|
+
for t in self.tabs:
|
|
2794
|
+
t["zoom"] = new_zoom
|
|
2795
|
+
twv = t.get("webview")
|
|
2796
|
+
if twv:
|
|
2797
|
+
self.apply_zoom_to_webview(twv, t)
|
|
2699
2798
|
elif key in ("user_agent_mode", "custom_user_agent"):
|
|
2700
2799
|
ua = SETTINGS.get_user_agent()
|
|
2701
2800
|
for t in self.tabs:
|
|
2702
2801
|
if t.get("webview"):
|
|
2703
2802
|
t["webview"].setCustomUserAgent_(ua)
|
|
2803
|
+
elif key == "js_enabled":
|
|
2804
|
+
js_flag = bool(val)
|
|
2805
|
+
for t in self.tabs:
|
|
2806
|
+
twv = t.get("webview")
|
|
2807
|
+
if twv and hasattr(twv.configuration().preferences(), "setJavaScriptEnabled_"):
|
|
2808
|
+
twv.configuration().preferences().setJavaScriptEnabled_(js_flag)
|
|
2809
|
+
elif key == "block_popups":
|
|
2810
|
+
pop_flag = not bool(val)
|
|
2811
|
+
for t in self.tabs:
|
|
2812
|
+
twv = t.get("webview")
|
|
2813
|
+
if twv and hasattr(twv.configuration().preferences(), "setJavaScriptCanOpenWindowsAutomatically_"):
|
|
2814
|
+
twv.configuration().preferences().setJavaScriptCanOpenWindowsAutomatically_(pop_flag)
|
|
2704
2815
|
elif key == "doh_provider":
|
|
2705
2816
|
with DNS_LOCK:
|
|
2706
2817
|
DNS_CACHE.clear()
|
|
2707
2818
|
elif key in ("adblock_enabled", "adblock_stevenblack_enabled", "adblock_base_enabled", "custom_adblock_domains"):
|
|
2708
2819
|
compile_webkit_adblock_rules()
|
|
2709
2820
|
elif cmd == "choose_download_directory":
|
|
2710
|
-
# Fixed: Asynchronous modal sheet targeting caller webview without blocking WebKit IPC
|
|
2711
2821
|
calling_wv = message.webView()
|
|
2712
2822
|
panel = Cocoa.NSOpenPanel.openPanel()
|
|
2713
2823
|
panel.setCanChooseDirectories_(True)
|
|
@@ -2718,22 +2828,22 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2718
2828
|
cur_dir = validate_and_get_download_dir()
|
|
2719
2829
|
panel.setDirectoryURL_(Foundation.NSURL.fileURLWithPath_(cur_dir))
|
|
2720
2830
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
)
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2831
|
+
res = panel.runModal()
|
|
2832
|
+
if res == Cocoa.NSModalResponseOK and panel.URLs():
|
|
2833
|
+
chosen_path = str(panel.URLs()[0].path())
|
|
2834
|
+
if os.access(chosen_path, os.W_OK):
|
|
2835
|
+
SETTINGS.settings["download_path"] = chosen_path
|
|
2836
|
+
SETTINGS.save()
|
|
2837
|
+
if calling_wv:
|
|
2838
|
+
path_json = json.dumps(chosen_path)
|
|
2839
|
+
calling_wv.evaluateJavaScript_completionHandler_(
|
|
2840
|
+
f"if (document.getElementById('dl-path-input')) document.getElementById('dl-path-input').value = {path_json};"
|
|
2841
|
+
f"if (document.getElementById('dl-path-display')) document.getElementById('dl-path-display').innerText = {path_json};"
|
|
2842
|
+
"showToast('Download directory updated');", None
|
|
2843
|
+
)
|
|
2844
|
+
else:
|
|
2845
|
+
if calling_wv:
|
|
2846
|
+
calling_wv.evaluateJavaScript_completionHandler_("showToast('Error: Selected directory is not writable');", None)
|
|
2737
2847
|
elif cmd == "search_blocked_domains":
|
|
2738
2848
|
matches = search_blocked_domains(body.get("query", ""), limit=100)
|
|
2739
2849
|
json_res = json.dumps(matches)
|
|
@@ -2750,11 +2860,13 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2750
2860
|
dispatch_to_main(on_done)
|
|
2751
2861
|
threading.Thread(target=fetch_task, daemon=True).start()
|
|
2752
2862
|
elif cmd == "clear_browsing_data":
|
|
2753
|
-
|
|
2863
|
+
raw_targets = body.get("targets", [])
|
|
2864
|
+
targets = list(raw_targets) if hasattr(raw_targets, "__iter__") else []
|
|
2754
2865
|
if "history" in targets:
|
|
2755
2866
|
clear_history()
|
|
2756
2867
|
if "favicons" in targets:
|
|
2757
2868
|
FaviconCache.clear()
|
|
2869
|
+
self.bookmarks_bar.reload_bookmarks()
|
|
2758
2870
|
if "dns_cache" in targets:
|
|
2759
2871
|
with DNS_LOCK:
|
|
2760
2872
|
DNS_CACHE.clear()
|
|
@@ -2764,10 +2876,12 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2764
2876
|
elif cmd == "reset_defaults":
|
|
2765
2877
|
SETTINGS.reset_defaults()
|
|
2766
2878
|
self.relayout_views()
|
|
2879
|
+
def_zoom = float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2767
2880
|
for tab in self.tabs:
|
|
2881
|
+
tab["zoom"] = def_zoom
|
|
2768
2882
|
wv = tab.get("webview")
|
|
2769
2883
|
if wv:
|
|
2770
|
-
self.apply_zoom_to_webview(wv)
|
|
2884
|
+
self.apply_zoom_to_webview(wv, tab)
|
|
2771
2885
|
wv = self.get_active_webview()
|
|
2772
2886
|
if wv:
|
|
2773
2887
|
wv.reload()
|
|
@@ -2848,9 +2962,10 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2848
2962
|
|
|
2849
2963
|
def webView_decidePolicyForNavigationAction_decisionHandler_(self, wv, action, handler):
|
|
2850
2964
|
mods = action.modifierFlags() if hasattr(action, "modifierFlags") else 0
|
|
2851
|
-
|
|
2965
|
+
cmd_pressed = bool(mods & Cocoa.NSEventModifierFlagCommand)
|
|
2966
|
+
if cmd_pressed or action.navigationType() == WebKit.WKNavigationTypeLinkActivated:
|
|
2852
2967
|
url = action.request().URL().absoluteString() if action.request().URL() else None
|
|
2853
|
-
if url:
|
|
2968
|
+
if cmd_pressed and url:
|
|
2854
2969
|
self.add_tab(url)
|
|
2855
2970
|
handler(WebKit.WKNavigationActionPolicyCancel)
|
|
2856
2971
|
return
|
|
@@ -3025,25 +3140,45 @@ class AppDelegate(Cocoa.NSObject):
|
|
|
3025
3140
|
Cocoa.NSEvent.addLocalMonitorForEventsMatchingMask_handler_(Cocoa.NSEventMaskLeftMouseDown, click_monitor)
|
|
3026
3141
|
|
|
3027
3142
|
def key_monitor(event):
|
|
3028
|
-
|
|
3143
|
+
raw_flags = event.modifierFlags()
|
|
3144
|
+
flags = raw_flags & Cocoa.NSEventModifierFlagDeviceIndependentFlagsMask
|
|
3029
3145
|
cmd = bool(flags & Cocoa.NSEventModifierFlagCommand)
|
|
3030
3146
|
shift = bool(flags & Cocoa.NSEventModifierFlagShift)
|
|
3031
3147
|
opt = bool(flags & Cocoa.NSEventModifierFlagOption)
|
|
3032
3148
|
key = event.keyCode()
|
|
3033
3149
|
chars = event.charactersIgnoringModifiers().lower() if event.charactersIgnoringModifiers() else ""
|
|
3034
|
-
win = Cocoa.NSApp.keyWindow()
|
|
3035
|
-
wc = win.delegate() if win and isinstance(win.delegate(), BrowserWindowController) else None
|
|
3036
3150
|
|
|
3037
|
-
|
|
3151
|
+
win = event.window() or Cocoa.NSApp.keyWindow() or Cocoa.NSApp.mainWindow()
|
|
3152
|
+
wc = None
|
|
3153
|
+
if win and isinstance(win.delegate(), BrowserWindowController):
|
|
3154
|
+
wc = win.delegate()
|
|
3155
|
+
elif hasattr(self, 'controllers') and self.controllers:
|
|
3156
|
+
for c in self.controllers:
|
|
3157
|
+
if c.window() == win:
|
|
3158
|
+
wc = c
|
|
3159
|
+
break
|
|
3160
|
+
if not wc and self.controllers:
|
|
3161
|
+
wc = self.controllers[0]
|
|
3162
|
+
|
|
3163
|
+
if key == 53: # Escape key
|
|
3038
3164
|
if shift and not cmd and not opt:
|
|
3039
3165
|
self.process_manager.show()
|
|
3040
3166
|
return None
|
|
3041
|
-
if opt and wc:
|
|
3167
|
+
if opt and not cmd and not shift and wc:
|
|
3042
3168
|
wc.toggle_tab_bar()
|
|
3043
3169
|
return None
|
|
3044
3170
|
if wc:
|
|
3171
|
+
# Allow Escape to pass directly into terminal tabs (vim, nano, fzf, less)
|
|
3172
|
+
if wc.is_active_tab_terminal():
|
|
3173
|
+
return event
|
|
3174
|
+
if wc.find_bar and not wc.find_bar.isHidden():
|
|
3175
|
+
wc.find_bar.setHidden_(True)
|
|
3176
|
+
return None
|
|
3177
|
+
if not wc.tab_bar_visible:
|
|
3178
|
+
wc.toggle_tab_bar()
|
|
3179
|
+
return None
|
|
3045
3180
|
wc.handle_escape_key()
|
|
3046
|
-
return
|
|
3181
|
+
return event
|
|
3047
3182
|
|
|
3048
3183
|
if cmd and shift and not opt:
|
|
3049
3184
|
if key == 0 or chars == 'a':
|
|
@@ -3143,8 +3278,10 @@ class AppDelegate(Cocoa.NSObject):
|
|
|
3143
3278
|
view_menu.addItemWithTitle_action_keyEquivalent_("Reload Page", "onReload:", "r")
|
|
3144
3279
|
view_menu.addItemWithTitle_action_keyEquivalent_("Focus Omnibox", "onFocusOmnibox:", "l")
|
|
3145
3280
|
view_menu.addItemWithTitle_action_keyEquivalent_("Picture-in-Picture", "onTogglePiP:", "p")
|
|
3281
|
+
zen_item = view_menu.addItemWithTitle_action_keyEquivalent_("Toggle Zen Mode", "onToggleZenMode:", "\x1b")
|
|
3282
|
+
zen_item.setKeyEquivalentModifierMask_(Cocoa.NSEventModifierFlagOption)
|
|
3146
3283
|
view_menu.addItem_(Cocoa.NSMenuItem.separatorItem())
|
|
3147
|
-
view_menu.addItemWithTitle_action_keyEquivalent_("Zoom In", "onZoomIn:", "
|
|
3284
|
+
view_menu.addItemWithTitle_action_keyEquivalent_("Zoom In", "onZoomIn:", "=")
|
|
3148
3285
|
view_menu.addItemWithTitle_action_keyEquivalent_("Zoom Out", "onZoomOut:", "-")
|
|
3149
3286
|
view_menu.addItemWithTitle_action_keyEquivalent_("Actual Size", "onResetZoom:", "0")
|
|
3150
3287
|
view_menu.addItem_(Cocoa.NSMenuItem.separatorItem())
|
|
@@ -23,7 +23,7 @@ DEFAULT_URL = "https://www.google.com"
|
|
|
23
23
|
DATA_DIR = os.path.expanduser("~/data")
|
|
24
24
|
os.makedirs(DATA_DIR, exist_ok=True)
|
|
25
25
|
|
|
26
|
-
_cur_dir = os.path.dirname(os.path.abspath(__file__))
|
|
26
|
+
_cur_dir = os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals() else os.getcwd()
|
|
27
27
|
_old_candidates = [
|
|
28
28
|
os.path.abspath(os.path.join(_cur_dir, "..", "browser_data")),
|
|
29
29
|
os.path.abspath(os.path.join(os.getcwd(), "browser_data")),
|
|
@@ -80,6 +80,49 @@ GROUP_COLORS = {
|
|
|
80
80
|
"Green": "#1E8E3E", "Pink": "#E52592", "Purple": "#9334E6", "Cyan": "#12B5CB"
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
def sanitize_for_json(obj):
|
|
84
|
+
"""
|
|
85
|
+
Recursively converts PyObjC wrapper types (__NSCFBoolean, NSString, NSNumber,
|
|
86
|
+
NSArray, NSDictionary) into standard Python primitives for json.dump compatibility.
|
|
87
|
+
"""
|
|
88
|
+
if obj is None:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
type_name = type(obj).__name__
|
|
92
|
+
|
|
93
|
+
# PyObjC booleans (__NSCFBoolean) must use boolValue() to accurately preserve False/True
|
|
94
|
+
if hasattr(obj, "boolValue") and ("Boolean" in type_name or "bool" in type_name.lower()):
|
|
95
|
+
return bool(obj.boolValue())
|
|
96
|
+
|
|
97
|
+
# Standard Python booleans
|
|
98
|
+
if isinstance(obj, bool) or type_name == "bool":
|
|
99
|
+
return bool(obj)
|
|
100
|
+
|
|
101
|
+
# Number conversion for PyObjC NSNumber (e.g. float/double or int)
|
|
102
|
+
if hasattr(obj, "doubleValue") and not isinstance(obj, (int, float, str, bytes)):
|
|
103
|
+
val = obj.doubleValue()
|
|
104
|
+
return int(val) if val.is_integer() else val
|
|
105
|
+
|
|
106
|
+
if isinstance(obj, (int, float)):
|
|
107
|
+
return obj
|
|
108
|
+
|
|
109
|
+
if isinstance(obj, (str, bytes)):
|
|
110
|
+
return str(obj)
|
|
111
|
+
|
|
112
|
+
if hasattr(obj, "items") or isinstance(obj, dict):
|
|
113
|
+
return {str(k): sanitize_for_json(v) for k, v in obj.items()}
|
|
114
|
+
|
|
115
|
+
if hasattr(obj, "__iter__") or isinstance(obj, (list, tuple, set)):
|
|
116
|
+
return [sanitize_for_json(i) for i in obj]
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
if hasattr(obj, "stringValue"):
|
|
120
|
+
return str(obj.stringValue())
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
return str(obj)
|
|
125
|
+
|
|
83
126
|
def validate_and_get_download_dir(target_path=None):
|
|
84
127
|
if not target_path:
|
|
85
128
|
target_path = SETTINGS.settings.get("download_path", os.path.expanduser("~/Downloads"))
|
|
@@ -130,7 +173,7 @@ class SettingsManager:
|
|
|
130
173
|
"vertical_tabs_collapsed": False,
|
|
131
174
|
"show_home_button": False,
|
|
132
175
|
"home_url": "https://www.google.com",
|
|
133
|
-
"default_zoom": 0
|
|
176
|
+
"default_zoom": 1.0,
|
|
134
177
|
"doh_provider": "cloudflare",
|
|
135
178
|
"doh_custom_url": "",
|
|
136
179
|
"custom_adblock_domains": "",
|
|
@@ -158,18 +201,20 @@ class SettingsManager:
|
|
|
158
201
|
except Exception as e:
|
|
159
202
|
print(f"[Settings Load Warning] {e}")
|
|
160
203
|
try:
|
|
161
|
-
z = float(self.settings.get("default_zoom", 0
|
|
204
|
+
z = float(self.settings.get("default_zoom", 1.0))
|
|
162
205
|
self.settings["default_zoom"] = max(0.5, min(2.0, z))
|
|
163
206
|
except Exception:
|
|
164
|
-
self.settings["default_zoom"] = 0
|
|
207
|
+
self.settings["default_zoom"] = 1.0
|
|
165
208
|
self.save()
|
|
166
209
|
|
|
167
210
|
def save(self):
|
|
168
211
|
try:
|
|
169
212
|
dir_name = os.path.dirname(SETTINGS_FILE) or "."
|
|
213
|
+
os.makedirs(dir_name, exist_ok=True)
|
|
214
|
+
sanitized = sanitize_for_json(self.settings)
|
|
170
215
|
fd, temp_path = tempfile.mkstemp(dir=dir_name)
|
|
171
216
|
with os.fdopen(fd, 'w', encoding='utf-8') as f:
|
|
172
|
-
json.dump(
|
|
217
|
+
json.dump(sanitized, f, indent=2)
|
|
173
218
|
os.replace(temp_path, SETTINGS_FILE)
|
|
174
219
|
except Exception as e:
|
|
175
220
|
print(f"[Settings Save Error] {e}")
|
|
@@ -199,7 +244,7 @@ class SettingsManager:
|
|
|
199
244
|
domains = set()
|
|
200
245
|
custom = self.settings.get("custom_adblock_domains", "")
|
|
201
246
|
if custom:
|
|
202
|
-
for d in re.split(r'[\r\n,]+', custom):
|
|
247
|
+
for d in re.split(r'[\r\n,]+', str(custom)):
|
|
203
248
|
d = d.strip().lower()
|
|
204
249
|
if d:
|
|
205
250
|
domains.add(d)
|
|
@@ -226,7 +271,7 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
226
271
|
tab_action = s.get("tab_bar_double_click_action", "maximize")
|
|
227
272
|
downloads_enabled = s.get("downloads_enabled", True)
|
|
228
273
|
cur_dl_path = s.get("download_path", os.path.expanduser("~/Downloads"))
|
|
229
|
-
cur_zoom =
|
|
274
|
+
cur_zoom = f"{float(s.get('default_zoom', 1.0)):.2f}"
|
|
230
275
|
|
|
231
276
|
return f"""<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Settings</title>
|
|
232
277
|
<style>
|
|
@@ -296,7 +341,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
296
341
|
</div>
|
|
297
342
|
<div id="custom-search-row" class="row" style="display: {'flex' if engine_preset == 'custom' else 'none'};">
|
|
298
343
|
<div class="label-group"><div class="label-title">Custom Search URL Template</div><div class="label-desc">Use <code>%s</code> as query placeholder</div></div>
|
|
299
|
-
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(custom_search_url)}"
|
|
344
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(custom_search_url)}"
|
|
345
|
+
onchange="saveSetting('custom_search_url', this.value, 'Custom search URL saved')"
|
|
346
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
300
347
|
</div>
|
|
301
348
|
<div class="row">
|
|
302
349
|
<div class="label-group"><div class="label-title">Search in New Tab</div><div class="label-desc">Open Omnibox search results in a new tab</div></div>
|
|
@@ -327,13 +374,13 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
327
374
|
</select>
|
|
328
375
|
</div>
|
|
329
376
|
<div class="row">
|
|
330
|
-
<div class="label-group"><div class="label-title">Default Zoom Level</div><div class="label-desc">Default page scaling factor
|
|
377
|
+
<div class="label-group"><div class="label-title">Default Zoom Level</div><div class="label-desc">Default page scaling factor across all web views</div></div>
|
|
331
378
|
<select class="m3-select" onchange="saveSetting('default_zoom', parseFloat(this.value), 'Zoom level updated')">
|
|
332
379
|
<option value="0.75" {'selected' if cur_zoom == '0.75' else ''}>75%</option>
|
|
333
|
-
<option value="0.
|
|
334
|
-
<option value="1.
|
|
380
|
+
<option value="0.80" {'selected' if cur_zoom == '0.80' else ''}>80%</option>
|
|
381
|
+
<option value="1.00" {'selected' if cur_zoom in ('1.00', '1.0', '1') else ''}>100% (Default)</option>
|
|
335
382
|
<option value="1.25" {'selected' if cur_zoom == '1.25' else ''}>125%</option>
|
|
336
|
-
<option value="1.
|
|
383
|
+
<option value="1.50" {'selected' if cur_zoom == '1.50' else ''}>150%</option>
|
|
337
384
|
</select>
|
|
338
385
|
</div>
|
|
339
386
|
<div class="row">
|
|
@@ -359,7 +406,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
359
406
|
</div>
|
|
360
407
|
<div class="row">
|
|
361
408
|
<div class="label-group"><div class="label-title">Home Page URL</div><div class="label-desc">Webpage loaded on clicking home button</div></div>
|
|
362
|
-
<input type="text" class="m3-input" style="width:260px;" value="{html.escape(s.get('home_url', 'https://www.google.com'))}"
|
|
409
|
+
<input type="text" class="m3-input" style="width:260px;" value="{html.escape(s.get('home_url', 'https://www.google.com'))}"
|
|
410
|
+
onchange="saveSetting('home_url', this.value, 'Home URL saved')"
|
|
411
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
363
412
|
</div>
|
|
364
413
|
</div>
|
|
365
414
|
</div>
|
|
@@ -395,6 +444,13 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
395
444
|
<span class="slider"></span>
|
|
396
445
|
</label>
|
|
397
446
|
</div>
|
|
447
|
+
<div class="row">
|
|
448
|
+
<div class="label-group"><div class="label-title">Block Unsolicited Popups</div><div class="label-desc">Prevent background scripts from opening intrusive windows</div></div>
|
|
449
|
+
<label class="switch">
|
|
450
|
+
<input type="checkbox" {'checked' if s.get('block_popups', True) else ''} onchange="saveSetting('block_popups', this.checked, 'Popup blocker toggled')">
|
|
451
|
+
<span class="slider"></span>
|
|
452
|
+
</label>
|
|
453
|
+
</div>
|
|
398
454
|
<div class="row-col">
|
|
399
455
|
<div class="label-group" style="max-width: 100%;">
|
|
400
456
|
<div class="label-title">Rule & Domain Inspector</div>
|
|
@@ -426,7 +482,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
426
482
|
</div>
|
|
427
483
|
<div id="custom-doh-row" class="row" style="display: {'flex' if doh_provider == 'custom' else 'none'};">
|
|
428
484
|
<div class="label-group"><div class="label-title">Custom DoH URL</div><div class="label-desc">Must support DNS-over-HTTPS JSON API</div></div>
|
|
429
|
-
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(doh_custom_url)}" placeholder="https://..."
|
|
485
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(doh_custom_url)}" placeholder="https://..."
|
|
486
|
+
onchange="saveSetting('doh_custom_url', this.value, 'Custom DoH URL saved')"
|
|
487
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
430
488
|
</div>
|
|
431
489
|
<div class="row">
|
|
432
490
|
<div class="label-group"><div class="label-title">HTTPS-Only Mode</div><div class="label-desc">Upgrades unencrypted HTTP requests to secure HTTPS</div></div>
|
|
@@ -444,7 +502,8 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
444
502
|
<div class="label-title">Custom Blocked Domains</div>
|
|
445
503
|
<div class="label-desc">Add additional domains to block (comma- or line-separated)</div>
|
|
446
504
|
</div>
|
|
447
|
-
<textarea class="m3-input" style="width:100%; height:75px; resize:vertical;" placeholder="adserver.com tracker.telemetry.io"
|
|
505
|
+
<textarea class="m3-input" style="width:100%; height:75px; resize:vertical;" placeholder="adserver.com tracker.telemetry.io"
|
|
506
|
+
onchange="saveSetting('custom_adblock_domains', this.value, 'Custom domains saved')">{html.escape(s.get('custom_adblock_domains', ''))}</textarea>
|
|
448
507
|
</div>
|
|
449
508
|
</div>
|
|
450
509
|
</div>
|
|
@@ -462,7 +521,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
462
521
|
</div>
|
|
463
522
|
<div id="custom-startup-row" class="row" style="display: {'flex' if startup_behavior == 'custom_url' else 'none'};">
|
|
464
523
|
<div class="label-group"><div class="label-title">Startup URL</div><div class="label-desc">Webpage loaded on initial application launch</div></div>
|
|
465
|
-
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(s.get('startup_url', 'https://www.google.com'))}"
|
|
524
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(s.get('startup_url', 'https://www.google.com'))}"
|
|
525
|
+
onchange="saveSetting('startup_url', this.value, 'Startup URL saved')"
|
|
526
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
466
527
|
</div>
|
|
467
528
|
<div class="row">
|
|
468
529
|
<div class="label-group"><div class="label-title">Window State on Launch</div><div class="label-desc">Window geometry upon application launch</div></div>
|
|
@@ -500,8 +561,8 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
500
561
|
</div>
|
|
501
562
|
<div style="display:flex; gap:8px; align-items:center;">
|
|
502
563
|
<input type="text" id="dl-path-input" class="m3-input" style="width:240px;" value="{html.escape(cur_dl_path)}"
|
|
503
|
-
onchange="
|
|
504
|
-
onkeydown="if(event.key==='Enter')
|
|
564
|
+
onchange="onDownloadInputChange(this.value)"
|
|
565
|
+
onkeydown="if(event.key==='Enter') {{ onDownloadInputChange(this.value); this.blur(); }}">
|
|
505
566
|
<button class="btn" onclick="browseDownloadFolder()">Browse…</button>
|
|
506
567
|
</div>
|
|
507
568
|
</div>
|
|
@@ -523,7 +584,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
523
584
|
</div>
|
|
524
585
|
<div id="custom-ua-row" class="row" style="display: {'flex' if ua_mode == 'custom' else 'none'};">
|
|
525
586
|
<div class="label-group"><div class="label-title">Custom User-Agent Header</div><div class="label-desc">Raw User-Agent string sent with requests</div></div>
|
|
526
|
-
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(s.get('custom_user_agent', ''))}"
|
|
587
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(s.get('custom_user_agent', ''))}"
|
|
588
|
+
onchange="saveSetting('custom_user_agent', this.value, 'Custom User-Agent saved')"
|
|
589
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
527
590
|
</div>
|
|
528
591
|
<div class="row">
|
|
529
592
|
<div class="label-group"><div class="label-title">WebGL Hardware Acceleration</div><div class="label-desc">Enable 3D canvas and accelerated GPU graphics</div></div>
|
|
@@ -583,9 +646,18 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
583
646
|
window._toastTimer = setTimeout(() => t.classList.remove('show'), 2400);
|
|
584
647
|
}}
|
|
585
648
|
function saveSetting(key, val, msg) {{
|
|
586
|
-
window.webkit.messageHandlers.bridge
|
|
649
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {{
|
|
650
|
+
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'save_setting', key: key, val: val }});
|
|
651
|
+
}}
|
|
587
652
|
showToast(msg);
|
|
588
653
|
}}
|
|
654
|
+
function onDownloadInputChange(val) {{
|
|
655
|
+
const trimmed = (val || '').trim();
|
|
656
|
+
if (!trimmed) return;
|
|
657
|
+
saveSetting('download_path', trimmed, 'Download path saved');
|
|
658
|
+
const disp = document.getElementById('dl-path-display');
|
|
659
|
+
if (disp) disp.innerText = trimmed;
|
|
660
|
+
}}
|
|
589
661
|
function onSearchPresetChange(val) {{
|
|
590
662
|
saveSetting('search_engine_preset', val, 'Search engine updated');
|
|
591
663
|
document.getElementById('custom-search-row').style.display = (val === 'custom') ? 'flex' : 'none';
|
|
@@ -603,12 +675,16 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
603
675
|
document.getElementById('custom-startup-row').style.display = (val === 'custom_url') ? 'flex' : 'none';
|
|
604
676
|
}}
|
|
605
677
|
function browseDownloadFolder() {{
|
|
606
|
-
window.webkit.messageHandlers.bridge
|
|
678
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {{
|
|
679
|
+
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'choose_download_directory' }});
|
|
680
|
+
}}
|
|
607
681
|
}}
|
|
608
682
|
function updateAdblockFilters() {{
|
|
609
683
|
const b = document.getElementById('update-filters-btn');
|
|
610
684
|
b.innerText = "Syncing Lists..."; b.disabled = true;
|
|
611
|
-
window.webkit.messageHandlers.bridge
|
|
685
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {{
|
|
686
|
+
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'update_adblock_filters' }});
|
|
687
|
+
}}
|
|
612
688
|
}}
|
|
613
689
|
function searchDomain() {{
|
|
614
690
|
const q = document.getElementById('inspector-query').value.trim();
|
|
@@ -616,7 +692,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
616
692
|
const box = document.getElementById('inspector-results');
|
|
617
693
|
box.style.display = 'block';
|
|
618
694
|
box.innerHTML = 'Searching blocklists...';
|
|
619
|
-
window.webkit.messageHandlers.bridge
|
|
695
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {{
|
|
696
|
+
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'search_blocked_domains', query: q }});
|
|
697
|
+
}}
|
|
620
698
|
}}
|
|
621
699
|
window.renderBlockedResults = function(jsonStr) {{
|
|
622
700
|
let items = [];
|
|
@@ -639,12 +717,16 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
639
717
|
if (document.getElementById('clr-favicons').checked) targets.push('favicons');
|
|
640
718
|
if (document.getElementById('clr-dns').checked) targets.push('dns_cache');
|
|
641
719
|
if (targets.length === 0) {{ showToast('Select at least one category'); return; }}
|
|
642
|
-
window.webkit.messageHandlers.bridge
|
|
720
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {{
|
|
721
|
+
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'clear_browsing_data', targets: targets }});
|
|
722
|
+
}}
|
|
643
723
|
showToast('Browsing data cleared');
|
|
644
724
|
}}
|
|
645
725
|
function executeResetDefaults() {{
|
|
646
726
|
if (confirm("Restore all browser settings to factory defaults?")) {{
|
|
647
|
-
window.webkit.messageHandlers.bridge
|
|
727
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {{
|
|
728
|
+
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'reset_defaults' }});
|
|
729
|
+
}}
|
|
648
730
|
}}
|
|
649
731
|
}}
|
|
650
732
|
function jumpTo(id, navBtn) {{
|
|
@@ -322,7 +322,7 @@ class TerminalSession:
|
|
|
322
322
|
if not self.alive or self.master_fd < 0:
|
|
323
323
|
return
|
|
324
324
|
try:
|
|
325
|
-
data = text.encode('utf-8')
|
|
325
|
+
data = text.encode('utf-8') if isinstance(text, str) else text
|
|
326
326
|
total = 0
|
|
327
327
|
while total < len(data) and self.master_fd >= 0:
|
|
328
328
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/requires.txt
RENAMED
|
File without changes
|
{stupidpythonbrowser-1.0.1 → stupidpythonbrowser-1.0.3}/stupidpythonbrowser.egg-info/top_level.txt
RENAMED
|
File without changes
|