Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -74,6 +74,17 @@ def _detect_browser() -> str:
|
|
| 74 |
BROWSER = _detect_browser()
|
| 75 |
print(f"๐ Browser: {BROWSER}")
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 78 |
# โโ Firefox Profile (per-user, isolated, crash-recovery DISABLED) โ
|
| 79 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -125,6 +136,38 @@ user_pref("datareporting.policy.dataSubmissionEnabled", false);
|
|
| 125 |
user_pref("app.normandy.enabled", false);
|
| 126 |
user_pref("app.update.enabled", false);
|
| 127 |
user_pref("app.update.auto", false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
"""
|
| 129 |
|
| 130 |
def _ensure_firefox_profile(user_id: str, display: str) -> str:
|
|
@@ -192,8 +235,12 @@ def _start_xvfb(display: str) -> "subprocess.Popen | None":
|
|
| 192 |
pass
|
| 193 |
|
| 194 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
proc = subprocess.Popen(
|
| 196 |
-
["Xvfb", display, "-screen", "0", "
|
| 197 |
"-nolisten", "tcp", "-ac"],
|
| 198 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 199 |
)
|
|
@@ -434,170 +481,90 @@ def _load_capture_image(path: str):
|
|
| 434 |
return None, 0, 0
|
| 435 |
|
| 436 |
|
| 437 |
-
def
|
| 438 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 439 |
from PIL import Image
|
| 440 |
env = {**os.environ, "DISPLAY": display}
|
| 441 |
-
ts = int(time.time() * 1000)
|
| 442 |
-
disp_safe = display.replace(":", "")
|
| 443 |
-
tmp_base = f"/tmp/zss_{disp_safe}_{ts}"
|
| 444 |
-
created_files = []
|
| 445 |
-
|
| 446 |
-
def _cleanup_files():
|
| 447 |
-
for p in created_files:
|
| 448 |
-
try:
|
| 449 |
-
if p and os.path.exists(p):
|
| 450 |
-
os.unlink(p)
|
| 451 |
-
except Exception:
|
| 452 |
-
pass
|
| 453 |
-
|
| 454 |
try:
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
subprocess.run(["import", "-window", "root", "-silent", p2], env=env, timeout=10, capture_output=True)
|
| 469 |
-
img, w, h = _load_capture_image(p2)
|
| 470 |
-
if img and not _is_black_screen(img):
|
| 471 |
-
return img, w, h
|
| 472 |
-
except Exception as e:
|
| 473 |
-
print(f"[cap:{display}] import: {e}")
|
| 474 |
-
|
| 475 |
-
p3 = f"{tmp_base}_ff.png"
|
| 476 |
-
created_files.append(p3)
|
| 477 |
-
try:
|
| 478 |
-
sw, sh = _get_screen_size(display)
|
| 479 |
-
subprocess.run([
|
| 480 |
-
"ffmpeg", "-y", "-f", "x11grab",
|
| 481 |
-
"-video_size", f"{sw}x{sh}",
|
| 482 |
-
"-i", display,
|
| 483 |
-
"-vframes", "1", "-q:v", "1", p3
|
| 484 |
-
], env=env, timeout=12, capture_output=True)
|
| 485 |
-
img, w, h = _load_capture_image(p3)
|
| 486 |
-
if img and not _is_black_screen(img):
|
| 487 |
-
return img, w, h
|
| 488 |
-
except Exception as e:
|
| 489 |
-
print(f"[cap:{display}] ffmpeg: {e}")
|
| 490 |
-
|
| 491 |
-
try:
|
| 492 |
-
from Xlib import display as Xdisp, X
|
| 493 |
-
xd = Xdisp.Display(display)
|
| 494 |
-
root = xd.screen().root
|
| 495 |
-
geom = root.get_geometry()
|
| 496 |
-
w, h = geom.width, geom.height
|
| 497 |
-
raw = root.get_image(0, 0, w, h, X.ZPixmap, 0xFFFFFFFF)
|
| 498 |
-
data = raw.data
|
| 499 |
-
pixels = bytearray(len(data) // 4 * 3)
|
| 500 |
-
for i in range(0, len(data) - 3, 4):
|
| 501 |
-
b, g, r_c = data[i], data[i+1], data[i+2]
|
| 502 |
-
j = (i // 4) * 3
|
| 503 |
-
pixels[j], pixels[j+1], pixels[j+2] = r_c, g, b
|
| 504 |
-
img = Image.frombytes("RGB", (w, h), bytes(pixels))
|
| 505 |
-
if not _is_black_screen(img):
|
| 506 |
-
return img, w, h
|
| 507 |
-
except Exception as e:
|
| 508 |
-
print(f"[cap:{display}] xlib: {e}")
|
| 509 |
-
|
| 510 |
return None, 0, 0
|
| 511 |
-
finally:
|
| 512 |
-
_cleanup_files()
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
def _capture_via_xwd(display: str):
|
| 516 |
-
"""ุงูุทุฑููุฉ ุงูุซุงููุฉ: xwd ุซู
convert ุฅูู PNG."""
|
| 517 |
-
env = {**os.environ, "DISPLAY": display}
|
| 518 |
-
ts = int(time.time() * 1000)
|
| 519 |
-
disp_safe = display.replace(":", "")
|
| 520 |
-
xwd_path = f"/tmp/zss_{disp_safe}_{ts}.xwd"
|
| 521 |
-
png_path = f"/tmp/zss_{disp_safe}_{ts}.png"
|
| 522 |
-
try:
|
| 523 |
-
with open(xwd_path, "wb") as f:
|
| 524 |
-
subprocess.run(["xwd", "-root", "-silent"], env=env, stdout=f, stderr=subprocess.PIPE, timeout=12)
|
| 525 |
-
subprocess.run(["convert", xwd_path, png_path], env=env, capture_output=True, timeout=15)
|
| 526 |
-
img, w, h = _load_capture_image(png_path)
|
| 527 |
-
if img and not _is_black_screen(img):
|
| 528 |
-
return img, w, h
|
| 529 |
-
except FileNotFoundError as e:
|
| 530 |
-
print(f"[cap:{display}] xwd/convert not found: {e}")
|
| 531 |
except Exception as e:
|
| 532 |
-
print(f"[cap:{display}]
|
| 533 |
-
|
| 534 |
-
for p in (xwd_path, png_path):
|
| 535 |
-
try:
|
| 536 |
-
if os.path.exists(p):
|
| 537 |
-
os.unlink(p)
|
| 538 |
-
except Exception:
|
| 539 |
-
pass
|
| 540 |
-
return None, 0, 0
|
| 541 |
|
| 542 |
|
| 543 |
-
def
|
| 544 |
-
"""
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
|
|
|
| 549 |
try:
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
return img, w, h
|
| 560 |
-
except FileNotFoundError as e:
|
| 561 |
-
print(f"[cap:{display}] pyscreenshot not available: {e}")
|
| 562 |
except Exception as e:
|
| 563 |
-
print(f"[cap:{display}]
|
| 564 |
-
|
| 565 |
-
try:
|
| 566 |
-
if os.path.exists(png_path):
|
| 567 |
-
os.unlink(png_path)
|
| 568 |
-
except Exception:
|
| 569 |
-
pass
|
| 570 |
-
return None, 0, 0
|
| 571 |
|
| 572 |
|
| 573 |
def _capture_raw(display: str) -> tuple:
|
| 574 |
-
"""
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
)
|
|
|
|
|
|
|
| 584 |
try:
|
| 585 |
img, w, h = method(display)
|
| 586 |
if img and not _is_black_screen(img):
|
| 587 |
-
|
|
|
|
| 588 |
return img, w, h
|
| 589 |
except Exception as e:
|
| 590 |
print(f"[cap:{display}] {method_name}: {e}")
|
|
|
|
|
|
|
| 591 |
|
| 592 |
print(f"[cap:{display}] โ ๏ธ All methods failed โ placeholder")
|
| 593 |
from PIL import Image as PILImg, ImageDraw
|
| 594 |
sw, sh = _get_screen_size(display)
|
| 595 |
-
img = PILImg.new("RGB", (sw or
|
| 596 |
draw = ImageDraw.Draw(img)
|
| 597 |
draw.rectangle([(0, 0), (sw, 55)], fill=(30, 40, 80))
|
| 598 |
draw.text((10, 10), f"โ ๏ธ Screenshot failed โ DISPLAY={display}", fill=(255, 120, 80))
|
| 599 |
-
draw.text((10, 32), "Methods:
|
| 600 |
-
return img, sw or
|
|
|
|
| 601 |
|
| 602 |
def _get_screen_size(display: str) -> tuple:
|
| 603 |
try:
|
|
@@ -609,7 +576,7 @@ def _get_screen_size(display: str) -> tuple:
|
|
| 609 |
parts = r.stdout.strip().split()
|
| 610 |
return int(parts[0]), int(parts[1])
|
| 611 |
except Exception:
|
| 612 |
-
return
|
| 613 |
|
| 614 |
|
| 615 |
def _get_mouse_pos(display: str) -> tuple:
|
|
@@ -637,15 +604,15 @@ def _draw_mouse_marker(draw, msx, msy, color=(255, 50, 50, 240)):
|
|
| 637 |
|
| 638 |
def _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 639 |
grid_step: int, line_color: tuple, label_every: int,
|
| 640 |
-
major_only_labels: bool = True)
|
| 641 |
"""
|
| 642 |
ูุฑุณู
ูุณุฎุฉ ุดุจูุฉ ุฅุญุฏุงุซูุงุช ูุงุญุฏุฉ ููู ูุณุฎุฉ ู
ู ุงูุตูุฑุฉ ุงูุฃุณุงุณูุฉ.
|
| 643 |
grid_step โ ุงูู
ุณุงูุฉ ุจุงูุจูุณู ุงูุญูููู ุจูู ูู ุฎุท ุดุจูุฉ.
|
| 644 |
-
line_color โ ููู ุงูุฎุทูุท ูุงูุฃุฑูุงู
(RGBA)
|
| 645 |
-
label_every โ ูู ูู
ุจูุณู
|
| 646 |
-
major_only_labels โ ุฅุฐุง Trueุ ุงูุฃุฑูุงู
ุนูู ุงูุญูุงู ุชุธูุฑ ููุท ุนูุฏ ุฎุทูุท "ุฑุฆูุณูุฉ"
|
| 647 |
"""
|
| 648 |
-
from PIL import
|
| 649 |
img = base_img.copy()
|
| 650 |
draw = ImageDraw.Draw(img, "RGBA")
|
| 651 |
|
|
@@ -679,7 +646,6 @@ def _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
|
| 679 |
y_majors.append((y_sc, y_r))
|
| 680 |
y_sc += step_y; y_r += grid_step
|
| 681 |
|
| 682 |
-
# ุชุณู
ูุฉ ุฅุญุฏุงุซู (x,y) ูุงู
ูุฉ ุนูุฏ ูู ุชูุงุทุน "ุฑุฆูุณู" โ ุชุถู
ู ุฑูู
ุงู ุญููููุงู ูุฑูุจุงู ู
ู ูู ู
ูุทูุฉ
|
| 683 |
for (xs, xr) in x_majors:
|
| 684 |
for (ys, yr) in y_majors:
|
| 685 |
label = f"{xr},{yr}"
|
|
@@ -700,26 +666,25 @@ def _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
|
| 700 |
return final
|
| 701 |
|
| 702 |
|
| 703 |
-
def capture_with_grid(display: str, scale: float = 0.
|
| 704 |
force_mx: int | None = None,
|
| 705 |
force_my: int | None = None,
|
| 706 |
grid_step: int = 50) -> dict:
|
| 707 |
"""
|
| 708 |
-
ููุชูุท ุงูุดุงุดุฉ ู
ู display ู
ุญุฏุฏ ููููุชุฌ
|
| 709 |
-
- "data"
|
| 710 |
-
|
| 711 |
-
- "
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
ุงูุซูุงุซ
|
| 715 |
-
ูุถู
ุงู ุชุทุงุจููุง ุชู
ุงู
ุงู ููุชูุงุฏู ุฃู ุชุฃุฎูุฑ ุฃู ุงุฎุชูุงู ุจูููุง.
|
| 716 |
"""
|
| 717 |
from PIL import Image, ImageDraw
|
| 718 |
|
| 719 |
img, ow, oh = _capture_raw(display)
|
| 720 |
if img is None:
|
| 721 |
return {"data": "", "data_grid": "", "data_grid2": "", "data_grid3": "",
|
| 722 |
-
"width":
|
| 723 |
|
| 724 |
mx, my = (force_mx, force_my) if force_mx is not None else _get_mouse_pos(display)
|
| 725 |
|
|
@@ -729,7 +694,6 @@ def capture_with_grid(display: str, scale: float = 0.65, quality: int = 72,
|
|
| 729 |
msx = int(mx * sw / ow)
|
| 730 |
msy = int(my * sh / oh)
|
| 731 |
|
| 732 |
-
# โโ ุงููุณุฎุฉ ุงูุนุงุฏูุฉ ุงููุธููุฉ (ุจุฏูู ุฃู ุดุจูุฉ) โ ููุท ุนูุงู
ุฉ ุงูู
ุงูุณ โโโโโโโโโ
|
| 733 |
clean_img = base_img.copy()
|
| 734 |
draw_clean = ImageDraw.Draw(clean_img, "RGBA")
|
| 735 |
_draw_mouse_marker(draw_clean, msx, msy)
|
|
@@ -738,32 +702,23 @@ def capture_with_grid(display: str, scale: float = 0.65, quality: int = 72,
|
|
| 738 |
clean_final.save(buf_clean, format="JPEG", quality=quality, optimize=True)
|
| 739 |
data_clean = base64.b64encode(buf_clean.getvalue()).decode()
|
| 740 |
|
| 741 |
-
# โโ ูุณุฎุฉ ุดุจูุฉ ุนุงุฏูุฉ: ูู 50pxุ ุฃุฎุถุฑ/ุณู
ุงูู โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 742 |
grid1 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 743 |
grid_step=grid_step, line_color=(0, 255, 180, 235),
|
| 744 |
label_every=100)
|
| 745 |
buf1 = io.BytesIO(); grid1.save(buf1, format="JPEG", quality=quality, optimize=True)
|
| 746 |
data_grid1 = base64.b64encode(buf1.getvalue()).decode()
|
| 747 |
|
| 748 |
-
# โโ ูุณุฎุฉ ุดุจูุฉ ุฃุฏู: ูู 25pxุ ููุณ ุงูุฃููุงู โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 749 |
grid2 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 750 |
-
grid_step=
|
| 751 |
-
label_every=
|
| 752 |
-
buf2 = io.BytesIO(); grid2.save(buf2, format="JPEG", quality=quality, optimize=True)
|
| 753 |
data_grid2 = base64.b64encode(buf2.getvalue()).decode()
|
| 754 |
|
| 755 |
-
# โโ ูุณุฎุฉ ุดุจูุฉ ูุงุฆูุฉ ุงูุฏูุฉ: ูู 10px ุชูุฑูุจุงูุ ุฃุญู
ุฑ ุจุงููุงู
ู โโโโโโโโโโโโ
|
| 756 |
-
grid3 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 757 |
-
grid_step=10, line_color=(255, 60, 60, 235),
|
| 758 |
-
label_every=50, major_only_labels=True)
|
| 759 |
-
buf3 = io.BytesIO(); grid3.save(buf3, format="JPEG", quality=max(quality, 78), optimize=True)
|
| 760 |
-
data_grid3 = base64.b64encode(buf3.getvalue()).decode()
|
| 761 |
-
|
| 762 |
return {
|
| 763 |
-
"data": data_clean,
|
| 764 |
-
"data_grid": data_grid1,
|
| 765 |
-
"data_grid2": data_grid2,
|
| 766 |
-
"data_grid3":
|
| 767 |
"width": ow, "height": oh, "mouse_x": mx, "mouse_y": my,
|
| 768 |
}
|
| 769 |
|
|
@@ -1058,75 +1013,94 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 1058 |
ูุฃู ุงูู
ุญุชูู ู
ุชููุน ุฃู ูููู ู
ุฎุชููุงู (ุตูุญุฉ ุชุญู
ููุช ุฃูุซุฑ) ุญุชู ูู ุชุดุงุจู ุงูู hash
|
| 1059 |
ุฌุฒุฆูุงู ู
ุน ุฎูููุฉ ู
ุดุงุจูุฉ.
|
| 1060 |
"""
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
|
|
|
| 1065 |
|
| 1066 |
-
|
| 1067 |
-
sess["last_bg_shot_ts"] = time.time()
|
| 1068 |
-
if result["data"]:
|
| 1069 |
-
fh = _frame_hash(result["data"])
|
| 1070 |
-
if fh != sess.get("last_bg_hash", ""):
|
| 1071 |
-
sess["last_bg_hash"] = fh
|
| 1072 |
-
await send({
|
| 1073 |
-
"type": "screenshot",
|
| 1074 |
-
"data": result["data"],
|
| 1075 |
-
"data_grid": result.get("data_grid", ""),
|
| 1076 |
-
"data_grid2": result.get("data_grid2", ""),
|
| 1077 |
-
"data_grid3": result.get("data_grid3", ""),
|
| 1078 |
-
"ts": int(time.time() * 1000),
|
| 1079 |
-
"auto": True, "label": label,
|
| 1080 |
-
"screen_width": result["width"],
|
| 1081 |
-
"screen_height": result["height"],
|
| 1082 |
-
"mouse_x": result["mouse_x"],
|
| 1083 |
-
"mouse_y": result["mouse_y"],
|
| 1084 |
-
"has_grid": True,
|
| 1085 |
-
})
|
| 1086 |
-
|
| 1087 |
-
if extra_shot_delay:
|
| 1088 |
-
await asyncio.sleep(extra_shot_delay)
|
| 1089 |
-
result2 = await _safe_capture(0.65, 72, force_mx, force_my)
|
| 1090 |
sess["last_bg_shot_ts"] = time.time()
|
| 1091 |
-
if
|
| 1092 |
-
|
| 1093 |
-
sess
|
| 1094 |
-
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1108 |
|
| 1109 |
async def shot_explicit(label: str = ""):
|
| 1110 |
"""screenshot ุตุฑูุญ โ ููุฑุณู ุฏุงุฆู
ุงู ุจุฏูู delta suppressionุ ููู ุฃููููุฉ ู
ุทููุฉ
|
| 1111 |
-
ุนูู ุฃู ุนู
ููุฉ shot_bg ุชููุงุฆูุฉ ุฌุงุฑูุฉ (ูุง ููุชุธุฑ ุฎูููุง ุทูููุงู).
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
"
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1130 |
|
| 1131 |
# โโ reset_computer: ุฅุนุงุฏุฉ ุถุจุท ุงููู
ุจููุชุฑ ูุฃูู ุฌุฏูุฏ โโโโโโโโโ
|
| 1132 |
if action == "reset_computer":
|
|
@@ -1145,7 +1119,17 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 1145 |
# ูุฐุง ูุถู
ู ุฃู ุฃู ุฅุฌุฑุงุก ูุงุญู (ููุฑุฉุ ุทุจุงุนุฉุ ุทูุจ screenshot ุขุฎุฑ) ูุตู ูููุนุงููุฌ
|
| 1146 |
# ููุฑุงู ุฏูู ุงูุชุธุงุฑ ุงูุชู
ุงู ูุฐู ุงูููุทุฉ ุฃููุงู.
|
| 1147 |
if action == "screenshot":
|
| 1148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1149 |
|
| 1150 |
# โโ terminal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1151 |
elif action == "terminal":
|
|
@@ -1281,8 +1265,9 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 1281 |
_wipe_firefox_session_data(profile_dir)
|
| 1282 |
# ุญูู --profile ุฏุงุฎู ุฃู
ุฑ ูุงูุฑูููุณ (ุฅู ูู
ููู ู
ุญูููุงู ู
ุณุจูุงู)
|
| 1283 |
if "--profile" not in cmd and "-P " not in cmd:
|
| 1284 |
-
|
| 1285 |
-
|
|
|
|
| 1286 |
proc = subprocess.Popen(cmd, shell=True, env=env,
|
| 1287 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 1288 |
if any(b in cmd for b in ("firefox", "chromium", "chrome")):
|
|
@@ -1305,9 +1290,9 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 1305 |
display, timeout=8
|
| 1306 |
)
|
| 1307 |
_wipe_firefox_session_data(profile_dir)
|
| 1308 |
-
env = {**os.environ, "DISPLAY": display}
|
| 1309 |
proc = subprocess.Popen(
|
| 1310 |
-
[BROWSER, "--profile", profile_dir,
|
| 1311 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 1312 |
)
|
| 1313 |
with _display_lock:
|
|
@@ -1507,7 +1492,7 @@ async def health():
|
|
| 1507 |
]
|
| 1508 |
return {
|
| 1509 |
"status": "ok",
|
| 1510 |
-
"version": "
|
| 1511 |
"browser": BROWSER,
|
| 1512 |
"active_users": n,
|
| 1513 |
"users": users,
|
|
@@ -1519,7 +1504,11 @@ async def health():
|
|
| 1519 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1520 |
|
| 1521 |
async def _cleanup_tmp():
|
| 1522 |
-
"""ููุธูู /tmp ูู 5 ุฏูุงุฆู.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1523 |
while True:
|
| 1524 |
await asyncio.sleep(300)
|
| 1525 |
try:
|
|
|
|
| 74 |
BROWSER = _detect_browser()
|
| 75 |
print(f"๐ Browser: {BROWSER}")
|
| 76 |
|
| 77 |
+
# โโ CLI flags ุซุงุจุชุฉ ุชูุถุงู ููู ุชุดุบูู Firefox โโ
|
| 78 |
+
# ุชุณุฑูุน ุงูุฅููุงุน (ูุง GPU ููุฒูุงุฆู ุญูููู ูู Xvfb ููุง ุฏุงุนู ูู
ุญุงููุฉ
|
| 79 |
+
# ุงูุชุณุฑูุน ุงูู
ุฑุฆู ุงูุฐู ููุดู ุฏุงุฆู
ุงู ููุง ููุณุจูุจ ุชุฃุฎูุฑุงู ูู ุงูุฅููุงุน)ุ
|
| 80 |
+
# ูุชู
ูุน ุฃู nag screens ุฃู crash-reporter windows ู
ู ุงูุธููุฑ.
|
| 81 |
+
FIREFOX_CLI_FLAGS = ["--no-remote", "--new-instance"]
|
| 82 |
+
FIREFOX_ENV_EXTRA = {
|
| 83 |
+
# ูู
ูุน ู
ุญุงููุงุช ุชุณุฑูุน GPU ุบูุฑ ุงูู
ุชููุฑุฉ ูู Xvfb ู
ู ุฅุจุทุงุก ุงูุฅููุงุน
|
| 84 |
+
"MOZ_DISABLE_GPU_SANDBOX": "1",
|
| 85 |
+
"MOZ_ACCELERATED": "0",
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 89 |
# โโ Firefox Profile (per-user, isolated, crash-recovery DISABLED) โ
|
| 90 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
|
|
| 136 |
user_pref("app.normandy.enabled", false);
|
| 137 |
user_pref("app.update.enabled", false);
|
| 138 |
user_pref("app.update.auto", false);
|
| 139 |
+
// โโ ุฅุตูุงุญุงุช ุชุฌู
ุฏ ุงูุชุญู
ูู ("Connecting to ... ุชุจูู ููุฃุจุฏ") โโ
|
| 140 |
+
// ุงูุณุจุจ ุงูุบุงูุจ ุนูู ุณูุฑูุฑุงุช ู
ุฌุงููุฉ/container: ู
ุญุงููุฉ IPv6 ุฃููุงู (ุบูุฑ
|
| 141 |
+
// ู
ุฏุนูู
ูุนููุงู ุนูู ุฃุบูุจ ู
ุฒูุฏู Render/HuggingFace ุงูู
ุฌุงููุฉ) ูุจู ุงูุฑุฌูุน
|
| 142 |
+
// ูู IPv4 ุจุนุฏ timeout ุทููู ุฌุฏุงู (ูุฏ ูุตู 20-30 ุซุงููุฉ ููู ุทูุจ DNS)ุ
|
| 143 |
+
// ููุฐุง ุจุงูุถุจุท ุงูุณุจุจ ุงูุฐู ูุธูุฑ ูุดุงุดุฉ "Connecting..." ู
ุนููุฉ ูุง ุชุชูุฏู
ุฃุจุฏุงู.
|
| 144 |
+
user_pref("network.dns.disableIPv6", true);
|
| 145 |
+
user_pref("network.http.fast-fallback-to-IPv4", true);
|
| 146 |
+
// ุชูููู ู
ููุฉ connect ุงูุฅุฌู
ุงููุฉ ููู ุทูุจ ู
ู 15 ุซุงููุฉ (ุงูุงูุชุฑุงุถู) ุฅูู 8 ุซูุงูู
|
| 147 |
+
// โ ุฃูุถู ุฃู ููุดู ุงูุทูุจ ุจุณุฑุนุฉ ูููุฑุฑ ุงูุฐูุงุก ุงูุงุตุทูุงุนู ุจุฏููุงู ู
ู ุฃู ูุจูู ุนุงููุงู ุทูููุงู.
|
| 148 |
+
user_pref("network.http.connection-timeout", 8);
|
| 149 |
+
user_pref("network.http.response.timeout", 12);
|
| 150 |
+
// ุชุนุทูู predictive prefetching/speculative connect ุงูุชู ุชุณุชููู ุนุฑุถ ุญุฒู
ุฉ ุนูู
|
| 151 |
+
// ุณูุฑูุฑ ู
ุญุฏูุฏ ุงูู
ูุงุฑุฏ ุจุฏูู ูุงุฆุฏุฉ ุญููููุฉ ููุง.
|
| 152 |
+
user_pref("network.dns.disablePrefetch", true);
|
| 153 |
+
user_pref("network.prefetch-next", false);
|
| 154 |
+
user_pref("network.predictor.enabled", false);
|
| 155 |
+
user_pref("network.http.speculative-parallel-limit", 0);
|
| 156 |
+
// ุชุนุทูู safebrowsing ุงูุฐู ูุณุชุฏุนู ุทูุจุงุช ุฎุงุฑุฌูุฉ ุฅุถุงููุฉ ุนูุฏ ูุชุญ ูู ุตูุญุฉ
|
| 157 |
+
// (ููุจุทุฆ ุงูุชุญู
ูู ุฃู ูุนููู ูู ุงูุทูุจ ูููุงุฆู
Google ูุดู ุฃู ุงุณุชุบุฑู).
|
| 158 |
+
user_pref("browser.safebrowsing.malware.enabled", false);
|
| 159 |
+
user_pref("browser.safebrowsing.phishing.enabled", false);
|
| 160 |
+
user_pref("browser.safebrowsing.downloads.enabled", false);
|
| 161 |
+
user_pref("browser.safebrowsing.provider.google4.updateURL", "");
|
| 162 |
+
user_pref("browser.safebrowsing.provider.google.updateURL", "");
|
| 163 |
+
// ุชุนุทูู telemetry/captive-portal checks ุงูุชู ุชูุชุญ ุงุชุตุงูุงุช ุฎูููุฉ ุบูุฑ ุถุฑูุฑูุฉ ุนูุฏ ุงูุฅููุงุน
|
| 164 |
+
user_pref("network.captive-portal-service.enabled", false);
|
| 165 |
+
user_pref("network.connectivity-service.enabled", false);
|
| 166 |
+
user_pref("toolkit.telemetry.server", "");
|
| 167 |
+
// ุชูููู ุนุฏุฏ ุงุชุตุงูุงุช HTTP ุงูู
ุชุฒุงู
ูุฉ ููู ุฏูู
ูู โ ูููู ุงูุถุบุท ุนูู ุดุจูุฉ ู
ุญุฏูุฏุฉ
|
| 168 |
+
// ุงููุทุงู ููุณูุฑูุฑุงุช ุงูู
ุฌุงููุฉ ููููู ุงุญุชู
ุงู ุงูุชุนูู.
|
| 169 |
+
user_pref("network.http.max-persistent-connections-per-server", 4);
|
| 170 |
+
user_pref("network.http.max-connections", 48);
|
| 171 |
"""
|
| 172 |
|
| 173 |
def _ensure_firefox_profile(user_id: str, display: str) -> str:
|
|
|
|
| 235 |
pass
|
| 236 |
|
| 237 |
try:
|
| 238 |
+
# โโ ุฏูุฉ 1280x800 ุจุฏู 1920x1080 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 239 |
+
# ูุฐุง ูููู ุญุฌู
ุจูุงูุงุช ูู ููุทุฉ ุดุงุดุฉ ุจูุณุจุฉ ~60%ุ ูุจุงูุชุงูู ูุณุฑูุน ูู
|
| 240 |
+
# ู
ุฑุงุญู ุงูุงูุชูุงุท ูุงูุชุฑู
ูุฒ ุจุดูู ูุจูุฑ ุนูู ุณูุฑูุฑ ุจู
ูุงุฑุฏ ู
ุญุฏูุฏุฉ.
|
| 241 |
+
# ููุณ ุงูุฏูุฉ ุงูู
ุณุชุฎุฏู
ุฉ ูู ุงูุชุทุจููุงุช ุงูู
ุฑุฌุนูุฉ ูู computer-use.
|
| 242 |
proc = subprocess.Popen(
|
| 243 |
+
["Xvfb", display, "-screen", "0", "1280x800x24",
|
| 244 |
"-nolisten", "tcp", "-ac"],
|
| 245 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 246 |
)
|
|
|
|
| 481 |
return None, 0, 0
|
| 482 |
|
| 483 |
|
| 484 |
+
def _capture_via_import_pipe(display: str):
|
| 485 |
+
"""
|
| 486 |
+
ุงูุทุฑููุฉ ุงูุฃุณุงุณูุฉ ูุงููุญูุฏุฉ: `import -window root` ู
ุน ุงูุฅุฎุฑุงุฌ ู
ุจุงุดุฑุฉ
|
| 487 |
+
ุนุจุฑ stdout (pipe) โ ุจุฏูู ุฃู ู
ูู ู
ุคูุช ุนูู ุงููุฑุต. ุฃุณุฑุน ุจุดูู ู
ูุญูุธ ู
ู
|
| 488 |
+
ุงููุชุงุจุฉ ูู
ูู ุซู
ุฅุนุงุฏุฉ ูุฑุงุกุชูุ ุฎุตูุตุงู ุนูู ุชุฎุฒูู ุดุจูู ุจุทูุก.
|
| 489 |
+
"""
|
| 490 |
from PIL import Image
|
| 491 |
env = {**os.environ, "DISPLAY": display}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
try:
|
| 493 |
+
r = subprocess.run(
|
| 494 |
+
["import", "-window", "root", "-silent", "png:-"],
|
| 495 |
+
env=env, timeout=6, capture_output=True
|
| 496 |
+
)
|
| 497 |
+
if r.returncode != 0 or not r.stdout or len(r.stdout) < 500:
|
| 498 |
+
return None, 0, 0
|
| 499 |
+
img = Image.open(io.BytesIO(r.stdout)).convert("RGB")
|
| 500 |
+
w, h = img.size
|
| 501 |
+
if w < 100 or h < 100:
|
| 502 |
+
return None, 0, 0
|
| 503 |
+
return img, w, h
|
| 504 |
+
except FileNotFoundError:
|
| 505 |
+
print(f"[cap:{display}] import (ImageMagick) not installed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
return None, 0, 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
except Exception as e:
|
| 508 |
+
print(f"[cap:{display}] import-pipe: {e}")
|
| 509 |
+
return None, 0, 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
|
| 511 |
|
| 512 |
+
def _capture_via_xlib_direct(display: str):
|
| 513 |
+
"""
|
| 514 |
+
ุฎุท ุงูุฏูุงุน ุงูุซุงูู ูุงูุฃุฎูุฑ: ูุฑุงุกุฉ X11 framebuffer ู
ุจุงุดุฑุฉ ุนุจุฑ
|
| 515 |
+
python-xlibุ ุจุฏูู subprocess ุฅุทูุงูุงู (ุฃุณุฑุน ู
ู ุฃู ุฃุฏุงุฉ ุฎุงุฑุฌูุฉุ
|
| 516 |
+
ููุนู
ู ุญุชู ูู ImageMagick ุบูุฑ ู
ุซุจูุช ุนูู ุงูุณูุฑูุฑ).
|
| 517 |
+
"""
|
| 518 |
+
from PIL import Image
|
| 519 |
try:
|
| 520 |
+
from Xlib import display as Xdisp, X
|
| 521 |
+
xd = Xdisp.Display(display)
|
| 522 |
+
root = xd.screen().root
|
| 523 |
+
geom = root.get_geometry()
|
| 524 |
+
w, h = geom.width, geom.height
|
| 525 |
+
raw = root.get_image(0, 0, w, h, X.ZPixmap, 0xFFFFFFFF)
|
| 526 |
+
img = Image.frombuffer("RGB", (w, h), raw.data, "raw", "BGRX", 0, 1)
|
| 527 |
+
xd.close()
|
| 528 |
+
return img, w, h
|
|
|
|
|
|
|
|
|
|
| 529 |
except Exception as e:
|
| 530 |
+
print(f"[cap:{display}] xlib-direct: {e}")
|
| 531 |
+
return None, 0, 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
|
| 533 |
|
| 534 |
def _capture_raw(display: str) -> tuple:
|
| 535 |
+
"""
|
| 536 |
+
ููุชูุท ุงูุดุงุดุฉ ุจุฃุณุฑุน ุทุฑููุฉ ู
ูุซููุฉ: import-pipe ุฃููุงู (ุณุฑูุนุฉ ุฌุฏุงู)ุ
|
| 537 |
+
ูุนูุฏ ูุดููุง ููุท xlib-direct ูุจุฏูู ูุงุญุฏ. ุจุญุฏ ุฃูุตู ู
ุญุงููุชูู ุณุฑูุนุชูู
|
| 538 |
+
ููู ุทุฑููุฉ (ูููุณ 9 ู
ุญุงููุงุช ูุงูุณุงุจู) โ ูุฃู ููุง ุงูุทุฑููุชูู ุฅู
ุง ุชูุฌุญุงู
|
| 539 |
+
ููุฑุงู ุฃู ุชูุดูุงู ูุณุจุจ ุฏุงุฆู
ุ ูุฅุนุงุฏุฉ ุงูู
ุญุงููุฉ ุฃูุซุฑ ู
ู ู
ุฑุชูู ูุง ุชูุบููุฑ
|
| 540 |
+
ุงููุชูุฌุฉุ ููุท ุชูุจุทุฆ ุงูุงุณุชุฌุงุจุฉ.
|
| 541 |
+
"""
|
| 542 |
+
for method_name, method in (
|
| 543 |
+
("import-pipe", _capture_via_import_pipe),
|
| 544 |
+
("xlib-direct", _capture_via_xlib_direct),
|
| 545 |
+
):
|
| 546 |
+
for attempt in range(2):
|
| 547 |
try:
|
| 548 |
img, w, h = method(display)
|
| 549 |
if img and not _is_black_screen(img):
|
| 550 |
+
if attempt > 0:
|
| 551 |
+
print(f"[cap:{display}] โ
{method_name} succeeded on retry")
|
| 552 |
return img, w, h
|
| 553 |
except Exception as e:
|
| 554 |
print(f"[cap:{display}] {method_name}: {e}")
|
| 555 |
+
if attempt == 0:
|
| 556 |
+
time.sleep(0.3)
|
| 557 |
|
| 558 |
print(f"[cap:{display}] โ ๏ธ All methods failed โ placeholder")
|
| 559 |
from PIL import Image as PILImg, ImageDraw
|
| 560 |
sw, sh = _get_screen_size(display)
|
| 561 |
+
img = PILImg.new("RGB", (sw or 1280, sh or 800), (15, 20, 40))
|
| 562 |
draw = ImageDraw.Draw(img)
|
| 563 |
draw.rectangle([(0, 0), (sw, 55)], fill=(30, 40, 80))
|
| 564 |
draw.text((10, 10), f"โ ๏ธ Screenshot failed โ DISPLAY={display}", fill=(255, 120, 80))
|
| 565 |
+
draw.text((10, 32), "Methods tried: import-pipe, xlib-direct", fill=(130, 130, 150))
|
| 566 |
+
return img, sw or 1280, sh or 800
|
| 567 |
+
|
| 568 |
|
| 569 |
def _get_screen_size(display: str) -> tuple:
|
| 570 |
try:
|
|
|
|
| 576 |
parts = r.stdout.strip().split()
|
| 577 |
return int(parts[0]), int(parts[1])
|
| 578 |
except Exception:
|
| 579 |
+
return 1280, 800
|
| 580 |
|
| 581 |
|
| 582 |
def _get_mouse_pos(display: str) -> tuple:
|
|
|
|
| 604 |
|
| 605 |
def _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 606 |
grid_step: int, line_color: tuple, label_every: int,
|
| 607 |
+
major_only_labels: bool = True):
|
| 608 |
"""
|
| 609 |
ูุฑุณู
ูุณุฎุฉ ุดุจูุฉ ุฅุญุฏุงุซูุงุช ูุงุญุฏุฉ ููู ูุณุฎุฉ ู
ู ุงูุตูุฑุฉ ุงูุฃุณุงุณูุฉ.
|
| 610 |
grid_step โ ุงูู
ุณุงูุฉ ุจุงูุจูุณู ุงูุญูููู ุจูู ูู ุฎุท ุดุจูุฉ.
|
| 611 |
+
line_color โ ููู ุงูุฎุทูุท ูุงูุฃุฑูุงู
(RGBA).
|
| 612 |
+
label_every โ ูู ูู
ุจูุณู ุชููุชุจ ููู ุชุณู
ูุฉ ุฅุญุฏุงุซู (x,y) ูุงู
ูุฉ ุนูุฏ ุงูุชูุงุทุนุงุช.
|
| 613 |
+
major_only_labels โ ุฅุฐุง Trueุ ุงูุฃุฑูุงู
ุนูู ุงูุญูุงู ุชุธูุฑ ููุท ุนูุฏ ุฎุทูุท "ุฑุฆูุณูุฉ".
|
| 614 |
"""
|
| 615 |
+
from PIL import ImageDraw
|
| 616 |
img = base_img.copy()
|
| 617 |
draw = ImageDraw.Draw(img, "RGBA")
|
| 618 |
|
|
|
|
| 646 |
y_majors.append((y_sc, y_r))
|
| 647 |
y_sc += step_y; y_r += grid_step
|
| 648 |
|
|
|
|
| 649 |
for (xs, xr) in x_majors:
|
| 650 |
for (ys, yr) in y_majors:
|
| 651 |
label = f"{xr},{yr}"
|
|
|
|
| 666 |
return final
|
| 667 |
|
| 668 |
|
| 669 |
+
def capture_with_grid(display: str, scale: float = 0.85, quality: int = 72,
|
| 670 |
force_mx: int | None = None,
|
| 671 |
force_my: int | None = None,
|
| 672 |
grid_step: int = 50) -> dict:
|
| 673 |
"""
|
| 674 |
+
ููุชูุท ุงูุดุงุดุฉ ู
ู display ู
ุญุฏุฏ ููููุชุฌ 3 ูุณุฎ ู
ู ููุณ ุงูููุทุฉ ุจุงูุถุจุท (ููุณ ุงููุญุธุฉ):
|
| 675 |
+
- "data" โ ุงููุณุฎุฉ ุงูุนุงุฏูุฉ (ูุธููุฉ ุชู
ุงู
ุงู) + ุนูุงู
ุฉ ุงูู
ุงูุณ ููุทุ ุจุฏูู ุฃู Grid.
|
| 676 |
+
- "data_grid" โ ุดุจูุฉ ุฅุญุฏุงุซูุงุช ุนุงุฏูุฉ (ูู 50px)ุ ุฃุฎุถุฑ/ุณู
ุงูู โ ููุฐูุงุก ุงูุงุตุทูุงุนู.
|
| 677 |
+
- "data_grid2" โ ุดุจูุฉ ุฅุญุฏุงุซูุงุช ุฃุฏู (ูู 20px)ุ ุฃุญู
ุฑ โ ููููุฑุงุช ุงูุฏูููุฉ.
|
| 678 |
+
โโ ุชุจุณูุท v13: ูุณุฎุชุงู ููุท (ุจุฏู 3) ูุชูููู ุนุฏุฏ ุนู
ููุงุช resize/encode ููู
|
| 679 |
+
ููุทุฉ ุจูุณุจุฉ ูุฑุงุจุฉ ุณ4ุฅูู ุณ2ุ ูุฏูุฉ ุงูู
ูุงุณ ุงููุงุญุฏ 1280x800 ุงูุฌุฏูุฏุฉ ูู
|
| 680 |
+
ุชุนุฏ ุชุญุชุงุฌ ูุซูุงุซ ุชุฏุฑุฌุงุช ุฏูุฉ ู
ููุตูุฉ ูุชุญุฏูุฏ ุงูุฅุญุฏุงุซูุงุช.
|
|
|
|
| 681 |
"""
|
| 682 |
from PIL import Image, ImageDraw
|
| 683 |
|
| 684 |
img, ow, oh = _capture_raw(display)
|
| 685 |
if img is None:
|
| 686 |
return {"data": "", "data_grid": "", "data_grid2": "", "data_grid3": "",
|
| 687 |
+
"width": 1280, "height": 800, "mouse_x": 0, "mouse_y": 0}
|
| 688 |
|
| 689 |
mx, my = (force_mx, force_my) if force_mx is not None else _get_mouse_pos(display)
|
| 690 |
|
|
|
|
| 694 |
msx = int(mx * sw / ow)
|
| 695 |
msy = int(my * sh / oh)
|
| 696 |
|
|
|
|
| 697 |
clean_img = base_img.copy()
|
| 698 |
draw_clean = ImageDraw.Draw(clean_img, "RGBA")
|
| 699 |
_draw_mouse_marker(draw_clean, msx, msy)
|
|
|
|
| 702 |
clean_final.save(buf_clean, format="JPEG", quality=quality, optimize=True)
|
| 703 |
data_clean = base64.b64encode(buf_clean.getvalue()).decode()
|
| 704 |
|
|
|
|
| 705 |
grid1 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 706 |
grid_step=grid_step, line_color=(0, 255, 180, 235),
|
| 707 |
label_every=100)
|
| 708 |
buf1 = io.BytesIO(); grid1.save(buf1, format="JPEG", quality=quality, optimize=True)
|
| 709 |
data_grid1 = base64.b64encode(buf1.getvalue()).decode()
|
| 710 |
|
|
|
|
| 711 |
grid2 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 712 |
+
grid_step=20, line_color=(255, 60, 60, 235),
|
| 713 |
+
label_every=60, major_only_labels=True)
|
| 714 |
+
buf2 = io.BytesIO(); grid2.save(buf2, format="JPEG", quality=max(quality, 78), optimize=True)
|
| 715 |
data_grid2 = base64.b64encode(buf2.getvalue()).decode()
|
| 716 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 717 |
return {
|
| 718 |
+
"data": data_clean,
|
| 719 |
+
"data_grid": data_grid1,
|
| 720 |
+
"data_grid2": data_grid2,
|
| 721 |
+
"data_grid3": data_grid2, # ุชูุงูููุฉ ุฑุฌุนูุฉ: ููุณ ููู
ุฉ data_grid2 ูุฃู ููุฏ ูุฏูู
ููุฑุฃ data_grid3
|
| 722 |
"width": ow, "height": oh, "mouse_x": mx, "mouse_y": my,
|
| 723 |
}
|
| 724 |
|
|
|
|
| 1013 |
ูุฃู ุงูู
ุญุชูู ู
ุชููุน ุฃู ูููู ู
ุฎุชููุงู (ุตูุญุฉ ุชุญู
ููุช ุฃูุซุฑ) ุญุชู ูู ุชุดุงุจู ุงูู hash
|
| 1014 |
ุฌุฒุฆูุงู ู
ุน ุฎูููุฉ ู
ุดุงุจูุฉ.
|
| 1015 |
"""
|
| 1016 |
+
try:
|
| 1017 |
+
await asyncio.sleep(delay)
|
| 1018 |
+
now = time.time()
|
| 1019 |
+
if now - sess.get("last_bg_shot_ts", 0) < 0.3:
|
| 1020 |
+
await asyncio.sleep(0.3 - (now - sess["last_bg_shot_ts"]))
|
| 1021 |
|
| 1022 |
+
result = await _safe_capture(0.65, 72, force_mx, force_my)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1023 |
sess["last_bg_shot_ts"] = time.time()
|
| 1024 |
+
if result["data"]:
|
| 1025 |
+
fh = _frame_hash(result["data"])
|
| 1026 |
+
if fh != sess.get("last_bg_hash", ""):
|
| 1027 |
+
sess["last_bg_hash"] = fh
|
| 1028 |
+
await send({
|
| 1029 |
+
"type": "screenshot",
|
| 1030 |
+
"data": result["data"],
|
| 1031 |
+
"data_grid": result.get("data_grid", ""),
|
| 1032 |
+
"data_grid2": result.get("data_grid2", ""),
|
| 1033 |
+
"data_grid3": result.get("data_grid3", ""),
|
| 1034 |
+
"ts": int(time.time() * 1000),
|
| 1035 |
+
"auto": True, "label": label,
|
| 1036 |
+
"screen_width": result["width"],
|
| 1037 |
+
"screen_height": result["height"],
|
| 1038 |
+
"mouse_x": result["mouse_x"],
|
| 1039 |
+
"mouse_y": result["mouse_y"],
|
| 1040 |
+
"has_grid": True,
|
| 1041 |
+
})
|
| 1042 |
+
|
| 1043 |
+
if extra_shot_delay:
|
| 1044 |
+
await asyncio.sleep(extra_shot_delay)
|
| 1045 |
+
result2 = await _safe_capture(0.65, 72, force_mx, force_my)
|
| 1046 |
+
sess["last_bg_shot_ts"] = time.time()
|
| 1047 |
+
if result2["data"]:
|
| 1048 |
+
fh2 = _frame_hash(result2["data"])
|
| 1049 |
+
sess["last_bg_hash"] = fh2
|
| 1050 |
+
await send({
|
| 1051 |
+
"type": "screenshot",
|
| 1052 |
+
"data": result2["data"],
|
| 1053 |
+
"data_grid": result2.get("data_grid", ""),
|
| 1054 |
+
"data_grid2": result2.get("data_grid2", ""),
|
| 1055 |
+
"data_grid3": result2.get("data_grid3", ""),
|
| 1056 |
+
"ts": int(time.time() * 1000),
|
| 1057 |
+
"auto": True, "label": f"{label} (delayed)",
|
| 1058 |
+
"screen_width": result2["width"],
|
| 1059 |
+
"screen_height": result2["height"],
|
| 1060 |
+
"mouse_x": result2["mouse_x"],
|
| 1061 |
+
"mouse_y": result2["mouse_y"],
|
| 1062 |
+
"has_grid": True,
|
| 1063 |
+
})
|
| 1064 |
+
except Exception as e:
|
| 1065 |
+
# ููุทุฉ ุฎูููุฉ ูุงุดูุฉ ูุง ูุฌุจ ุฃู ุชููู ุงูุฌูุณุฉ ุฃู ุชุธูุฑ ุจุตู
ุช ูู ุงูููู ููุท.
|
| 1066 |
+
print(f"[shot_bg:{display}] โ ๏ธ {e}")
|
| 1067 |
|
| 1068 |
async def shot_explicit(label: str = ""):
|
| 1069 |
"""screenshot ุตุฑูุญ โ ููุฑุณู ุฏุงุฆู
ุงู ุจุฏูู delta suppressionุ ููู ุฃููููุฉ ู
ุทููุฉ
|
| 1070 |
+
ุนูู ุฃู ุนู
ููุฉ shot_bg ุชููุงุฆูุฉ ุฌุงุฑูุฉ (ูุง ููุชุธุฑ ุฎูููุง ุทูููุงู).
|
| 1071 |
+
โโ ุฅุตูุงุญ ุฌุฐุฑู: try/except ุดุงู
ู + ุฅุฑุณุงู ุฑุณุงูุฉ ุฎุทุฃ ุตุฑูุญุฉ ููุนู
ูู โโ
|
| 1072 |
+
ุงูู
ุดููุฉ ุงููุฏูู
ุฉ: ูุฐู ุงูุฏุงูุฉ ุชูุดุบููู ุนุจุฑ asyncio.create_task ุจุฏูู ุฃู
|
| 1073 |
+
ู
ุนุงูุฌุฉ ุงุณุชุซูุงุก ู
ุญูุทุฉ ุจูุง. ุฃู ุฎุทุฃ ุบูุฑ ู
ุชููุน ูุงู ููุณูุท ุงูู task ุจุงููุงู
ู
|
| 1074 |
+
ุจุตู
ุช ุชุงู
โ ูุง ุฑุณุงูุฉ ุชุตู ููุนู
ูู ููุง ุญุชู ุณุทุฑ ูู ุงููููุ ููุจูู ุงูุนู
ูู
|
| 1075 |
+
ููุชุธุฑ frame ูู ูุตู ุฃุจุฏุงู. ุงูุขู ุฃู ูุดู ููุณุฌููู ูููุฑุณูู ูุฑุณุงูุฉ "error"
|
| 1076 |
+
ุตุฑูุญุฉุ ูุงูุนู
ูู (index.html) ูุชุนุงู
ู ู
ุนูุง ููุนูุฏ ุงูู
ุญุงููุฉ ููุฑุงู."""
|
| 1077 |
+
try:
|
| 1078 |
+
result = await _priority_capture(0.65, 75)
|
| 1079 |
+
if not result or not result["data"]:
|
| 1080 |
+
print(f"[shot_explicit:{display}] โ ๏ธ empty capture result")
|
| 1081 |
+
await send({"type": "error", "action": "screenshot", "msg": "capture returned empty"})
|
| 1082 |
+
return
|
| 1083 |
+
sess["last_bg_hash"] = _frame_hash(result["data"])
|
| 1084 |
+
await send({
|
| 1085 |
+
"type": "screenshot",
|
| 1086 |
+
"data": result["data"],
|
| 1087 |
+
"data_grid": result.get("data_grid", ""),
|
| 1088 |
+
"data_grid2": result.get("data_grid2", ""),
|
| 1089 |
+
"data_grid3": result.get("data_grid3", ""),
|
| 1090 |
+
"ts": int(time.time() * 1000),
|
| 1091 |
+
"auto": False, "label": label,
|
| 1092 |
+
"screen_width": result["width"],
|
| 1093 |
+
"screen_height": result["height"],
|
| 1094 |
+
"mouse_x": result["mouse_x"],
|
| 1095 |
+
"mouse_y": result["mouse_y"],
|
| 1096 |
+
"has_grid": True,
|
| 1097 |
+
})
|
| 1098 |
+
except Exception as e:
|
| 1099 |
+
print(f"[shot_explicit:{display}] โ EXCEPTION: {e}")
|
| 1100 |
+
try:
|
| 1101 |
+
await send({"type": "error", "action": "screenshot", "msg": str(e)})
|
| 1102 |
+
except Exception:
|
| 1103 |
+
pass
|
| 1104 |
|
| 1105 |
# โโ reset_computer: ุฅุนุงุฏุฉ ุถุจุท ุงููู
ุจููุชุฑ ูุฃูู ุฌุฏูุฏ โโโโโโโโโ
|
| 1106 |
if action == "reset_computer":
|
|
|
|
| 1119 |
# ูุฐุง ูุถู
ู ุฃู ุฃู ุฅุฌุฑุงุก ูุงุญู (ููุฑุฉุ ุทุจุงุนุฉุ ุทูุจ screenshot ุขุฎุฑ) ูุตู ูููุนุงููุฌ
|
| 1120 |
# ููุฑุงู ุฏูู ุงูุชุธุงุฑ ุงูุชู
ุงู ูุฐู ุงูููุทุฉ ุฃููุงู.
|
| 1121 |
if action == "screenshot":
|
| 1122 |
+
# โโ ุทุจูุฉ ุญู
ุงูุฉ ุฅุถุงููุฉ: ุญุชู ูู ุญุตู ุฎุทุฃ ุบูุฑ ู
ุชููุน ุชู
ุงู
ุงู ุฏุงุฎู
|
| 1123 |
+
# shot_explicit ุชููุช ู
ู ุงูู try/except ุงูุฏุงุฎูู (ู
ุซู CancelledError
|
| 1124 |
+
# ุฃู ุฎุทุฃ ุนูุฏ ุฅูุดุงุก ุงูู task ููุณู)ุ ูุฐุง ุงูุบูุงู ุงูุฎุงุฑุฌู ูุถู
ู ุชุณุฌููู
|
| 1125 |
+
# ูู ุงูููู ุจุฏู ุฃู ูุฎุชูู ุจุตู
ุช ูู "unhandled task exception" ูู event
|
| 1126 |
+
# loop ุจุงูุซูู (ููุฐุง ูุงู ูู ุณููู ุงูุชุฑุงุถู ุจุตุงู
ุช ูู ุจุนุถ ุฅุนุฏุงุฏุงุช uvicorn).
|
| 1127 |
+
_task = asyncio.create_task(shot_explicit("explicit screenshot"))
|
| 1128 |
+
def _on_shot_done(t: asyncio.Task):
|
| 1129 |
+
exc = t.exception() if not t.cancelled() else None
|
| 1130 |
+
if exc:
|
| 1131 |
+
print(f"[screenshot-task:{display}] โ unhandled exception: {exc}")
|
| 1132 |
+
_task.add_done_callback(_on_shot_done)
|
| 1133 |
|
| 1134 |
# โโ terminal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1135 |
elif action == "terminal":
|
|
|
|
| 1265 |
_wipe_firefox_session_data(profile_dir)
|
| 1266 |
# ุญูู --profile ุฏุงุฎู ุฃู
ุฑ ูุงูุฑูููุณ (ุฅู ูู
ููู ู
ุญูููุงู ู
ุณุจูุงู)
|
| 1267 |
if "--profile" not in cmd and "-P " not in cmd:
|
| 1268 |
+
flags_str = " ".join(FIREFOX_CLI_FLAGS)
|
| 1269 |
+
cmd = cmd.replace("firefox", f"firefox --profile '{profile_dir}' {flags_str}", 1)
|
| 1270 |
+
env = {**os.environ, "DISPLAY": display, **(FIREFOX_ENV_EXTRA if "firefox" in cmd.lower() else {})}
|
| 1271 |
proc = subprocess.Popen(cmd, shell=True, env=env,
|
| 1272 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 1273 |
if any(b in cmd for b in ("firefox", "chromium", "chrome")):
|
|
|
|
| 1290 |
display, timeout=8
|
| 1291 |
)
|
| 1292 |
_wipe_firefox_session_data(profile_dir)
|
| 1293 |
+
env = {**os.environ, "DISPLAY": display, **FIREFOX_ENV_EXTRA}
|
| 1294 |
proc = subprocess.Popen(
|
| 1295 |
+
[BROWSER, "--profile", profile_dir, *FIREFOX_CLI_FLAGS, url], env=env,
|
| 1296 |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 1297 |
)
|
| 1298 |
with _display_lock:
|
|
|
|
| 1492 |
]
|
| 1493 |
return {
|
| 1494 |
"status": "ok",
|
| 1495 |
+
"version": "v13-fast-capture-fixed-firefox",
|
| 1496 |
"browser": BROWSER,
|
| 1497 |
"active_users": n,
|
| 1498 |
"users": users,
|
|
|
|
| 1504 |
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 1505 |
|
| 1506 |
async def _cleanup_tmp():
|
| 1507 |
+
"""ููุธูู /tmp ูู 5 ุฏูุงุฆู. ู
ูุงุญุธุฉ v13: ู
ุญุฑู ุงูุงูุชูุงุท
|
| 1508 |
+
ุงูุฌุฏูุฏ (import-pipe/xlib-direct) ูุง ููุชุจ ุฃู ู
ููุงุช zss_* ุนูู ุงูุฅุทูุงู (ููุดูุก
|
| 1509 |
+
ูู ุงูุฐุงูุฑุฉ ุนุจุฑ pipe)ุ ููุฐู ุงูุฏุงูุฉ ุฃุตุจุญุช ุบูุฑ ุถุฑูุฑูุฉ ูุนููุงู ูููุทุงุช
|
| 1510 |
+
ุงูุดุงุดุฉุ ููููุง ุชูุฑู ูุดุจูุฉ ุฃู
ุงู ุฅุถุงููุฉ (ู
ุซูุงู ูู ุฃุถูู ููุฏ ู
ุณุชูุจูุงู
|
| 1511 |
+
ููุชุจ ู
ููุงุช ู
ุคูุชุฉ ุจููุณ ุงูุจุงุฏุฆุฉ)."""
|
| 1512 |
while True:
|
| 1513 |
await asyncio.sleep(300)
|
| 1514 |
try:
|