stupidpythonbrowser 1.0.1__py3-none-any.whl → 1.0.2__py3-none-any.whl
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.
- pybrowser/__init__.py +2 -2
- pybrowser/browser.py +185 -70
- pybrowser/settings.py +77 -18
- pybrowser/terminal.py +1 -1
- {stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/METADATA +1 -1
- stupidpythonbrowser-1.0.2.dist-info/RECORD +14 -0
- stupidpythonbrowser-1.0.1.dist-info/RECORD +0 -14
- {stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/WHEEL +0 -0
- {stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/entry_points.txt +0 -0
- {stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/top_level.txt +0 -0
pybrowser/__init__.py
CHANGED
|
@@ -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.2"
|
|
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
|
+
]
|
pybrowser/browser.py
CHANGED
|
@@ -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,6 +417,42 @@ 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
458
|
if (e.target && !e.target.muted) window.webkit.messageHandlers.audio.postMessage({cmd:'audio_play'});
|
|
@@ -841,8 +881,7 @@ class BookmarksBarView(Cocoa.NSView):
|
|
|
841
881
|
if not isinstance(b, dict) or not b.get("url"):
|
|
842
882
|
continue
|
|
843
883
|
title = b.get("title", b["url"])[:24]
|
|
844
|
-
|
|
845
|
-
btn_w = max(40, min(140, len(title) * 7.5 + 14))
|
|
884
|
+
btn_w = max(50, min(160, len(title) * 7.5 + 26))
|
|
846
885
|
btn = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(x, 2, btn_w, h - 4))
|
|
847
886
|
btn.setTitle_(title)
|
|
848
887
|
btn.setBezelStyle_(Cocoa.NSBezelStyleInline)
|
|
@@ -850,10 +889,29 @@ class BookmarksBarView(Cocoa.NSView):
|
|
|
850
889
|
btn.setTarget_(self)
|
|
851
890
|
btn.setAction_("onBookmarkClick:")
|
|
852
891
|
btn.setToolTip_(b.get("url", ""))
|
|
892
|
+
|
|
893
|
+
# Dynamically query and bind favicon
|
|
894
|
+
try:
|
|
895
|
+
host = urllib.parse.urlparse(b.get("url", "")).netloc
|
|
896
|
+
if host:
|
|
897
|
+
FaviconCache.get_favicon(host, lambda img, b_ref=btn: self._apply_button_icon(b_ref, img))
|
|
898
|
+
except Exception:
|
|
899
|
+
pass
|
|
900
|
+
|
|
853
901
|
self.addSubview_(btn)
|
|
854
902
|
x += btn_w + 4.0
|
|
855
903
|
self.setNeedsDisplay_(True)
|
|
856
904
|
|
|
905
|
+
def _apply_button_icon(self, btn, img):
|
|
906
|
+
if img and btn:
|
|
907
|
+
try:
|
|
908
|
+
scaled = img.copy()
|
|
909
|
+
scaled.setSize_(Cocoa.NSMakeSize(14, 14))
|
|
910
|
+
btn.setImage_(scaled)
|
|
911
|
+
btn.setImagePosition_(Cocoa.NSImageLeft)
|
|
912
|
+
except Exception:
|
|
913
|
+
pass
|
|
914
|
+
|
|
857
915
|
def onBookmarkClick_(self, sender):
|
|
858
916
|
url = sender.toolTip()
|
|
859
917
|
if url and self.controller:
|
|
@@ -968,7 +1026,6 @@ class InternalSchemeHandler(Cocoa.NSObject):
|
|
|
968
1026
|
self.active_tasks.discard(task)
|
|
969
1027
|
|
|
970
1028
|
def get_terminal_html(self):
|
|
971
|
-
# Fullscreen responsive xterm with ResizeObserver to ensure vim/less utilize entire viewport
|
|
972
1029
|
bg = "#1e1e2e"
|
|
973
1030
|
fg = "#c792ea"
|
|
974
1031
|
return f"""<!DOCTYPE html><html><head><meta charset='utf-8'>
|
|
@@ -992,6 +1049,19 @@ const fit = new FitAddon.FitAddon();
|
|
|
992
1049
|
term.loadAddon(fit);
|
|
993
1050
|
term.open(document.getElementById('t'));
|
|
994
1051
|
|
|
1052
|
+
// Guarantee Esc (0x1b) is sent directly to child PTY (vim/nano/fzf) without being swallowed
|
|
1053
|
+
term.attachCustomKeyEventHandler(e => {{
|
|
1054
|
+
if (e.key === 'Escape' || e.keyCode === 27) {{
|
|
1055
|
+
if (e.type === 'keydown') {{
|
|
1056
|
+
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.terminalInput) {{
|
|
1057
|
+
window.webkit.messageHandlers.terminalInput.postMessage('\\x1b');
|
|
1058
|
+
}}
|
|
1059
|
+
}}
|
|
1060
|
+
return false;
|
|
1061
|
+
}}
|
|
1062
|
+
return true;
|
|
1063
|
+
}});
|
|
1064
|
+
|
|
995
1065
|
const postResize = () => {{
|
|
996
1066
|
try {{
|
|
997
1067
|
fit.fit();
|
|
@@ -1001,9 +1071,7 @@ const postResize = () => {{
|
|
|
1001
1071
|
}} catch(e) {{}}
|
|
1002
1072
|
}};
|
|
1003
1073
|
|
|
1004
|
-
const ro = new ResizeObserver(() => {{
|
|
1005
|
-
postResize();
|
|
1006
|
-
}});
|
|
1074
|
+
const ro = new ResizeObserver(() => {{ postResize(); }});
|
|
1007
1075
|
ro.observe(document.getElementById('t'));
|
|
1008
1076
|
|
|
1009
1077
|
setTimeout(postResize, 30);
|
|
@@ -1038,10 +1106,12 @@ window.writeToTerm = b => {{
|
|
|
1038
1106
|
u = b["url"]
|
|
1039
1107
|
t = b.get("title", u)
|
|
1040
1108
|
safe_u = json.dumps(u)
|
|
1109
|
+
host = urllib.parse.urlparse(u).netloc
|
|
1110
|
+
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 ""
|
|
1041
1111
|
del_call = f"window.webkit.messageHandlers.bridge.postMessage({{cmd: 'delete_bookmark', url: {safe_u}}}); setTimeout(() => location.reload(), 100);"
|
|
1042
|
-
items.append(f"<li><a href='{html.escape(u)}'>{html.escape(t)}</a> <button onclick='{del_call}'>Delete</button></li>")
|
|
1112
|
+
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
1113
|
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>"
|
|
1114
|
+
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='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
1115
|
|
|
1046
1116
|
def get_history_html(self):
|
|
1047
1117
|
records = query_history()
|
|
@@ -1274,9 +1344,10 @@ class OmniboxPanelCoordinator:
|
|
|
1274
1344
|
|
|
1275
1345
|
def fetch_remote():
|
|
1276
1346
|
try:
|
|
1277
|
-
|
|
1347
|
+
# Secured endpoint via HTTPS to avoid keystroke sniffing
|
|
1348
|
+
url = f"https://suggestqueries.google.com/complete/search?client=chrome&q={urllib.parse.quote(query_str)}"
|
|
1278
1349
|
req = urllib.request.Request(url, headers={"User-Agent": UA_PRESETS["chrome_mac"]})
|
|
1279
|
-
with urllib.request.urlopen(req, timeout=0
|
|
1350
|
+
with urllib.request.urlopen(req, timeout=1.0) as resp:
|
|
1280
1351
|
data = json.loads(resp.read().decode('utf-8'))
|
|
1281
1352
|
remote = [{"type": "search", "text": s, "url": s} for s in data[1][:5]]
|
|
1282
1353
|
if req_id == self.current_req_id:
|
|
@@ -1551,7 +1622,6 @@ class QuickSwitcherController(Cocoa.NSWindowController):
|
|
|
1551
1622
|
elif sel == "insertNewline:":
|
|
1552
1623
|
if self.filtered_tabs and self.selected_index >= 0:
|
|
1553
1624
|
t = self.filtered_tabs[self.selected_index]
|
|
1554
|
-
# Fixed: Resolve current tab index dynamically by ID to avoid desync/crash
|
|
1555
1625
|
cur_idx = t["wc"].get_tab_index_by_id(t["id"])
|
|
1556
1626
|
if cur_idx is not None:
|
|
1557
1627
|
t["wc"].window().makeKeyAndOrderFront_(None)
|
|
@@ -1601,7 +1671,6 @@ class PiPPanelController(Cocoa.NSWindowController):
|
|
|
1601
1671
|
self.window().makeKeyAndOrderFront_(None)
|
|
1602
1672
|
|
|
1603
1673
|
def exec_js(self, js):
|
|
1604
|
-
# Fixed: Prefer active window controller or stored browser controller
|
|
1605
1674
|
app_delegate = Cocoa.NSApp.delegate()
|
|
1606
1675
|
wc = None
|
|
1607
1676
|
key_win = Cocoa.NSApp.keyWindow()
|
|
@@ -1770,7 +1839,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1770
1839
|
self.home_btn.setHidden_(not SETTINGS.settings.get("show_home_button", False))
|
|
1771
1840
|
|
|
1772
1841
|
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
1842
|
self.omnibox_container = Cocoa.NSView.alloc().initWithFrame_(Cocoa.NSMakeRect(omni_x, 3, max(180, w - omni_x - 162), 28))
|
|
1775
1843
|
self.toolbar.addSubview_(self.omnibox_container)
|
|
1776
1844
|
|
|
@@ -1798,7 +1866,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1798
1866
|
self.star_btn.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
|
|
1799
1867
|
self.omnibox_container.addSubview_(self.star_btn)
|
|
1800
1868
|
|
|
1801
|
-
# Fixed: Buttons pushed cleanly to the right edge with uniform 30px spacing
|
|
1802
1869
|
self.pip_btn = btn("⧉", w - 154, "onTogglePiP:", "Media Controls")
|
|
1803
1870
|
self.dl_btn = btn("⬇", w - 124, "onShowDownloads:", "Downloads")
|
|
1804
1871
|
self.share_btn = btn("↗", w - 94, "onShareCurrentPage:", "Share Page")
|
|
@@ -1853,7 +1920,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1853
1920
|
omni_x = 132 if show_home else 102
|
|
1854
1921
|
tb_w = self.toolbar.bounds().size.width
|
|
1855
1922
|
|
|
1856
|
-
# Fixed: Expand Omnibox to fill empty space, eliminated gap between zen and menu buttons
|
|
1857
1923
|
if hasattr(self, 'omnibox_container'):
|
|
1858
1924
|
box_w = max(180, tb_w - omni_x - 162)
|
|
1859
1925
|
self.omnibox_container.setFrame_(Cocoa.NSMakeRect(omni_x, 3, box_w, 28))
|
|
@@ -1878,7 +1944,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1878
1944
|
sub.removeFromSuperview()
|
|
1879
1945
|
w = self.tab_strip.bounds().size.width
|
|
1880
1946
|
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
1947
|
x = 8.0
|
|
1883
1948
|
tab_h = 28 if not is_vert else 32
|
|
1884
1949
|
y = self.tab_strip.bounds().size.height - 38 - tab_h if is_vert else 3
|
|
@@ -1965,6 +2030,8 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
1965
2030
|
uc.addScriptMessageHandler_name_(self, "terminalInput")
|
|
1966
2031
|
uc.addScriptMessageHandler_name_(self, "terminalResize")
|
|
1967
2032
|
|
|
2033
|
+
# Injected scripts: Command+click capture, audio monitoring, context menus, and fullscreen handlers
|
|
2034
|
+
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(COMMAND_CLICK_JS, WebKit.WKUserScriptInjectionTimeAtDocumentStart, False))
|
|
1968
2035
|
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(AUDIO_MONITOR_JS, WebKit.WKUserScriptInjectionTimeAtDocumentEnd, False))
|
|
1969
2036
|
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(IMAGE_CONTEXT_MENU_JS, WebKit.WKUserScriptInjectionTimeAtDocumentStart, False))
|
|
1970
2037
|
uc.addUserScript_(WebKit.WKUserScript.alloc().initWithSource_injectionTime_forMainFrameOnly_(KEYCHAIN_AUTOSAVE_JS, WebKit.WKUserScriptInjectionTimeAtDocumentEnd, False))
|
|
@@ -2009,7 +2076,7 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2009
2076
|
except Exception:
|
|
2010
2077
|
pass
|
|
2011
2078
|
|
|
2012
|
-
initial_zoom = float(SETTINGS.settings.get("default_zoom", 0
|
|
2079
|
+
initial_zoom = float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2013
2080
|
tab = {
|
|
2014
2081
|
"id": str(uuid.uuid4()), "webview": wv, "url": url, "title": "New Tab",
|
|
2015
2082
|
"favicon": None, "pinned": pinned, "group_id": None, "zoom": initial_zoom,
|
|
@@ -2049,8 +2116,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2049
2116
|
self.wake_tab(index)
|
|
2050
2117
|
wv = tab["webview"]
|
|
2051
2118
|
|
|
2052
|
-
# Fixed: Keep webviews attached and toggle visibility with setHidden_
|
|
2053
|
-
# This completely preserves DOM focus, text inputs, and render state
|
|
2054
2119
|
for i, t in enumerate(self.tabs):
|
|
2055
2120
|
twv = t.get("webview")
|
|
2056
2121
|
if twv:
|
|
@@ -2066,7 +2131,6 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2066
2131
|
self.update_progress_for_webview(wv)
|
|
2067
2132
|
self.relayout_tabs()
|
|
2068
2133
|
|
|
2069
|
-
# Fixed: Restore keyboard focus and text cursor to the active input element automatically
|
|
2070
2134
|
self.window().makeFirstResponder_(wv)
|
|
2071
2135
|
wv.evaluateJavaScript_completionHandler_(
|
|
2072
2136
|
"if (document.activeElement && typeof document.activeElement.focus === 'function') { document.activeElement.focus(); }",
|
|
@@ -2258,7 +2322,7 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2258
2322
|
def apply_zoom_to_webview(self, wv, tab=None):
|
|
2259
2323
|
if not wv:
|
|
2260
2324
|
return
|
|
2261
|
-
target_zoom = tab.get("zoom", float(SETTINGS.settings.get("default_zoom", 0
|
|
2325
|
+
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
2326
|
target_zoom = max(0.5, min(2.0, target_zoom))
|
|
2263
2327
|
try:
|
|
2264
2328
|
if hasattr(wv, "setAllowsMagnification_"): wv.setAllowsMagnification_(False)
|
|
@@ -2268,18 +2332,19 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2268
2332
|
pass
|
|
2269
2333
|
|
|
2270
2334
|
def restore_normal_zoom(self):
|
|
2335
|
+
if self.is_active_tab_terminal():
|
|
2336
|
+
return
|
|
2271
2337
|
wv = self.get_active_webview()
|
|
2272
2338
|
if wv:
|
|
2273
|
-
self.
|
|
2339
|
+
tab = self.tabs[self.active_tab_index] if (0 <= self.active_tab_index < len(self.tabs)) else None
|
|
2340
|
+
self.apply_zoom_to_webview(wv, tab)
|
|
2274
2341
|
wv.evaluateJavaScript_completionHandler_(REFLOW_RESTORE_JS, None)
|
|
2275
2342
|
|
|
2276
2343
|
def handle_fullscreen_enter(self, auto_hide_bars=True):
|
|
2277
2344
|
if auto_hide_bars:
|
|
2278
2345
|
if not self.tab_bar_visible:
|
|
2279
|
-
# Tab was already in Zen mode before entering fullscreen
|
|
2280
2346
|
self.zen_auto_entered = False
|
|
2281
2347
|
else:
|
|
2282
|
-
# Tab was in normal mode, auto-enter Zen mode for fullscreen
|
|
2283
2348
|
self.zen_auto_entered = True
|
|
2284
2349
|
self.tab_bar_visible = False
|
|
2285
2350
|
self.relayout_views()
|
|
@@ -2292,22 +2357,27 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2292
2357
|
pass
|
|
2293
2358
|
|
|
2294
2359
|
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!
|
|
2360
|
+
if restore_bars and (self.zen_auto_entered or not self.tab_bar_visible):
|
|
2361
|
+
self.tab_bar_visible = True
|
|
2362
|
+
self.zen_auto_entered = False
|
|
2363
|
+
self.relayout_views()
|
|
2302
2364
|
wv = self.get_active_webview()
|
|
2303
2365
|
if wv:
|
|
2304
2366
|
wv.evaluateJavaScript_completionHandler_("if (window.setTerminalFullscreen) window.setTerminalFullscreen(false);", None)
|
|
2305
2367
|
self.restore_normal_zoom()
|
|
2306
2368
|
|
|
2369
|
+
def is_active_tab_terminal(self):
|
|
2370
|
+
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2371
|
+
url = self.tabs[self.active_tab_index].get("url") or ""
|
|
2372
|
+
return "browser://terminal" in url
|
|
2373
|
+
return False
|
|
2374
|
+
|
|
2307
2375
|
def handle_escape_key(self):
|
|
2308
2376
|
if self.find_bar and not self.find_bar.isHidden():
|
|
2309
2377
|
self.find_bar.setHidden_(True)
|
|
2310
2378
|
return
|
|
2379
|
+
if self.is_active_tab_terminal():
|
|
2380
|
+
return
|
|
2311
2381
|
wv = self.get_active_webview()
|
|
2312
2382
|
if not self.tab_bar_visible:
|
|
2313
2383
|
self.handle_fullscreen_exit(restore_bars=True)
|
|
@@ -2339,21 +2409,21 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2339
2409
|
def onZoomIn_(self, sender=None):
|
|
2340
2410
|
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2341
2411
|
tab = self.tabs[self.active_tab_index]
|
|
2342
|
-
z = min(2.0, round(tab.get("zoom", 0
|
|
2412
|
+
z = min(2.0, round(tab.get("zoom", 1.0) + 0.1, 2))
|
|
2343
2413
|
tab["zoom"] = z
|
|
2344
2414
|
self.apply_zoom_to_webview(tab.get("webview"), tab)
|
|
2345
2415
|
|
|
2346
2416
|
def onZoomOut_(self, sender=None):
|
|
2347
2417
|
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2348
2418
|
tab = self.tabs[self.active_tab_index]
|
|
2349
|
-
z = max(0.5, round(tab.get("zoom", 0
|
|
2419
|
+
z = max(0.5, round(tab.get("zoom", 1.0) - 0.1, 2))
|
|
2350
2420
|
tab["zoom"] = z
|
|
2351
2421
|
self.apply_zoom_to_webview(tab.get("webview"), tab)
|
|
2352
2422
|
|
|
2353
2423
|
def onResetZoom_(self, sender=None):
|
|
2354
2424
|
if 0 <= self.active_tab_index < len(self.tabs):
|
|
2355
2425
|
tab = self.tabs[self.active_tab_index]
|
|
2356
|
-
tab["zoom"] = float(SETTINGS.settings.get("default_zoom", 0
|
|
2426
|
+
tab["zoom"] = float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2357
2427
|
self.apply_zoom_to_webview(tab.get("webview"), tab)
|
|
2358
2428
|
self.restore_normal_zoom()
|
|
2359
2429
|
|
|
@@ -2377,7 +2447,9 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2377
2447
|
menu.addItemWithTitle_action_keyEquivalent_("Bookmark Current Page", "onBookmarkCurrentPage:", "d").setTarget_(self)
|
|
2378
2448
|
bm_text = "Hide Bookmarks Bar" if SETTINGS.settings.get("bookmarks_bar_visible", True) else "Show Bookmarks Bar"
|
|
2379
2449
|
menu.addItemWithTitle_action_keyEquivalent_(bm_text, "toggleBookmarksBar:", "b").setTarget_(self)
|
|
2380
|
-
menu.addItemWithTitle_action_keyEquivalent_("Toggle Zen Mode", "onToggleZenMode:", "")
|
|
2450
|
+
zen_item = menu.addItemWithTitle_action_keyEquivalent_("Toggle Zen Mode", "onToggleZenMode:", "\x1b")
|
|
2451
|
+
zen_item.setKeyEquivalentModifierMask_(Cocoa.NSEventModifierFlagOption)
|
|
2452
|
+
zen_item.setTarget_(self)
|
|
2381
2453
|
menu.addItemWithTitle_action_keyEquivalent_("Find in Page", "onFindInPage:", "f").setTarget_(self)
|
|
2382
2454
|
menu.addItem_(Cocoa.NSMenuItem.separatorItem())
|
|
2383
2455
|
menu.addItemWithTitle_action_keyEquivalent_("Settings", "openSettings:", ",").setTarget_(self)
|
|
@@ -2629,8 +2701,9 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2629
2701
|
cmd = body.get("cmd") if isinstance(body, dict) else None
|
|
2630
2702
|
|
|
2631
2703
|
if cmd == "open_url":
|
|
2632
|
-
|
|
2633
|
-
|
|
2704
|
+
target_url = body.get("url")
|
|
2705
|
+
if target_url:
|
|
2706
|
+
self.add_tab(target_url)
|
|
2634
2707
|
elif cmd == "toggle_fullscreen_terminal":
|
|
2635
2708
|
self.toggle_tab_bar()
|
|
2636
2709
|
elif cmd == "context_target_image":
|
|
@@ -2677,10 +2750,12 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2677
2750
|
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
2751
|
elif cmd == "save_setting":
|
|
2679
2752
|
key, val = body.get("key"), body.get("val")
|
|
2753
|
+
val = sanitize_for_json(val)
|
|
2680
2754
|
if key == "download_path":
|
|
2681
2755
|
val = os.path.expanduser(str(val).strip())
|
|
2682
2756
|
SETTINGS.settings[key] = val
|
|
2683
2757
|
SETTINGS.save()
|
|
2758
|
+
|
|
2684
2759
|
if key in ("bookmarks_bar_visible", "show_home_button", "vertical_tabs_enabled"):
|
|
2685
2760
|
self.relayout_views()
|
|
2686
2761
|
elif key == "theme_mode":
|
|
@@ -2693,21 +2768,35 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2693
2768
|
for sub in c.tab_strip.subviews():
|
|
2694
2769
|
sub.setNeedsDisplay_(True)
|
|
2695
2770
|
elif key in ("default_zoom",):
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2771
|
+
new_zoom = float(val)
|
|
2772
|
+
for t in self.tabs:
|
|
2773
|
+
t["zoom"] = new_zoom
|
|
2774
|
+
twv = t.get("webview")
|
|
2775
|
+
if twv:
|
|
2776
|
+
self.apply_zoom_to_webview(twv, t)
|
|
2699
2777
|
elif key in ("user_agent_mode", "custom_user_agent"):
|
|
2700
2778
|
ua = SETTINGS.get_user_agent()
|
|
2701
2779
|
for t in self.tabs:
|
|
2702
2780
|
if t.get("webview"):
|
|
2703
2781
|
t["webview"].setCustomUserAgent_(ua)
|
|
2782
|
+
elif key == "js_enabled":
|
|
2783
|
+
js_flag = bool(val)
|
|
2784
|
+
for t in self.tabs:
|
|
2785
|
+
twv = t.get("webview")
|
|
2786
|
+
if twv and hasattr(twv.configuration().preferences(), "setJavaScriptEnabled_"):
|
|
2787
|
+
twv.configuration().preferences().setJavaScriptEnabled_(js_flag)
|
|
2788
|
+
elif key == "block_popups":
|
|
2789
|
+
pop_flag = not bool(val)
|
|
2790
|
+
for t in self.tabs:
|
|
2791
|
+
twv = t.get("webview")
|
|
2792
|
+
if twv and hasattr(twv.configuration().preferences(), "setJavaScriptCanOpenWindowsAutomatically_"):
|
|
2793
|
+
twv.configuration().preferences().setJavaScriptCanOpenWindowsAutomatically_(pop_flag)
|
|
2704
2794
|
elif key == "doh_provider":
|
|
2705
2795
|
with DNS_LOCK:
|
|
2706
2796
|
DNS_CACHE.clear()
|
|
2707
2797
|
elif key in ("adblock_enabled", "adblock_stevenblack_enabled", "adblock_base_enabled", "custom_adblock_domains"):
|
|
2708
2798
|
compile_webkit_adblock_rules()
|
|
2709
2799
|
elif cmd == "choose_download_directory":
|
|
2710
|
-
# Fixed: Asynchronous modal sheet targeting caller webview without blocking WebKit IPC
|
|
2711
2800
|
calling_wv = message.webView()
|
|
2712
2801
|
panel = Cocoa.NSOpenPanel.openPanel()
|
|
2713
2802
|
panel.setCanChooseDirectories_(True)
|
|
@@ -2718,22 +2807,22 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2718
2807
|
cur_dir = validate_and_get_download_dir()
|
|
2719
2808
|
panel.setDirectoryURL_(Foundation.NSURL.fileURLWithPath_(cur_dir))
|
|
2720
2809
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
)
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2810
|
+
res = panel.runModal()
|
|
2811
|
+
if res == Cocoa.NSModalResponseOK and panel.URLs():
|
|
2812
|
+
chosen_path = panel.URLs()[0].path()
|
|
2813
|
+
if os.access(chosen_path, os.W_OK):
|
|
2814
|
+
SETTINGS.settings["download_path"] = chosen_path
|
|
2815
|
+
SETTINGS.save()
|
|
2816
|
+
if calling_wv:
|
|
2817
|
+
path_json = json.dumps(chosen_path)
|
|
2818
|
+
calling_wv.evaluateJavaScript_completionHandler_(
|
|
2819
|
+
f"if (document.getElementById('dl-path-input')) document.getElementById('dl-path-input').value = {path_json};"
|
|
2820
|
+
f"if (document.getElementById('dl-path-display')) document.getElementById('dl-path-display').innerText = {path_json};"
|
|
2821
|
+
"showToast('Download directory updated');", None
|
|
2822
|
+
)
|
|
2823
|
+
else:
|
|
2824
|
+
if calling_wv:
|
|
2825
|
+
calling_wv.evaluateJavaScript_completionHandler_("showToast('Error: Selected directory is not writable');", None)
|
|
2737
2826
|
elif cmd == "search_blocked_domains":
|
|
2738
2827
|
matches = search_blocked_domains(body.get("query", ""), limit=100)
|
|
2739
2828
|
json_res = json.dumps(matches)
|
|
@@ -2755,6 +2844,7 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2755
2844
|
clear_history()
|
|
2756
2845
|
if "favicons" in targets:
|
|
2757
2846
|
FaviconCache.clear()
|
|
2847
|
+
self.bookmarks_bar.reload_bookmarks()
|
|
2758
2848
|
if "dns_cache" in targets:
|
|
2759
2849
|
with DNS_LOCK:
|
|
2760
2850
|
DNS_CACHE.clear()
|
|
@@ -2764,10 +2854,12 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2764
2854
|
elif cmd == "reset_defaults":
|
|
2765
2855
|
SETTINGS.reset_defaults()
|
|
2766
2856
|
self.relayout_views()
|
|
2857
|
+
def_zoom = float(SETTINGS.settings.get("default_zoom", 1.0))
|
|
2767
2858
|
for tab in self.tabs:
|
|
2859
|
+
tab["zoom"] = def_zoom
|
|
2768
2860
|
wv = tab.get("webview")
|
|
2769
2861
|
if wv:
|
|
2770
|
-
self.apply_zoom_to_webview(wv)
|
|
2862
|
+
self.apply_zoom_to_webview(wv, tab)
|
|
2771
2863
|
wv = self.get_active_webview()
|
|
2772
2864
|
if wv:
|
|
2773
2865
|
wv.reload()
|
|
@@ -2848,9 +2940,10 @@ class BrowserWindowController(Cocoa.NSWindowController):
|
|
|
2848
2940
|
|
|
2849
2941
|
def webView_decidePolicyForNavigationAction_decisionHandler_(self, wv, action, handler):
|
|
2850
2942
|
mods = action.modifierFlags() if hasattr(action, "modifierFlags") else 0
|
|
2851
|
-
|
|
2943
|
+
cmd_pressed = bool(mods & Cocoa.NSEventModifierFlagCommand)
|
|
2944
|
+
if cmd_pressed or action.navigationType() == WebKit.WKNavigationTypeLinkActivated:
|
|
2852
2945
|
url = action.request().URL().absoluteString() if action.request().URL() else None
|
|
2853
|
-
if url:
|
|
2946
|
+
if cmd_pressed and url:
|
|
2854
2947
|
self.add_tab(url)
|
|
2855
2948
|
handler(WebKit.WKNavigationActionPolicyCancel)
|
|
2856
2949
|
return
|
|
@@ -3025,25 +3118,45 @@ class AppDelegate(Cocoa.NSObject):
|
|
|
3025
3118
|
Cocoa.NSEvent.addLocalMonitorForEventsMatchingMask_handler_(Cocoa.NSEventMaskLeftMouseDown, click_monitor)
|
|
3026
3119
|
|
|
3027
3120
|
def key_monitor(event):
|
|
3028
|
-
|
|
3121
|
+
raw_flags = event.modifierFlags()
|
|
3122
|
+
flags = raw_flags & Cocoa.NSEventModifierFlagDeviceIndependentFlagsMask
|
|
3029
3123
|
cmd = bool(flags & Cocoa.NSEventModifierFlagCommand)
|
|
3030
3124
|
shift = bool(flags & Cocoa.NSEventModifierFlagShift)
|
|
3031
3125
|
opt = bool(flags & Cocoa.NSEventModifierFlagOption)
|
|
3032
3126
|
key = event.keyCode()
|
|
3033
3127
|
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
3128
|
|
|
3037
|
-
|
|
3129
|
+
win = event.window() or Cocoa.NSApp.keyWindow() or Cocoa.NSApp.mainWindow()
|
|
3130
|
+
wc = None
|
|
3131
|
+
if win and isinstance(win.delegate(), BrowserWindowController):
|
|
3132
|
+
wc = win.delegate()
|
|
3133
|
+
elif hasattr(self, 'controllers') and self.controllers:
|
|
3134
|
+
for c in self.controllers:
|
|
3135
|
+
if c.window() == win:
|
|
3136
|
+
wc = c
|
|
3137
|
+
break
|
|
3138
|
+
if not wc and self.controllers:
|
|
3139
|
+
wc = self.controllers[0]
|
|
3140
|
+
|
|
3141
|
+
if key == 53: # Escape key
|
|
3038
3142
|
if shift and not cmd and not opt:
|
|
3039
3143
|
self.process_manager.show()
|
|
3040
3144
|
return None
|
|
3041
|
-
if opt and wc:
|
|
3145
|
+
if opt and not cmd and not shift and wc:
|
|
3042
3146
|
wc.toggle_tab_bar()
|
|
3043
3147
|
return None
|
|
3044
3148
|
if wc:
|
|
3149
|
+
# Allow Escape to pass directly into terminal tabs (vim, nano, fzf, less)
|
|
3150
|
+
if wc.is_active_tab_terminal():
|
|
3151
|
+
return event
|
|
3152
|
+
if wc.find_bar and not wc.find_bar.isHidden():
|
|
3153
|
+
wc.find_bar.setHidden_(True)
|
|
3154
|
+
return None
|
|
3155
|
+
if not wc.tab_bar_visible:
|
|
3156
|
+
wc.toggle_tab_bar()
|
|
3157
|
+
return None
|
|
3045
3158
|
wc.handle_escape_key()
|
|
3046
|
-
return
|
|
3159
|
+
return event
|
|
3047
3160
|
|
|
3048
3161
|
if cmd and shift and not opt:
|
|
3049
3162
|
if key == 0 or chars == 'a':
|
|
@@ -3143,8 +3256,10 @@ class AppDelegate(Cocoa.NSObject):
|
|
|
3143
3256
|
view_menu.addItemWithTitle_action_keyEquivalent_("Reload Page", "onReload:", "r")
|
|
3144
3257
|
view_menu.addItemWithTitle_action_keyEquivalent_("Focus Omnibox", "onFocusOmnibox:", "l")
|
|
3145
3258
|
view_menu.addItemWithTitle_action_keyEquivalent_("Picture-in-Picture", "onTogglePiP:", "p")
|
|
3259
|
+
zen_item = view_menu.addItemWithTitle_action_keyEquivalent_("Toggle Zen Mode", "onToggleZenMode:", "\x1b")
|
|
3260
|
+
zen_item.setKeyEquivalentModifierMask_(Cocoa.NSEventModifierFlagOption)
|
|
3146
3261
|
view_menu.addItem_(Cocoa.NSMenuItem.separatorItem())
|
|
3147
|
-
view_menu.addItemWithTitle_action_keyEquivalent_("Zoom In", "onZoomIn:", "
|
|
3262
|
+
view_menu.addItemWithTitle_action_keyEquivalent_("Zoom In", "onZoomIn:", "=")
|
|
3148
3263
|
view_menu.addItemWithTitle_action_keyEquivalent_("Zoom Out", "onZoomOut:", "-")
|
|
3149
3264
|
view_menu.addItemWithTitle_action_keyEquivalent_("Actual Size", "onResetZoom:", "0")
|
|
3150
3265
|
view_menu.addItem_(Cocoa.NSMenuItem.separatorItem())
|
pybrowser/settings.py
CHANGED
|
@@ -80,6 +80,38 @@ 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
|
+
# Check booleans explicitly first because bool subclasses int in Python
|
|
91
|
+
type_name = type(obj).__name__
|
|
92
|
+
if isinstance(obj, bool) or "Boolean" in type_name or type_name == "bool":
|
|
93
|
+
return bool(obj)
|
|
94
|
+
if isinstance(obj, (int, float)):
|
|
95
|
+
return obj
|
|
96
|
+
if isinstance(obj, (str, bytes)):
|
|
97
|
+
return str(obj)
|
|
98
|
+
if hasattr(obj, "items") or isinstance(obj, dict):
|
|
99
|
+
return {str(k): sanitize_for_json(v) for k, v in obj.items()}
|
|
100
|
+
if hasattr(obj, "__iter__") or isinstance(obj, (list, tuple, set)):
|
|
101
|
+
return [sanitize_for_json(i) for i in obj]
|
|
102
|
+
try:
|
|
103
|
+
# Number conversion for PyObjC NSNumber
|
|
104
|
+
if hasattr(obj, "boolValue") and ("Boolean" in type_name or "bool" in str(type(obj)).lower()):
|
|
105
|
+
return bool(obj.boolValue())
|
|
106
|
+
if hasattr(obj, "doubleValue"):
|
|
107
|
+
val = obj.doubleValue()
|
|
108
|
+
return int(val) if val.is_integer() else val
|
|
109
|
+
if hasattr(obj, "stringValue"):
|
|
110
|
+
return str(obj.stringValue())
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
return str(obj)
|
|
114
|
+
|
|
83
115
|
def validate_and_get_download_dir(target_path=None):
|
|
84
116
|
if not target_path:
|
|
85
117
|
target_path = SETTINGS.settings.get("download_path", os.path.expanduser("~/Downloads"))
|
|
@@ -130,7 +162,7 @@ class SettingsManager:
|
|
|
130
162
|
"vertical_tabs_collapsed": False,
|
|
131
163
|
"show_home_button": False,
|
|
132
164
|
"home_url": "https://www.google.com",
|
|
133
|
-
"default_zoom": 0
|
|
165
|
+
"default_zoom": 1.0,
|
|
134
166
|
"doh_provider": "cloudflare",
|
|
135
167
|
"doh_custom_url": "",
|
|
136
168
|
"custom_adblock_domains": "",
|
|
@@ -158,18 +190,20 @@ class SettingsManager:
|
|
|
158
190
|
except Exception as e:
|
|
159
191
|
print(f"[Settings Load Warning] {e}")
|
|
160
192
|
try:
|
|
161
|
-
z = float(self.settings.get("default_zoom", 0
|
|
193
|
+
z = float(self.settings.get("default_zoom", 1.0))
|
|
162
194
|
self.settings["default_zoom"] = max(0.5, min(2.0, z))
|
|
163
195
|
except Exception:
|
|
164
|
-
self.settings["default_zoom"] = 0
|
|
196
|
+
self.settings["default_zoom"] = 1.0
|
|
165
197
|
self.save()
|
|
166
198
|
|
|
167
199
|
def save(self):
|
|
168
200
|
try:
|
|
169
201
|
dir_name = os.path.dirname(SETTINGS_FILE) or "."
|
|
202
|
+
os.makedirs(dir_name, exist_ok=True)
|
|
203
|
+
sanitized = sanitize_for_json(self.settings)
|
|
170
204
|
fd, temp_path = tempfile.mkstemp(dir=dir_name)
|
|
171
205
|
with os.fdopen(fd, 'w', encoding='utf-8') as f:
|
|
172
|
-
json.dump(
|
|
206
|
+
json.dump(sanitized, f, indent=2)
|
|
173
207
|
os.replace(temp_path, SETTINGS_FILE)
|
|
174
208
|
except Exception as e:
|
|
175
209
|
print(f"[Settings Save Error] {e}")
|
|
@@ -199,7 +233,7 @@ class SettingsManager:
|
|
|
199
233
|
domains = set()
|
|
200
234
|
custom = self.settings.get("custom_adblock_domains", "")
|
|
201
235
|
if custom:
|
|
202
|
-
for d in re.split(r'[\r\n,]+', custom):
|
|
236
|
+
for d in re.split(r'[\r\n,]+', str(custom)):
|
|
203
237
|
d = d.strip().lower()
|
|
204
238
|
if d:
|
|
205
239
|
domains.add(d)
|
|
@@ -226,7 +260,7 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
226
260
|
tab_action = s.get("tab_bar_double_click_action", "maximize")
|
|
227
261
|
downloads_enabled = s.get("downloads_enabled", True)
|
|
228
262
|
cur_dl_path = s.get("download_path", os.path.expanduser("~/Downloads"))
|
|
229
|
-
cur_zoom =
|
|
263
|
+
cur_zoom = f"{float(s.get('default_zoom', 1.0)):.2f}"
|
|
230
264
|
|
|
231
265
|
return f"""<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>Settings</title>
|
|
232
266
|
<style>
|
|
@@ -296,7 +330,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
296
330
|
</div>
|
|
297
331
|
<div id="custom-search-row" class="row" style="display: {'flex' if engine_preset == 'custom' else 'none'};">
|
|
298
332
|
<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)}"
|
|
333
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(custom_search_url)}"
|
|
334
|
+
onchange="saveSetting('custom_search_url', this.value, 'Custom search URL saved')"
|
|
335
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
300
336
|
</div>
|
|
301
337
|
<div class="row">
|
|
302
338
|
<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 +363,13 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
327
363
|
</select>
|
|
328
364
|
</div>
|
|
329
365
|
<div class="row">
|
|
330
|
-
<div class="label-group"><div class="label-title">Default Zoom Level</div><div class="label-desc">Default page scaling factor
|
|
366
|
+
<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
367
|
<select class="m3-select" onchange="saveSetting('default_zoom', parseFloat(this.value), 'Zoom level updated')">
|
|
332
368
|
<option value="0.75" {'selected' if cur_zoom == '0.75' else ''}>75%</option>
|
|
333
|
-
<option value="0.
|
|
334
|
-
<option value="1.
|
|
369
|
+
<option value="0.80" {'selected' if cur_zoom == '0.80' else ''}>80%</option>
|
|
370
|
+
<option value="1.00" {'selected' if cur_zoom in ('1.00', '1.0', '1') else ''}>100% (Default)</option>
|
|
335
371
|
<option value="1.25" {'selected' if cur_zoom == '1.25' else ''}>125%</option>
|
|
336
|
-
<option value="1.
|
|
372
|
+
<option value="1.50" {'selected' if cur_zoom == '1.50' else ''}>150%</option>
|
|
337
373
|
</select>
|
|
338
374
|
</div>
|
|
339
375
|
<div class="row">
|
|
@@ -359,7 +395,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
359
395
|
</div>
|
|
360
396
|
<div class="row">
|
|
361
397
|
<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'))}"
|
|
398
|
+
<input type="text" class="m3-input" style="width:260px;" value="{html.escape(s.get('home_url', 'https://www.google.com'))}"
|
|
399
|
+
onchange="saveSetting('home_url', this.value, 'Home URL saved')"
|
|
400
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
363
401
|
</div>
|
|
364
402
|
</div>
|
|
365
403
|
</div>
|
|
@@ -395,6 +433,13 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
395
433
|
<span class="slider"></span>
|
|
396
434
|
</label>
|
|
397
435
|
</div>
|
|
436
|
+
<div class="row">
|
|
437
|
+
<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>
|
|
438
|
+
<label class="switch">
|
|
439
|
+
<input type="checkbox" {'checked' if s.get('block_popups', True) else ''} onchange="saveSetting('block_popups', this.checked, 'Popup blocker toggled')">
|
|
440
|
+
<span class="slider"></span>
|
|
441
|
+
</label>
|
|
442
|
+
</div>
|
|
398
443
|
<div class="row-col">
|
|
399
444
|
<div class="label-group" style="max-width: 100%;">
|
|
400
445
|
<div class="label-title">Rule & Domain Inspector</div>
|
|
@@ -426,7 +471,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
426
471
|
</div>
|
|
427
472
|
<div id="custom-doh-row" class="row" style="display: {'flex' if doh_provider == 'custom' else 'none'};">
|
|
428
473
|
<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://..."
|
|
474
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(doh_custom_url)}" placeholder="https://..."
|
|
475
|
+
onchange="saveSetting('doh_custom_url', this.value, 'Custom DoH URL saved')"
|
|
476
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
430
477
|
</div>
|
|
431
478
|
<div class="row">
|
|
432
479
|
<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 +491,8 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
444
491
|
<div class="label-title">Custom Blocked Domains</div>
|
|
445
492
|
<div class="label-desc">Add additional domains to block (comma- or line-separated)</div>
|
|
446
493
|
</div>
|
|
447
|
-
<textarea class="m3-input" style="width:100%; height:75px; resize:vertical;" placeholder="adserver.com tracker.telemetry.io"
|
|
494
|
+
<textarea class="m3-input" style="width:100%; height:75px; resize:vertical;" placeholder="adserver.com tracker.telemetry.io"
|
|
495
|
+
onchange="saveSetting('custom_adblock_domains', this.value, 'Custom domains saved')">{html.escape(s.get('custom_adblock_domains', ''))}</textarea>
|
|
448
496
|
</div>
|
|
449
497
|
</div>
|
|
450
498
|
</div>
|
|
@@ -462,7 +510,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
462
510
|
</div>
|
|
463
511
|
<div id="custom-startup-row" class="row" style="display: {'flex' if startup_behavior == 'custom_url' else 'none'};">
|
|
464
512
|
<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'))}"
|
|
513
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(s.get('startup_url', 'https://www.google.com'))}"
|
|
514
|
+
onchange="saveSetting('startup_url', this.value, 'Startup URL saved')"
|
|
515
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
466
516
|
</div>
|
|
467
517
|
<div class="row">
|
|
468
518
|
<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 +550,8 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
500
550
|
</div>
|
|
501
551
|
<div style="display:flex; gap:8px; align-items:center;">
|
|
502
552
|
<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')
|
|
553
|
+
onchange="onDownloadInputChange(this.value)"
|
|
554
|
+
onkeydown="if(event.key==='Enter') {{ onDownloadInputChange(this.value); this.blur(); }}">
|
|
505
555
|
<button class="btn" onclick="browseDownloadFolder()">Browse…</button>
|
|
506
556
|
</div>
|
|
507
557
|
</div>
|
|
@@ -523,7 +573,9 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
523
573
|
</div>
|
|
524
574
|
<div id="custom-ua-row" class="row" style="display: {'flex' if ua_mode == 'custom' else 'none'};">
|
|
525
575
|
<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', ''))}"
|
|
576
|
+
<input type="text" class="m3-input" style="width:280px;" value="{html.escape(s.get('custom_user_agent', ''))}"
|
|
577
|
+
onchange="saveSetting('custom_user_agent', this.value, 'Custom User-Agent saved')"
|
|
578
|
+
onkeydown="if(event.key==='Enter') this.blur();">
|
|
527
579
|
</div>
|
|
528
580
|
<div class="row">
|
|
529
581
|
<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>
|
|
@@ -586,6 +638,13 @@ def get_settings_html(sb_count, base_count, custom_count, active_rules_count):
|
|
|
586
638
|
window.webkit.messageHandlers.bridge.postMessage({{ cmd: 'save_setting', key: key, val: val }});
|
|
587
639
|
showToast(msg);
|
|
588
640
|
}}
|
|
641
|
+
function onDownloadInputChange(val) {{
|
|
642
|
+
const trimmed = (val || '').trim();
|
|
643
|
+
if (!trimmed) return;
|
|
644
|
+
saveSetting('download_path', trimmed, 'Download path saved');
|
|
645
|
+
const disp = document.getElementById('dl-path-display');
|
|
646
|
+
if (disp) disp.innerText = trimmed;
|
|
647
|
+
}}
|
|
589
648
|
function onSearchPresetChange(val) {{
|
|
590
649
|
saveSetting('search_engine_preset', val, 'Search engine updated');
|
|
591
650
|
document.getElementById('custom-search-row').style.display = (val === 'custom') ? 'flex' : 'none';
|
pybrowser/terminal.py
CHANGED
|
@@ -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:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pybrowser/__init__.py,sha256=GVtXkxt_j9dwHYFRqGEWp4HzMX2VTp6HQAPyt8lqYAY,218
|
|
2
|
+
pybrowser/browser.py,sha256=MhhhLRxGZjUiVg-BAaGBwAZas6tK2f-QZI2WLxvt81s,159687
|
|
3
|
+
pybrowser/doh_server.py,sha256=uLlyu5wvn0cXSr2aDKa2EjOlXN4Mhzz81WM53jSA0s4,15253
|
|
4
|
+
pybrowser/incognito.py,sha256=GEc-xeVkElsDPzFIVLhTgSXtDHK3whTzxdnz7wYVJ0o,1675
|
|
5
|
+
pybrowser/main.py,sha256=DwtxNLZ0PrhH0RPUn3UzPFSHQgHcnO7AHriElxxOP2A,2332
|
|
6
|
+
pybrowser/settings.py,sha256=1b1iaZ-DJImRVzDtQH8QFYjiBBDZmPqMnrOr8sG3L5E,41304
|
|
7
|
+
pybrowser/task_manager.py,sha256=DRTazfw6MAqM360DkiMGIC0oZq6XuMBCpkMgHZAJ-II,15079
|
|
8
|
+
pybrowser/terminal.py,sha256=vdu64F3DRf_3KlU_BVzfllPUhb6dGRFjhVqoVr5Xetw,15159
|
|
9
|
+
stupidpythonbrowser-1.0.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
10
|
+
stupidpythonbrowser-1.0.2.dist-info/METADATA,sha256=xOLz3ZmY8CofHyqRAbgEFhxoDa54sDzBHp4wKrJWoJc,977
|
|
11
|
+
stupidpythonbrowser-1.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
stupidpythonbrowser-1.0.2.dist-info/entry_points.txt,sha256=ntsWkCj6QvxpckUUie3agZV8YJQdV5tQMzYfPK4NopM,46
|
|
13
|
+
stupidpythonbrowser-1.0.2.dist-info/top_level.txt,sha256=-Qio7UVGVcEO-lZkeLbyefDgD31D6lsx3CxPpKMipKE,10
|
|
14
|
+
stupidpythonbrowser-1.0.2.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
pybrowser/__init__.py,sha256=aJCcRt702Cy9fTLbE1N3qudmz28OxHG9SgrpPt4wHHY,217
|
|
2
|
-
pybrowser/browser.py,sha256=dp_zsN_oHJ0p1b_t-L7eTa7ugIe2mPmE47Bmki8pUg8,154448
|
|
3
|
-
pybrowser/doh_server.py,sha256=uLlyu5wvn0cXSr2aDKa2EjOlXN4Mhzz81WM53jSA0s4,15253
|
|
4
|
-
pybrowser/incognito.py,sha256=GEc-xeVkElsDPzFIVLhTgSXtDHK3whTzxdnz7wYVJ0o,1675
|
|
5
|
-
pybrowser/main.py,sha256=DwtxNLZ0PrhH0RPUn3UzPFSHQgHcnO7AHriElxxOP2A,2332
|
|
6
|
-
pybrowser/settings.py,sha256=JCPBQumdJ-fTgBAatp2TVRCY8fIavA12255_eYWeIXI,38817
|
|
7
|
-
pybrowser/task_manager.py,sha256=DRTazfw6MAqM360DkiMGIC0oZq6XuMBCpkMgHZAJ-II,15079
|
|
8
|
-
pybrowser/terminal.py,sha256=3pXosw9IfpcuPE59lHDpahHMeJq5lVNt-OrRgAIDshc,15124
|
|
9
|
-
stupidpythonbrowser-1.0.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
10
|
-
stupidpythonbrowser-1.0.1.dist-info/METADATA,sha256=yzzmqRIqveRn2vVhM0bdVLouTr9T4KTsHIniJVilQmk,977
|
|
11
|
-
stupidpythonbrowser-1.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
-
stupidpythonbrowser-1.0.1.dist-info/entry_points.txt,sha256=ntsWkCj6QvxpckUUie3agZV8YJQdV5tQMzYfPK4NopM,46
|
|
13
|
-
stupidpythonbrowser-1.0.1.dist-info/top_level.txt,sha256=-Qio7UVGVcEO-lZkeLbyefDgD31D6lsx3CxPpKMipKE,10
|
|
14
|
-
stupidpythonbrowser-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
{stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{stupidpythonbrowser-1.0.1.dist-info → stupidpythonbrowser-1.0.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|