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.
@@ -0,0 +1,366 @@
1
+ """
2
+ PyBrowser Task Manager & Process Controller.
3
+ HUD monitor for WebKit tabs, Host memory/CPU metrics, and live PTY sessions.
4
+ """
5
+
6
+ import Cocoa
7
+ import objc
8
+ import os
9
+ import resource
10
+ import gc
11
+ import WebKit
12
+ from pybrowser.terminal import TERMINAL_MANAGER
13
+
14
+ try:
15
+ import psutil
16
+ HAS_PSUTIL = True
17
+ except ImportError:
18
+ HAS_PSUTIL = False
19
+
20
+ class ProcessListView(Cocoa.NSView):
21
+ def isFlipped(self):
22
+ return True
23
+
24
+ class ProcessManagerController(Cocoa.NSWindowController):
25
+ """Native macOS HUD Process & Task Manager."""
26
+ def init(self):
27
+ w, h = 780, 540
28
+ window = Cocoa.NSPanel.alloc().initWithContentRect_styleMask_backing_defer_(
29
+ Cocoa.NSMakeRect(0, 0, w, h),
30
+ Cocoa.NSWindowStyleMaskTitled | Cocoa.NSWindowStyleMaskClosable |
31
+ Cocoa.NSWindowStyleMaskHUDWindow | Cocoa.NSWindowStyleMaskUtilityWindow |
32
+ Cocoa.NSWindowStyleMaskResizable,
33
+ Cocoa.NSBackingStoreBuffered, False
34
+ )
35
+ window.setTitle_("PyBrowser Task Manager")
36
+ window.setFloatingPanel_(True)
37
+ window.setLevel_(Cocoa.NSFloatingWindowLevel)
38
+ window.setReleasedWhenClosed_(False)
39
+ window.center()
40
+
41
+ self = objc.super(ProcessManagerController, self).initWithWindow_(window)
42
+ if self:
43
+ window.setDelegate_(self)
44
+ self.filter_query = ""
45
+ self.timer = None
46
+ self.action_targets = {}
47
+ self.setup_ui()
48
+ return self
49
+
50
+ def setup_ui(self):
51
+ cv = self.window().contentView()
52
+ w, h = cv.bounds().size.width, cv.bounds().size.height
53
+
54
+ self.top_bar = Cocoa.NSView.alloc().initWithFrame_(Cocoa.NSMakeRect(0, h - 44, w, 44))
55
+ self.top_bar.setAutoresizingMask_(Cocoa.NSViewWidthSizable | Cocoa.NSViewMinYMargin)
56
+ cv.addSubview_(self.top_bar)
57
+
58
+ self.search_field = Cocoa.NSSearchField.alloc().initWithFrame_(Cocoa.NSMakeRect(14, 8, 240, 26))
59
+ self.search_field.setPlaceholderString_("Filter tasks & tabs…")
60
+ self.search_field.setTarget_(self)
61
+ self.search_field.setAction_("onSearchChange:")
62
+ self.top_bar.addSubview_(self.search_field)
63
+
64
+ btn_gc = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(w - 290, 8, 140, 26))
65
+ btn_gc.setTitle_("Purge Memory")
66
+ btn_gc.setBezelStyle_(Cocoa.NSBezelStyleRounded)
67
+ btn_gc.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
68
+ btn_gc.setTarget_(self)
69
+ btn_gc.setAction_("onPurgeMemory:")
70
+ self.top_bar.addSubview_(btn_gc)
71
+
72
+ btn_sleep_all = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(w - 145, 8, 135, 26))
73
+ btn_sleep_all.setTitle_("Sleep Background")
74
+ btn_sleep_all.setBezelStyle_(Cocoa.NSBezelStyleRounded)
75
+ btn_sleep_all.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
76
+ btn_sleep_all.setTarget_(self)
77
+ btn_sleep_all.setAction_("onSleepBackgroundTabs:")
78
+ self.top_bar.addSubview_(btn_sleep_all)
79
+
80
+ self.status_bar = Cocoa.NSView.alloc().initWithFrame_(Cocoa.NSMakeRect(0, 0, w, 28))
81
+ self.status_bar.setAutoresizingMask_(Cocoa.NSViewWidthSizable | Cocoa.NSViewMaxYMargin)
82
+ cv.addSubview_(self.status_bar)
83
+
84
+ self.status_lbl = Cocoa.NSTextField.labelWithString_("Gathering process metrics…")
85
+ self.status_lbl.setFrame_(Cocoa.NSMakeRect(14, 5, w - 28, 18))
86
+ self.status_lbl.setFont_(Cocoa.NSFont.monospacedSystemFontOfSize_weight_(11, Cocoa.NSFontWeightRegular))
87
+ self.status_lbl.setAutoresizingMask_(Cocoa.NSViewWidthSizable)
88
+ self.status_bar.addSubview_(self.status_lbl)
89
+
90
+ self.scroll_view = Cocoa.NSScrollView.alloc().initWithFrame_(Cocoa.NSMakeRect(0, 28, w, h - 72))
91
+ self.scroll_view.setHasVerticalScroller_(True)
92
+ self.scroll_view.setAutoresizingMask_(Cocoa.NSViewWidthSizable | Cocoa.NSViewHeightSizable)
93
+
94
+ self.list_view = ProcessListView.alloc().initWithFrame_(Cocoa.NSMakeRect(0, 0, w, h - 72))
95
+ self.scroll_view.setDocumentView_(self.list_view)
96
+ cv.addSubview_(self.scroll_view)
97
+
98
+ def onSearchChange_(self, sender):
99
+ self.filter_query = self.search_field.stringValue().strip().lower()
100
+ self.refresh_data()
101
+
102
+ def show(self):
103
+ self.window().makeKeyAndOrderFront_(None)
104
+ self.refresh_data()
105
+ if not self.timer:
106
+ self.timer = Cocoa.NSTimer.scheduledTimerWithTimeInterval_repeats_block_(
107
+ 2.5, True, lambda t: self.refresh_data()
108
+ )
109
+
110
+ def windowWillClose_(self, notif):
111
+ if self.timer:
112
+ self.timer.invalidate()
113
+ self.timer = None
114
+
115
+ def onPurgeMemory_(self, sender):
116
+ try:
117
+ from pybrowser.doh_server import DNS_CACHE, DNS_LOCK
118
+ with DNS_LOCK:
119
+ DNS_CACHE.clear()
120
+ except Exception:
121
+ pass
122
+
123
+ try:
124
+ from pybrowser.browser import FaviconCache
125
+ FaviconCache.clear()
126
+ except Exception:
127
+ pass
128
+
129
+ try:
130
+ ds = WebKit.WKWebsiteDataStore.defaultDataStore()
131
+ types = Cocoa.NSSet.setWithArray_([
132
+ WebKit.WKWebsiteDataTypeMemoryCache,
133
+ WebKit.WKWebsiteDataTypeDiskCache
134
+ ])
135
+ ds.removeDataOfTypes_modifiedSince_completionHandler_(
136
+ types, Cocoa.NSDate.dateWithTimeIntervalSince1970_(0), lambda: None
137
+ )
138
+ except Exception:
139
+ pass
140
+ gc.collect()
141
+ self.refresh_data()
142
+
143
+ def onSleepBackgroundTabs_(self, sender):
144
+ app_delegate = Cocoa.NSApp.delegate()
145
+ if not app_delegate:
146
+ return
147
+ for wc in getattr(app_delegate, "controllers", []):
148
+ for i, tab in enumerate(wc.tabs):
149
+ if i != wc.active_tab_index and not tab.get("is_sleeping") and not tab.get("has_audio"):
150
+ wc.sleep_tab(i)
151
+ self.refresh_data()
152
+
153
+ def _find_tab(self, wc, tab_id):
154
+ for i, t in enumerate(wc.tabs):
155
+ if t.get("id") == tab_id:
156
+ return i, t
157
+ return None, None
158
+
159
+ def refresh_data(self):
160
+ if not self.window().isVisible():
161
+ return
162
+ procs = []
163
+ app_delegate = Cocoa.NSApp.delegate()
164
+ pid = Cocoa.NSProcessInfo.processInfo().processIdentifier()
165
+
166
+ mem_rss_mb = 0.0
167
+ cpu_percent = 0.0
168
+ threads_count = 1
169
+
170
+ if HAS_PSUTIL:
171
+ try:
172
+ p = psutil.Process()
173
+ mem_rss_mb = p.memory_info().rss / (1024 * 1024)
174
+ cpu_percent = p.cpu_percent(interval=None)
175
+ threads_count = p.num_threads()
176
+ except Exception:
177
+ pass
178
+ else:
179
+ try:
180
+ mem_rss_mb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / (1024 * 1024)
181
+ except Exception:
182
+ pass
183
+
184
+ procs.append({
185
+ "name": "PyBrowser (Host Process)",
186
+ "sub": f"PID: {pid} | Threads: {threads_count}",
187
+ "type": "host",
188
+ "stat": f"{cpu_percent:.1f}% CPU" if HAS_PSUTIL else "Active",
189
+ "mem": f"{mem_rss_mb:.1f} MB",
190
+ "is_sleeping": False,
191
+ "has_audio": False
192
+ })
193
+
194
+ for sid, session in list(TERMINAL_MANAGER.sessions.items()):
195
+ term_pid = session.proc.pid if session.proc else "-"
196
+ procs.append({
197
+ "name": f"Terminal ({session.cols}×{session.rows})",
198
+ "sub": f"PTY Subprocess (PID: {term_pid})",
199
+ "type": "terminal",
200
+ "stat": "Subprocess Live" if session.alive else "Exited",
201
+ "mem": "Active Shell",
202
+ "is_sleeping": False,
203
+ "has_audio": False,
204
+ "session": session
205
+ })
206
+
207
+ total_tabs = 0
208
+ sleeping_tabs = 0
209
+ if app_delegate and hasattr(app_delegate, "controllers"):
210
+ for wc in app_delegate.controllers:
211
+ for i, tab in enumerate(wc.tabs):
212
+ total_tabs += 1
213
+ is_active = (i == wc.active_tab_index)
214
+ is_sleep = tab.get("is_sleeping", False)
215
+ if is_sleep:
216
+ sleeping_tabs += 1
217
+ has_audio = tab.get("has_audio", False)
218
+ title = tab.get("title") or "New Tab"
219
+ url = tab.get("url") or "about:blank"
220
+
221
+ if self.filter_query and (self.filter_query not in title.lower() and self.filter_query not in url.lower()):
222
+ continue
223
+
224
+ procs.append({
225
+ "name": f"Tab: {title[:48]}",
226
+ "sub": url[:60],
227
+ "type": "tab",
228
+ "stat": "Active View" if is_active else ("Sleeping" if is_sleep else "Background"),
229
+ "mem": "Hibernated" if is_sleep else "~45 MB WebKit",
230
+ "is_sleeping": is_sleep,
231
+ "has_audio": has_audio,
232
+ "wc": wc,
233
+ "tab_id": tab.get("id")
234
+ })
235
+
236
+ self.status_lbl.setStringValue_(
237
+ f"Host Memory: {mem_rss_mb:.1f} MB | Terminals: {len(TERMINAL_MANAGER.sessions)} | "
238
+ f"Tabs: {total_tabs} ({sleeping_tabs} Sleeping)"
239
+ )
240
+
241
+ self.action_targets.clear()
242
+ for sub in list(self.list_view.subviews()):
243
+ sub.removeFromSuperview()
244
+
245
+ row_h = 48
246
+ w = max(self.scroll_view.bounds().size.width, 760)
247
+ y = 0
248
+
249
+ for idx_p, p in enumerate(procs):
250
+ row = Cocoa.NSView.alloc().initWithFrame_(Cocoa.NSMakeRect(0, y, w, row_h))
251
+ icon_str = "🖥️" if p["type"] == "host" else ("💻" if p["type"] == "terminal" else ("💤" if p["is_sleeping"] else ("🔊" if p["has_audio"] else "📑")))
252
+ icon_lbl = Cocoa.NSTextField.labelWithString_(icon_str)
253
+ icon_lbl.setFont_(Cocoa.NSFont.systemFontOfSize_(16))
254
+ icon_lbl.setFrame_(Cocoa.NSMakeRect(14, 14, 24, 20))
255
+ row.addSubview_(icon_lbl)
256
+
257
+ title_lbl = Cocoa.NSTextField.labelWithString_(p["name"])
258
+ title_lbl.setFont_(Cocoa.NSFont.boldSystemFontOfSize_(12))
259
+ title_lbl.setFrame_(Cocoa.NSMakeRect(44, 24, 300, 18))
260
+ row.addSubview_(title_lbl)
261
+
262
+ sub_lbl = Cocoa.NSTextField.labelWithString_(p["sub"])
263
+ sub_lbl.setFont_(Cocoa.NSFont.systemFontOfSize_(11))
264
+ sub_lbl.setTextColor_(Cocoa.NSColor.secondaryLabelColor())
265
+ sub_lbl.setFrame_(Cocoa.NSMakeRect(44, 6, 300, 16))
266
+ row.addSubview_(sub_lbl)
267
+
268
+ stat_lbl = Cocoa.NSTextField.labelWithString_(f"{p['stat']} | {p['mem']}")
269
+ stat_lbl.setFont_(Cocoa.NSFont.monospacedSystemFontOfSize_weight_(11, Cocoa.NSFontWeightMedium))
270
+ stat_lbl.setFrame_(Cocoa.NSMakeRect(350, 14, 170, 18))
271
+ row.addSubview_(stat_lbl)
272
+
273
+ if p["type"] == "tab":
274
+ self.action_targets[idx_p] = ("tab", p["wc"], p["tab_id"])
275
+
276
+ btn_sw = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(w - 220, 11, 46, 24))
277
+ btn_sw.setTitle_("Focus")
278
+ btn_sw.setBezelStyle_(Cocoa.NSBezelStyleInline)
279
+ btn_sw.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
280
+ btn_sw.setTarget_(self)
281
+ btn_sw.setAction_("onTabFocus:")
282
+ btn_sw.setTag_(idx_p)
283
+ row.addSubview_(btn_sw)
284
+
285
+ btn_sl = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(w - 170, 11, 46, 24))
286
+ btn_sl.setTitle_("Wake" if p["is_sleeping"] else "Sleep")
287
+ btn_sl.setBezelStyle_(Cocoa.NSBezelStyleInline)
288
+ btn_sl.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
289
+ btn_sl.setTarget_(self)
290
+ btn_sl.setAction_("onTabToggleSleep:")
291
+ btn_sl.setTag_(idx_p)
292
+ row.addSubview_(btn_sl)
293
+
294
+ btn_end = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(w - 70, 11, 56, 24))
295
+ btn_end.setTitle_("Close")
296
+ btn_end.setBezelStyle_(Cocoa.NSBezelStyleInline)
297
+ btn_end.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
298
+ btn_end.setTarget_(self)
299
+ btn_end.setAction_("onTabClose:")
300
+ btn_end.setTag_(idx_p)
301
+ row.addSubview_(btn_end)
302
+
303
+ elif p["type"] == "terminal":
304
+ self.action_targets[idx_p] = ("terminal", p["session"])
305
+ btn_kill = Cocoa.NSButton.alloc().initWithFrame_(Cocoa.NSMakeRect(w - 70, 11, 56, 24))
306
+ btn_kill.setTitle_("Kill")
307
+ btn_kill.setBezelStyle_(Cocoa.NSBezelStyleInline)
308
+ btn_kill.setAutoresizingMask_(Cocoa.NSViewMinXMargin)
309
+ btn_kill.setTarget_(self)
310
+ btn_kill.setAction_("onTerminalKill:")
311
+ btn_kill.setTag_(idx_p)
312
+ row.addSubview_(btn_kill)
313
+
314
+ div = Cocoa.NSBox.alloc().initWithFrame_(Cocoa.NSMakeRect(14, 0, w - 28, 1))
315
+ div.setBoxType_(Cocoa.NSBoxSeparator)
316
+ div.setAutoresizingMask_(Cocoa.NSViewWidthSizable)
317
+ row.addSubview_(div)
318
+
319
+ self.list_view.addSubview_(row)
320
+ y += row_h
321
+
322
+ self.list_view.setFrameSize_(Cocoa.NSMakeSize(w, max(self.scroll_view.bounds().size.height, y)))
323
+ self.list_view.setNeedsDisplay_(True)
324
+
325
+ def onTabFocus_(self, sender):
326
+ tag = sender.tag()
327
+ if tag in self.action_targets:
328
+ ttype, wc, tab_id = self.action_targets[tag]
329
+ if ttype == "tab":
330
+ idx, _ = self._find_tab(wc, tab_id)
331
+ if idx is not None:
332
+ wc.window().makeKeyAndOrderFront_(None)
333
+ wc.switch_to_tab(idx)
334
+
335
+ def onTabToggleSleep_(self, sender):
336
+ tag = sender.tag()
337
+ if tag in self.action_targets:
338
+ ttype, wc, tab_id = self.action_targets[tag]
339
+ if ttype == "tab":
340
+ idx, tab = self._find_tab(wc, tab_id)
341
+ if idx is not None:
342
+ if tab.get("is_sleeping"):
343
+ wc.wake_tab(idx)
344
+ else:
345
+ wc.sleep_tab(idx)
346
+ self.refresh_data()
347
+
348
+ def onTabClose_(self, sender):
349
+ tag = sender.tag()
350
+ if tag in self.action_targets:
351
+ ttype, wc, tab_id = self.action_targets[tag]
352
+ if ttype == "tab":
353
+ idx, _ = self._find_tab(wc, tab_id)
354
+ if idx is not None:
355
+ wc.close_tab(idx)
356
+ self.refresh_data()
357
+
358
+ def onTerminalKill_(self, sender):
359
+ tag = sender.tag()
360
+ if tag in self.action_targets:
361
+ target = self.action_targets[tag]
362
+ if target[0] == "terminal":
363
+ session = target[1]
364
+ if session:
365
+ session.close()
366
+ self.refresh_data()