stupidpythonbrowser 4.7.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 +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-4.7.2.dist-info/METADATA +25 -0
- stupidpythonbrowser-4.7.2.dist-info/RECORD +14 -0
- stupidpythonbrowser-4.7.2.dist-info/WHEEL +5 -0
- stupidpythonbrowser-4.7.2.dist-info/entry_points.txt +2 -0
- stupidpythonbrowser-4.7.2.dist-info/licenses/LICENSE +674 -0
- stupidpythonbrowser-4.7.2.dist-info/top_level.txt +1 -0
|
@@ -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()
|