stupidpythonbrowser 1.0.1__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 +14 -0
- pybrowser/browser.py +3175 -0
- pybrowser/doh_server.py +423 -0
- pybrowser/incognito.py +36 -0
- pybrowser/main.py +66 -0
- pybrowser/settings.py +656 -0
- pybrowser/task_manager.py +366 -0
- pybrowser/terminal.py +451 -0
- stupidpythonbrowser-1.0.1.dist-info/METADATA +25 -0
- stupidpythonbrowser-1.0.1.dist-info/RECORD +14 -0
- stupidpythonbrowser-1.0.1.dist-info/WHEEL +5 -0
- stupidpythonbrowser-1.0.1.dist-info/entry_points.txt +2 -0
- stupidpythonbrowser-1.0.1.dist-info/licenses/LICENSE +674 -0
- stupidpythonbrowser-1.0.1.dist-info/top_level.txt +1 -0
pybrowser/terminal.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PyBrowser Embedded Terminal Session Manager.
|
|
3
|
+
Spawns native login shells over pseudo-terminals (PTY) and bridges I/O to WebKit.
|
|
4
|
+
Integrates with PyBrowser's DNS-over-HTTPS (DoH) proxy server for secure network I/O.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import pty
|
|
9
|
+
import select
|
|
10
|
+
import subprocess
|
|
11
|
+
import termios
|
|
12
|
+
import fcntl
|
|
13
|
+
import struct
|
|
14
|
+
import threading
|
|
15
|
+
import base64
|
|
16
|
+
import time
|
|
17
|
+
import signal
|
|
18
|
+
import socket
|
|
19
|
+
import Cocoa
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dispatch_to_main(fn, *args):
|
|
23
|
+
"""Executes a function on the macOS main Cocoa run loop."""
|
|
24
|
+
if Cocoa.NSThread.isMainThread():
|
|
25
|
+
fn(*args)
|
|
26
|
+
else:
|
|
27
|
+
Cocoa.NSOperationQueue.mainQueue().addOperationWithBlock_(lambda: fn(*args))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_proxy_port():
|
|
31
|
+
"""Resolves the local proxy port configured in PyBrowser settings."""
|
|
32
|
+
try:
|
|
33
|
+
from pybrowser.settings import LOCAL_PROXY_PORT
|
|
34
|
+
return int(LOCAL_PROXY_PORT)
|
|
35
|
+
except ImportError:
|
|
36
|
+
try:
|
|
37
|
+
from settings import LOCAL_PROXY_PORT
|
|
38
|
+
return int(LOCAL_PROXY_PORT)
|
|
39
|
+
except ImportError:
|
|
40
|
+
return 8888
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_data_dir():
|
|
44
|
+
"""Resolves the PyBrowser data directory path."""
|
|
45
|
+
try:
|
|
46
|
+
from pybrowser.settings import DATA_DIR
|
|
47
|
+
return DATA_DIR
|
|
48
|
+
except ImportError:
|
|
49
|
+
try:
|
|
50
|
+
from settings import DATA_DIR
|
|
51
|
+
return DATA_DIR
|
|
52
|
+
except ImportError:
|
|
53
|
+
return os.path.expanduser("~/data")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def ensure_doh_proxy_running():
|
|
57
|
+
"""Ensures that PyBrowser's local DoH proxy server is running and accepting connections."""
|
|
58
|
+
port = get_proxy_port()
|
|
59
|
+
try:
|
|
60
|
+
with socket.create_connection(("127.0.0.1", port), timeout=0.15):
|
|
61
|
+
return True
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
try:
|
|
67
|
+
from pybrowser.doh_server import (
|
|
68
|
+
start_proxy_server, load_dns_cache, load_adblock_filters_from_disk
|
|
69
|
+
)
|
|
70
|
+
except ImportError:
|
|
71
|
+
from doh_server import (
|
|
72
|
+
start_proxy_server, load_dns_cache, load_adblock_filters_from_disk
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
load_dns_cache()
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
try:
|
|
80
|
+
load_adblock_filters_from_disk()
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
start_proxy_server()
|
|
85
|
+
|
|
86
|
+
deadline = time.time() + 1.0
|
|
87
|
+
while time.time() < deadline:
|
|
88
|
+
try:
|
|
89
|
+
with socket.create_connection(("127.0.0.1", port), timeout=0.1):
|
|
90
|
+
return True
|
|
91
|
+
except Exception:
|
|
92
|
+
time.sleep(0.05)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"[Terminal DoH Integration Warning] Could not ensure proxy server: {e}")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def ensure_doh_cli_helper(bin_dir, proxy_port):
|
|
99
|
+
"""Creates CLI helpers inside ~/data/bin for manual DNS inspection and proxy routing."""
|
|
100
|
+
try:
|
|
101
|
+
os.makedirs(bin_dir, exist_ok=True)
|
|
102
|
+
helper_path = os.path.join(bin_dir, "doh-resolve")
|
|
103
|
+
helper_code = f"""#!/usr/bin/env python3
|
|
104
|
+
import sys
|
|
105
|
+
import urllib.request
|
|
106
|
+
import json
|
|
107
|
+
import socket
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
from pybrowser.doh_server import doh_resolve
|
|
111
|
+
except ImportError:
|
|
112
|
+
try:
|
|
113
|
+
from doh_server import doh_resolve
|
|
114
|
+
except ImportError:
|
|
115
|
+
doh_resolve = None
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
if len(sys.argv) < 2:
|
|
119
|
+
print("Usage: doh-resolve <hostname>")
|
|
120
|
+
sys.exit(1)
|
|
121
|
+
hostname = sys.argv[1].strip()
|
|
122
|
+
if doh_resolve:
|
|
123
|
+
try:
|
|
124
|
+
ip = doh_resolve(hostname)
|
|
125
|
+
print(f"{{hostname}} -> {{ip}} (via PyBrowser DoH Engine)")
|
|
126
|
+
return
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
proxy_handler = urllib.request.ProxyHandler({{
|
|
132
|
+
"http": "http://127.0.0.1:{proxy_port}",
|
|
133
|
+
"https": "http://127.0.0.1:{proxy_port}"
|
|
134
|
+
}})
|
|
135
|
+
opener = urllib.request.build_opener(proxy_handler)
|
|
136
|
+
url = "https://cloudflare-dns.com/dns-query?name=" + hostname + "&type=A"
|
|
137
|
+
req = urllib.request.Request(url, headers={{"Accept": "application/dns-json", "User-Agent": "PyBrowser-DoH-CLI"}})
|
|
138
|
+
with opener.open(req, timeout=5) as resp:
|
|
139
|
+
data = json.loads(resp.read().decode('utf-8'))
|
|
140
|
+
answers = [ans.get("data") for ans in data.get("Answer", []) if ans.get("type") == 1]
|
|
141
|
+
if answers:
|
|
142
|
+
for ip in answers:
|
|
143
|
+
print(f"{{hostname}} -> {{ip}} (via PyBrowser DoH Proxy)")
|
|
144
|
+
return
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
ip = socket.gethostbyname(hostname)
|
|
150
|
+
print(f"{{hostname}} -> {{ip}}")
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"Resolution failed for {{hostname}}: {{e}}")
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
main()
|
|
156
|
+
"""
|
|
157
|
+
with open(helper_path, "w", encoding="utf-8") as f:
|
|
158
|
+
f.write(helper_code)
|
|
159
|
+
os.chmod(helper_path, 0o755)
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TerminalSession:
|
|
165
|
+
"""Non-blocking PTY session streaming raw I/O over Base64 to xterm.js."""
|
|
166
|
+
def __init__(self, webview, cols=80, rows=24, on_close=None):
|
|
167
|
+
self.webview = webview
|
|
168
|
+
self.cols = max(10, int(cols))
|
|
169
|
+
self.rows = max(5, int(rows))
|
|
170
|
+
self.alive = True
|
|
171
|
+
self.master_fd = -1
|
|
172
|
+
self.proc = None
|
|
173
|
+
self.lock = threading.Lock()
|
|
174
|
+
self.on_close = on_close
|
|
175
|
+
self.spawn()
|
|
176
|
+
|
|
177
|
+
def _apply_doh_environment(self, env):
|
|
178
|
+
"""Configures shell environment variables to route network requests and DNS via doh_server."""
|
|
179
|
+
port = get_proxy_port()
|
|
180
|
+
proxy_url = f"http://127.0.0.1:{port}"
|
|
181
|
+
no_proxy_hosts = "localhost,127.0.0.1,::1,.local"
|
|
182
|
+
|
|
183
|
+
# Standard HTTP/HTTPS/All proxy environment variables (lowercase and uppercase)
|
|
184
|
+
env["http_proxy"] = proxy_url
|
|
185
|
+
env["https_proxy"] = proxy_url
|
|
186
|
+
env["all_proxy"] = proxy_url
|
|
187
|
+
env["ftp_proxy"] = proxy_url
|
|
188
|
+
|
|
189
|
+
env["HTTP_PROXY"] = proxy_url
|
|
190
|
+
env["HTTPS_PROXY"] = proxy_url
|
|
191
|
+
env["ALL_PROXY"] = proxy_url
|
|
192
|
+
env["FTP_PROXY"] = proxy_url
|
|
193
|
+
|
|
194
|
+
env["no_proxy"] = no_proxy_hosts
|
|
195
|
+
env["NO_PROXY"] = no_proxy_hosts
|
|
196
|
+
|
|
197
|
+
# Git proxy configuration: ensure git clone / fetch always routes through doh_server
|
|
198
|
+
try:
|
|
199
|
+
count = int(env.get("GIT_CONFIG_COUNT", "0"))
|
|
200
|
+
except (ValueError, TypeError):
|
|
201
|
+
count = 0
|
|
202
|
+
has_git_proxy = False
|
|
203
|
+
for i in range(count):
|
|
204
|
+
if env.get(f"GIT_CONFIG_KEY_{i}") == "http.proxy":
|
|
205
|
+
has_git_proxy = True
|
|
206
|
+
break
|
|
207
|
+
if not has_git_proxy:
|
|
208
|
+
env[f"GIT_CONFIG_KEY_{count}"] = "http.proxy"
|
|
209
|
+
env[f"GIT_CONFIG_VALUE_{count}"] = proxy_url
|
|
210
|
+
env["GIT_CONFIG_COUNT"] = str(count + 1)
|
|
211
|
+
|
|
212
|
+
# Git SSH proxy tunnel fallback via netcat through our CONNECT proxy
|
|
213
|
+
if os.path.exists("/usr/bin/nc") and "GIT_SSH_COMMAND" not in env:
|
|
214
|
+
env["GIT_SSH_COMMAND"] = f"ssh -o ProxyCommand='/usr/bin/nc -X connect -x 127.0.0.1:{port} %h %p'"
|
|
215
|
+
|
|
216
|
+
# Package manager & developer CLI proxy overrides
|
|
217
|
+
env["NPM_CONFIG_PROXY"] = proxy_url
|
|
218
|
+
env["NPM_CONFIG_HTTPS_PROXY"] = proxy_url
|
|
219
|
+
env["npm_config_proxy"] = proxy_url
|
|
220
|
+
env["npm_config_https_proxy"] = proxy_url
|
|
221
|
+
env["PIP_PROXY"] = proxy_url
|
|
222
|
+
env["CARGO_HTTP_PROXY"] = proxy_url
|
|
223
|
+
|
|
224
|
+
# Inject PyBrowser CLI tools into PATH
|
|
225
|
+
bin_dir = os.path.join(get_data_dir(), "bin")
|
|
226
|
+
ensure_doh_cli_helper(bin_dir, port)
|
|
227
|
+
cur_path = env.get("PATH", "/usr/bin:/bin:/usr/sbin:/sbin")
|
|
228
|
+
if bin_dir not in cur_path:
|
|
229
|
+
env["PATH"] = f"{bin_dir}:{cur_path}"
|
|
230
|
+
|
|
231
|
+
# PyBrowser session indicators
|
|
232
|
+
env["PYBROWSER_DOH_PROXY"] = proxy_url
|
|
233
|
+
env["PYBROWSER_DOH_PORT"] = str(port)
|
|
234
|
+
|
|
235
|
+
def spawn(self):
|
|
236
|
+
try:
|
|
237
|
+
# Ensure DoH proxy server is initialized and listening
|
|
238
|
+
ensure_doh_proxy_running()
|
|
239
|
+
|
|
240
|
+
self.master_fd, slave_fd = pty.openpty()
|
|
241
|
+
winsize = struct.pack("HHHH", int(self.rows), int(self.cols), 0, 0)
|
|
242
|
+
fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
|
|
243
|
+
|
|
244
|
+
flags = fcntl.fcntl(self.master_fd, fcntl.F_GETFL)
|
|
245
|
+
fcntl.fcntl(self.master_fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
246
|
+
|
|
247
|
+
env = os.environ.copy()
|
|
248
|
+
env["TERM"] = "xterm-256color"
|
|
249
|
+
env["LANG"] = "en_US.UTF-8"
|
|
250
|
+
env["SHELL"] = "/bin/zsh" if os.path.exists("/bin/zsh") else "/bin/bash"
|
|
251
|
+
env["COLUMNS"] = str(self.cols)
|
|
252
|
+
env["LINES"] = str(self.rows)
|
|
253
|
+
|
|
254
|
+
# Apply DoH proxy routing to all commands run in this terminal
|
|
255
|
+
self._apply_doh_environment(env)
|
|
256
|
+
|
|
257
|
+
# Pre-exec setup to establish controlling terminal for SIGWINCH support
|
|
258
|
+
def _preexec_child():
|
|
259
|
+
try:
|
|
260
|
+
if hasattr(os, "login_tty"):
|
|
261
|
+
os.login_tty(slave_fd)
|
|
262
|
+
elif hasattr(pty, "login_tty"):
|
|
263
|
+
pty.login_tty(slave_fd)
|
|
264
|
+
else:
|
|
265
|
+
os.setsid()
|
|
266
|
+
try:
|
|
267
|
+
fcntl.ioctl(slave_fd, termios.TIOCSCTTY, 0)
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
except Exception:
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
self.proc = subprocess.Popen(
|
|
274
|
+
[env["SHELL"], "-l"],
|
|
275
|
+
stdin=slave_fd,
|
|
276
|
+
stdout=slave_fd,
|
|
277
|
+
stderr=slave_fd,
|
|
278
|
+
preexec_fn=_preexec_child,
|
|
279
|
+
close_fds=True,
|
|
280
|
+
env=env,
|
|
281
|
+
cwd=os.path.expanduser("~")
|
|
282
|
+
)
|
|
283
|
+
os.close(slave_fd)
|
|
284
|
+
threading.Thread(target=self._read_loop, daemon=True).start()
|
|
285
|
+
except Exception as e:
|
|
286
|
+
print(f"[Terminal Spawn Error] {e}")
|
|
287
|
+
|
|
288
|
+
def _read_loop(self):
|
|
289
|
+
while self.alive and self.master_fd >= 0:
|
|
290
|
+
try:
|
|
291
|
+
r, _, _ = select.select([self.master_fd], [], [], 0.05)
|
|
292
|
+
if r:
|
|
293
|
+
with self.lock:
|
|
294
|
+
if self.master_fd < 0:
|
|
295
|
+
break
|
|
296
|
+
try:
|
|
297
|
+
data = os.read(self.master_fd, 8192)
|
|
298
|
+
except (BlockingIOError, InterruptedError):
|
|
299
|
+
continue
|
|
300
|
+
except OSError:
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
if not data:
|
|
304
|
+
break
|
|
305
|
+
b64_str = base64.b64encode(data).decode('ascii')
|
|
306
|
+
wv = self.webview
|
|
307
|
+
if wv:
|
|
308
|
+
dispatch_to_main(lambda b=b64_str, w=wv: self._flush_to_webview(w, b))
|
|
309
|
+
except (OSError, ValueError):
|
|
310
|
+
break
|
|
311
|
+
self.close()
|
|
312
|
+
|
|
313
|
+
def _flush_to_webview(self, wv, b64_str):
|
|
314
|
+
try:
|
|
315
|
+
wv.evaluateJavaScript_completionHandler_(
|
|
316
|
+
f"if (window.writeToTerm) window.writeToTerm('{b64_str}');", None
|
|
317
|
+
)
|
|
318
|
+
except Exception:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
def write(self, text):
|
|
322
|
+
if not self.alive or self.master_fd < 0:
|
|
323
|
+
return
|
|
324
|
+
try:
|
|
325
|
+
data = text.encode('utf-8')
|
|
326
|
+
total = 0
|
|
327
|
+
while total < len(data) and self.master_fd >= 0:
|
|
328
|
+
try:
|
|
329
|
+
with self.lock:
|
|
330
|
+
if self.master_fd < 0:
|
|
331
|
+
break
|
|
332
|
+
n = os.write(self.master_fd, data[total:])
|
|
333
|
+
total += n
|
|
334
|
+
except (BlockingIOError, InterruptedError):
|
|
335
|
+
time.sleep(0.001)
|
|
336
|
+
except OSError:
|
|
337
|
+
break
|
|
338
|
+
except Exception as e:
|
|
339
|
+
print(f"[Terminal Write Error] {e}")
|
|
340
|
+
|
|
341
|
+
def resize(self, cols, rows):
|
|
342
|
+
c = max(10, int(cols))
|
|
343
|
+
r = max(5, int(rows))
|
|
344
|
+
if self.cols == c and self.rows == r:
|
|
345
|
+
return
|
|
346
|
+
self.cols = c
|
|
347
|
+
self.rows = r
|
|
348
|
+
if self.master_fd >= 0 and self.alive:
|
|
349
|
+
try:
|
|
350
|
+
winsize = struct.pack("HHHH", int(self.rows), int(self.cols), 0, 0)
|
|
351
|
+
fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, winsize)
|
|
352
|
+
except Exception as e:
|
|
353
|
+
print(f"[Terminal Resize Error] {e}")
|
|
354
|
+
|
|
355
|
+
# Send SIGWINCH directly to child process group to immediately update vim/less
|
|
356
|
+
if self.proc:
|
|
357
|
+
try:
|
|
358
|
+
pgid = os.getpgid(self.proc.pid)
|
|
359
|
+
os.killpg(pgid, signal.SIGWINCH)
|
|
360
|
+
except Exception:
|
|
361
|
+
pass
|
|
362
|
+
|
|
363
|
+
def close(self):
|
|
364
|
+
if not self.alive and self.master_fd < 0 and self.proc is None:
|
|
365
|
+
return
|
|
366
|
+
self.alive = False
|
|
367
|
+
with self.lock:
|
|
368
|
+
if self.master_fd >= 0:
|
|
369
|
+
try:
|
|
370
|
+
os.close(self.master_fd)
|
|
371
|
+
except Exception:
|
|
372
|
+
pass
|
|
373
|
+
self.master_fd = -1
|
|
374
|
+
|
|
375
|
+
if self.proc:
|
|
376
|
+
try:
|
|
377
|
+
pgid = os.getpgid(self.proc.pid)
|
|
378
|
+
os.killpg(pgid, signal.SIGTERM)
|
|
379
|
+
try:
|
|
380
|
+
self.proc.wait(timeout=0.2)
|
|
381
|
+
except subprocess.TimeoutExpired:
|
|
382
|
+
os.killpg(pgid, signal.SIGKILL)
|
|
383
|
+
self.proc.wait(timeout=0.2)
|
|
384
|
+
except Exception:
|
|
385
|
+
try:
|
|
386
|
+
self.proc.terminate()
|
|
387
|
+
self.proc.wait(timeout=0.2)
|
|
388
|
+
except Exception:
|
|
389
|
+
pass
|
|
390
|
+
self.proc = None
|
|
391
|
+
|
|
392
|
+
self.webview = None
|
|
393
|
+
if self.on_close:
|
|
394
|
+
try:
|
|
395
|
+
self.on_close(self)
|
|
396
|
+
except Exception:
|
|
397
|
+
pass
|
|
398
|
+
self.on_close = None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class TerminalManager:
|
|
402
|
+
def __init__(self):
|
|
403
|
+
self.sessions = {}
|
|
404
|
+
self.lock = threading.Lock()
|
|
405
|
+
|
|
406
|
+
def get_or_create(self, webview, cols=None, rows=None):
|
|
407
|
+
with self.lock:
|
|
408
|
+
session = self.sessions.get(webview)
|
|
409
|
+
if session and session.alive:
|
|
410
|
+
if cols is not None and rows is not None:
|
|
411
|
+
session.resize(cols, rows)
|
|
412
|
+
return session
|
|
413
|
+
if session:
|
|
414
|
+
session.close()
|
|
415
|
+
|
|
416
|
+
c = cols if cols is not None else 80
|
|
417
|
+
r = rows if rows is not None else 24
|
|
418
|
+
session = TerminalSession(
|
|
419
|
+
webview, c, r,
|
|
420
|
+
on_close=lambda s: self._remove_session(webview, s)
|
|
421
|
+
)
|
|
422
|
+
self.sessions[webview] = session
|
|
423
|
+
return session
|
|
424
|
+
|
|
425
|
+
def _remove_session(self, webview, session):
|
|
426
|
+
with self.lock:
|
|
427
|
+
if self.sessions.get(webview) is session:
|
|
428
|
+
self.sessions.pop(webview, None)
|
|
429
|
+
|
|
430
|
+
def write_active(self, webview, text):
|
|
431
|
+
with self.lock:
|
|
432
|
+
session = self.sessions.get(webview)
|
|
433
|
+
if not session or not session.alive:
|
|
434
|
+
session = self.get_or_create(webview)
|
|
435
|
+
session.write(text)
|
|
436
|
+
|
|
437
|
+
def close_for_webview(self, webview):
|
|
438
|
+
with self.lock:
|
|
439
|
+
session = self.sessions.pop(webview, None)
|
|
440
|
+
if session:
|
|
441
|
+
session.close()
|
|
442
|
+
|
|
443
|
+
def close_all(self):
|
|
444
|
+
with self.lock:
|
|
445
|
+
sessions = list(self.sessions.values())
|
|
446
|
+
self.sessions.clear()
|
|
447
|
+
for s in sessions:
|
|
448
|
+
s.close()
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
TERMINAL_MANAGER = TerminalManager()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stupidpythonbrowser
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Native macOS Chromium/Material 3 Web Browser in PyObjC & WebKit
|
|
5
|
+
Author: avevall
|
|
6
|
+
Classifier: Environment :: MacOS X :: Cocoa
|
|
7
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pyobjc-core>=10.0
|
|
18
|
+
Requires-Dist: pyobjc-framework-Cocoa>=10.0
|
|
19
|
+
Requires-Dist: pyobjc-framework-WebKit>=10.0
|
|
20
|
+
Requires-Dist: pyobjc-framework-Network>=10.0
|
|
21
|
+
Requires-Dist: psutil>=5.9.0
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# pythonbrowser
|
|
25
|
+
This is mainly just a personal project, don't take it too seriously and don't expect too much out of it.
|
|
@@ -0,0 +1,14 @@
|
|
|
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,,
|