Pcservercomp / app.py
THEZYZSTUDIO's picture
Update app.py
b1f94a7 verified
Raw
History Blame Contribute Delete
83.2 kB
"""
THE Z AI โ€” Computer Mode Server v11 โ€” PER-USER ISOLATED DISPLAYS
=================================================================
ูƒู„ ู…ุณุชุฎุฏู… ู„ุฏูŠู‡:
โœ… Display Xvfb ุฎุงุต (:100, :101, :102 ...)
โœ… ู…ุชุตูุญ Firefox ู…ุนุฒูˆู„ ุชู…ุงู…ุงู‹ ู„ุง ูŠุชุดุงุฑูƒ ู…ุน ุฃูŠ ู…ุณุชุฎุฏู… ุขุฎุฑ
โœ… ู„ู‚ุทุงุช ุดุงุดุฉ ู…ุณุชู‚ู„ุฉ โ€” ู…ุง ูŠุฑุงู‡ ู…ุณุชุฎุฏู… A ู„ุง ูŠุฎุชู„ุท ุจู…ุณุชุฎุฏู… B
โœ… ุชุงุฑูŠุฎ terminal ู…ุณุชู‚ู„ ู„ูƒู„ ู…ุณุชุฎุฏู…
โœ… ุนู†ุฏ ูƒู„ ุงุชุตุงู„ ุฌุฏูŠุฏ ู„ู„ู…ุณุชุฎุฏู…: ู†ูุณ ุงู„ู€ display (ู„ุง ูŠูุนุงุฏ ุฅู†ุดุงุก Xvfb)
โœ… ุนู†ุฏ ุทู„ุจ ุฑูŠุณุชุงุฑุช / ูƒู…ุจูŠูˆุชุฑ ุฌุฏูŠุฏ: Xvfb + Firefox ูŠูุนุงุฏุงู† ุชู…ุงู…ุงู‹
ุงู„ููƒุฑุฉ:
- ูƒู„ user_id (email) โ†ฆ display ุฑู‚ู… ุซุงุจุช (ู…ุซู„ุงู‹ :100 ู„ู„ุฃูˆู„ุŒ :101 ู„ู„ุซุงู†ูŠ)
- ุฅุฐุง ุงู„ู…ุณุชุฎุฏู… ูŠุชุตู„ ู…ุฌุฏุฏุงู‹ุŒ ู†ูุนูŠุฏ ุงุณุชุฎุฏุงู… ู†ูุณ ุงู„ู€ display ุงู„ุฎุงุต ุจู‡
- ุฅุฐุง ุทู„ุจ ุฑูŠุณุชุงุฑุชุŒ ู†ู‚ุชู„ ุงู„ู€ Xvfb ูˆุงู„ู…ุชุตูุญ ูˆู†ูุนูŠุฏ ุฅู†ุดุงุกู‡ู…ุง
- ุฅุฐุง ุงู„ู…ุณุชุฎุฏู… ุฌุฏูŠุฏุŒ ู†ุฎุตุต ู„ู‡ display ุฌุฏูŠุฏ ูˆู†ูุดุบู‘ู„ Xvfb ู„ู‡
"""
import asyncio
import base64
import hashlib
import io
import json
import os
import re
import shutil
import subprocess
import sys
import time
import urllib.parse
import threading
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, HTMLResponse
import uvicorn
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ ุฅุนุฏุงุฏ Display Pool โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุจุฏุงูŠุฉ ู†ุทุงู‚ ุงู„ู€ displays ุงู„ุงูุชุฑุงุถูŠุฉ (ูŠู…ูƒู† ุชุบูŠูŠุฑู‡)
DISPLAY_BASE = 100
DISPLAY_MAX = 200 # ุฃู‚ุตู‰ ุนุฏุฏ ู…ุณุชุฎุฏู…ูŠู† ู…ุชุฒุงู…ู†ูŠู†
_display_lock = threading.Lock()
# user_id โ†’ display_info
# display_info = {
# "display": ":101",
# "xvfb_proc": subprocess.Popen | None,
# "browser_proc": subprocess.Popen | None,
# "last_bg_shot_ts": float,
# "last_bg_hash": str,
# "active_ws": WebSocket | None, # ุงู„ุงุชุตุงู„ ุงู„ุญุงู„ูŠ ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู…
# }
_user_displays: dict[str, dict] = {}
_display_numbers: set[int] = set() # ุฃุฑู‚ุงู… ุงู„ู€ displays ุงู„ู…ุณุชุฎุฏู…ุฉ
# semaphore ู„ู„ุชุญูƒู… ููŠ ุนู…ู„ูŠุงุช terminal (global ู„ูƒู„ ุงู„ุณูŠุฑูุฑ)
_terminal_sem = asyncio.Semaphore(8)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ ุงูƒุชุดุงู ุงู„ู…ุชุตูุญ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def _detect_browser() -> str:
for c in ["firefox", "firefox-esr", "chromium-browser", "chromium", "google-chrome"]:
r = subprocess.run(["which", c], capture_output=True, text=True)
if r.returncode == 0 and r.stdout.strip():
return c
return "firefox"
BROWSER = _detect_browser()
print(f"๐ŸŒ Browser: {BROWSER}")
# โ”€โ”€ CLI flags ุซุงุจุชุฉ ุชูุถุงู ู„ูƒู„ ุชุดุบูŠู„ Firefox โ”€โ”€
# ุชุณุฑู‘ุน ุงู„ุฅู‚ู„ุงุน (ู„ุง GPU ููŠุฒูŠุงุฆูŠ ุญู‚ูŠู‚ูŠ ููŠ Xvfb ูู„ุง ุฏุงุนูŠ ู„ู…ุญุงูˆู„ุฉ
# ุงู„ุชุณุฑูŠุน ุงู„ู…ุฑุฆูŠ ุงู„ุฐูŠ ูŠูุดู„ ุฏุงุฆู…ุงู‹ ู‡ู†ุง ูˆูŠุณุจู‘ุจ ุชุฃุฎูŠุฑุงู‹ ููŠ ุงู„ุฅู‚ู„ุงุน)ุŒ
# ูˆุชู…ู†ุน ุฃูŠ nag screens ุฃูˆ crash-reporter windows ู…ู† ุงู„ุธู‡ูˆุฑ.
FIREFOX_CLI_FLAGS = ["--no-remote", "--new-instance"]
FIREFOX_ENV_EXTRA = {
# ูŠู…ู†ุน ู…ุญุงูˆู„ุงุช ุชุณุฑูŠุน GPU ุบูŠุฑ ุงู„ู…ุชูˆูุฑุฉ ููŠ Xvfb ู…ู† ุฅุจุทุงุก ุงู„ุฅู‚ู„ุงุน
"MOZ_DISABLE_GPU_SANDBOX": "1",
"MOZ_ACCELERATED": "0",
}
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Firefox Profile (per-user, isolated, crash-recovery DISABLED) โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ุงู„ู…ุดูƒู„ุฉ ุงู„ุฌุฐุฑูŠุฉ ุงู„ุชูŠ ู‡ุฐุง ุงู„ู‚ุณู… ูŠุญู„ู‘ู‡ุง:
# ุนู†ุฏ ู‚ุชู„ Firefox (ุญุชู‰ ุจู€ pkill "ู„ุทูŠู")ุŒ ู„ุง ูŠูุบู„ู‚ ุจุดูƒู„ ู†ุธูŠู ุฏุงุฆู…ุงู‹ ุนู„ู‰
# ุณูŠุฑูุฑ ุจู…ูˆุงุฑุฏ ู…ุญุฏูˆุฏุฉ/XvfbุŒ ูุชูุณุฌูŽู‘ู„ ุงู„ุฌู„ุณุฉ ุนู„ู‰ ุฃู†ู‡ุง "crashed" ุฏุงุฎู„
# sessionstore.jsonlz4 ุงู„ุฎุงุต ุจุงู„ุจุฑูˆูุงูŠู„. ููŠ ุงู„ู…ุฑุฉ ุงู„ุชุงู„ูŠุฉ ูŠูุชุญ Firefox
# ูˆูŠุนุฑุถ ุดุงุดุฉ "Sorry. We're having trouble getting your pages back" ุจุฏู„
# ุงู„ุตูุญุฉ ุงู„ู…ุทู„ูˆุจุฉ โ€” ูˆู‡ุฐู‡ ู‡ูŠ ุงู„ุดุงุดุฉ ุงู„ุณูˆุฏุงุก/ุงู„ูุงุดู„ุฉ ุงู„ุชูŠ ุชุตู„ ูƒู„ู‚ุทุฉ "ู†ุงุฌุญุฉ"
# ุชู‚ู†ูŠุงู‹ (ู„ูŠุณุช ุณูˆุฏุงุก ูุนู„ุงู‹) ู„ูƒู†ู‡ุง ู„ุง ุชุญุชูˆูŠ ุนู„ู‰ ุงู„ู…ุญุชูˆู‰ ุงู„ู…ุทู„ูˆุจ ุฃุจุฏุงู‹.
# ุงู„ุญู„ ุงู„ุฌุฐุฑูŠ: ุจุฑูˆูุงูŠู„ ุฎุงุต ุจูƒู„ ู…ุณุชุฎุฏู… (isolated) ู…ุน user.js ูŠุนุทู‘ู„ ุชู…ุงู…ุงู‹:
# - ุงุณุชุฑุฌุงุน ุงู„ุฌู„ุณุฉ ุจุนุฏ ุงู„ุชุญุทู‘ู… (session restore prompt)
# - ุฃูŠ ู…ุญุงูˆู„ุฉ ู„ุฅุนุงุฏุฉ ูุชุญ ุชุจูˆูŠุจุงุช ุณุงุจู‚ุฉ
# ุจู‡ุฐุงุŒ ูƒู„ ูุชุญ ู„ูุงูŠุฑููˆูƒุณ ูŠุจุฏุฃ ุตูุญุฉ ู†ุธูŠูุฉ ูุงุฑุบุฉ ุฏุงุฆู…ุงู‹ุŒ ุจุบุถ ุงู„ู†ุธุฑ ุนู† ูƒูŠููŠุฉ
# ุฅุบู„ุงู‚ู‡ ุณุงุจู‚ุงู‹.
FIREFOX_PROFILE_PREFS = """
// โ”€โ”€ ุชุนุทูŠู„ ุงุณุชุฑุฌุงุน ุงู„ุฌู„ุณุฉ ู†ู‡ุงุฆูŠุงู‹ (ุณุจุจ ุดุงุดุฉ "Restore Session") โ”€โ”€
user_pref("browser.sessionstore.resume_from_crash", false);
user_pref("browser.sessionstore.resume_session_once", false);
user_pref("browser.sessionstore.max_resumed_crashes", -1);
user_pref("browser.sessionstore.restore_on_demand", false);
user_pref("browser.sessionstore.enabled", false);
user_pref("browser.sessionstore.privacy_level", 2);
user_pref("browser.startup.page", 0);
user_pref("browser.startup.homepage_override.mstone", "ignore");
user_pref("toolkit.startup.max_resumed_crashes", -1);
// ุชุนุทูŠู„ ู†ุงูุฐุฉ "Restore previous session" ูˆู†ูˆุงูุฐ ุงู„ุฃุฒู…ุงุช ุงู„ู…ุฎุชู„ูุฉ
user_pref("browser.sessionstore.max_tabs_undo", 0);
user_pref("browser.sessionstore.max_windows_undo", 0);
user_pref("browser.tabs.crashReporting.sendReport", false);
user_pref("browser.crashReports.unsubmittedCheck.autoSubmit2", false);
user_pref("browser.crashReports.unsubmittedCheck.enabled", false);
user_pref("toolkit.crashreporter.infoURL", "");
// ุชุนุทูŠู„ ุดุงุดุฉ "Restore" ูˆูƒุฐู„ูƒ ุฃูŠ ุฅุดุนุงุฑ ุชุญุทู‘ู…
user_pref("browser.sessionstore.resumeFromCrash", false);
user_pref("dom.ipc.plugins.flash.subprocess.crashreporter.enabled", false);
// ุฅู„ุบุงุก ุงุณุชุนุงุฏุฉ ุขุฎุฑ ุฌู„ุณุฉ ุชู…ุงู…ุงู‹ + ุฏุงุฆู…ุงู‹ about:blank
user_pref("browser.startup.homepage", "about:blank");
// ุชุนุทูŠู„ ุชุญุฏูŠุซุงุช ูˆุฑุณุงุฆู„ onboarding ุงู„ุชูŠ ู‚ุฏ ุชุจุทุฆ ุฃูˆ ุชุญุฌุจ ุงู„ู„ู‚ุทุฉ
user_pref("browser.shell.checkDefaultBrowser", false);
user_pref("browser.aboutwelcome.enabled", false);
user_pref("browser.startup.firstrunSkipsHomepage", true);
user_pref("startup.homepage_welcome_url", "");
user_pref("startup.homepage_welcome_url.additional", "");
user_pref("browser.uitour.enabled", false);
user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false);
user_pref("datareporting.policy.dataSubmissionEnabled", false);
user_pref("app.normandy.enabled", false);
user_pref("app.update.enabled", false);
user_pref("app.update.auto", false);
// โ”€โ”€ ุฅุตู„ุงุญุงุช ุชุฌู…ุฏ ุงู„ุชุญู…ูŠู„ ("Connecting to ... ุชุจู‚ู‰ ู„ู„ุฃุจุฏ") โ”€โ”€
// ุงู„ุณุจุจ ุงู„ุบุงู„ุจ ุนู„ู‰ ุณูŠุฑูุฑุงุช ู…ุฌุงู†ูŠุฉ/container: ู…ุญุงูˆู„ุฉ IPv6 ุฃูˆู„ุงู‹ (ุบูŠุฑ
// ู…ุฏุนูˆู… ูุนู„ูŠุงู‹ ุนู„ู‰ ุฃุบู„ุจ ู…ุฒูˆุฏูŠ Render/HuggingFace ุงู„ู…ุฌุงู†ูŠุฉ) ู‚ุจู„ ุงู„ุฑุฌูˆุน
// ู„ู€ IPv4 ุจุนุฏ timeout ุทูˆูŠู„ ุฌุฏุงู‹ (ู‚ุฏ ูŠุตู„ 20-30 ุซุงู†ูŠุฉ ู„ูƒู„ ุทู„ุจ DNS)ุŒ
// ูˆู‡ุฐุง ุจุงู„ุถุจุท ุงู„ุณุจุจ ุงู„ุฐูŠ ูŠุธู‡ุฑ ูƒุดุงุดุฉ "Connecting..." ู…ุนู„ู‚ุฉ ู„ุง ุชุชู‚ุฏู… ุฃุจุฏุงู‹.
user_pref("network.dns.disableIPv6", true);
user_pref("network.http.fast-fallback-to-IPv4", true);
// ุชู‚ู„ูŠู„ ู…ู‡ู„ุฉ connect ุงู„ุฅุฌู…ุงู„ูŠุฉ ู„ูƒู„ ุทู„ุจ ู…ู† 15 ุซุงู†ูŠุฉ (ุงู„ุงูุชุฑุงุถูŠ) ุฅู„ู‰ 8 ุซูˆุงู†ู
// โ€” ุฃูุถู„ ุฃู† ูŠูุดู„ ุงู„ุทู„ุจ ุจุณุฑุนุฉ ูˆูŠู‚ุฑุฑ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ ุจุฏูŠู„ุงู‹ ู…ู† ุฃู† ูŠุจู‚ู‰ ุนุงู„ู‚ุงู‹ ุทูˆูŠู„ุงู‹.
user_pref("network.http.connection-timeout", 8);
user_pref("network.http.response.timeout", 12);
// ุชุนุทูŠู„ predictive prefetching/speculative connect ุงู„ุชูŠ ุชุณุชู‡ู„ูƒ ุนุฑุถ ุญุฒู…ุฉ ุนู„ู‰
// ุณูŠุฑูุฑ ู…ุญุฏูˆุฏ ุงู„ู…ูˆุงุฑุฏ ุจุฏูˆู† ูุงุฆุฏุฉ ุญู‚ูŠู‚ูŠุฉ ู‡ู†ุง.
user_pref("network.dns.disablePrefetch", true);
user_pref("network.prefetch-next", false);
user_pref("network.predictor.enabled", false);
user_pref("network.http.speculative-parallel-limit", 0);
// ุชุนุทูŠู„ safebrowsing ุงู„ุฐูŠ ูŠุณุชุฏุนูŠ ุทู„ุจุงุช ุฎุงุฑุฌูŠุฉ ุฅุถุงููŠุฉ ุนู†ุฏ ูุชุญ ูƒู„ ุตูุญุฉ
// (ูŠูุจุทุฆ ุงู„ุชุญู…ูŠู„ ุฃูˆ ูŠุนู„ู‚ู‡ ู„ูˆ ุงู„ุทู„ุจ ู„ู‚ูˆุงุฆู… Google ูุดู„ ุฃูˆ ุงุณุชุบุฑู‚).
user_pref("browser.safebrowsing.malware.enabled", false);
user_pref("browser.safebrowsing.phishing.enabled", false);
user_pref("browser.safebrowsing.downloads.enabled", false);
user_pref("browser.safebrowsing.provider.google4.updateURL", "");
user_pref("browser.safebrowsing.provider.google.updateURL", "");
// ุชุนุทูŠู„ telemetry/captive-portal checks ุงู„ุชูŠ ุชูุชุญ ุงุชุตุงู„ุงุช ุฎู„ููŠุฉ ุบูŠุฑ ุถุฑูˆุฑูŠุฉ ุนู†ุฏ ุงู„ุฅู‚ู„ุงุน
user_pref("network.captive-portal-service.enabled", false);
user_pref("network.connectivity-service.enabled", false);
user_pref("toolkit.telemetry.server", "");
// ุชู‚ู„ูŠู„ ุนุฏุฏ ุงุชุตุงู„ุงุช HTTP ุงู„ู…ุชุฒุงู…ู†ุฉ ู„ูƒู„ ุฏูˆู…ูŠู† โ€” ูŠู‚ู„ู„ ุงู„ุถุบุท ุนู„ู‰ ุดุจูƒุฉ ู…ุญุฏูˆุฏุฉ
// ุงู„ู†ุทุงู‚ ู„ู„ุณูŠุฑูุฑุงุช ุงู„ู…ุฌุงู†ูŠุฉ ูˆูŠู‚ู„ู„ ุงุญุชู…ุงู„ ุงู„ุชุนู„ู‚.
user_pref("network.http.max-persistent-connections-per-server", 4);
user_pref("network.http.max-connections", 48);
"""
def _ensure_firefox_profile(user_id: str, display: str) -> str:
"""
ูŠูู†ุดุฆ (ุฃูˆ ูŠูุนูŠุฏ ุงุณุชุฎุฏุงู…) ุจุฑูˆูุงูŠู„ Firefox ู…ุฎุตุต ูˆู†ุธูŠู ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู…ุŒ
ู…ุน ุฅุนุฏุงุฏุงุช ุชุนุทู‘ู„ ุงุณุชุฑุฌุงุน ุงู„ุฌู„ุณุฉ (crash recovery) ู†ู‡ุงุฆูŠุงู‹.
ูŠูุนูŠุฏ ุงู„ู…ุณุงุฑ ุงู„ูƒุงู…ู„ ู„ู„ุจุฑูˆูุงูŠู„.
"""
safe_id = re.sub(r"[^a-zA-Z0-9_.-]", "_", user_id) or "anon"
profile_dir = os.path.expanduser(f"~/.zai_ff_profiles/{safe_id}")
try:
os.makedirs(profile_dir, exist_ok=True)
prefs_path = os.path.join(profile_dir, "user.js")
with open(prefs_path, "w", encoding="utf-8") as f:
f.write(FIREFOX_PROFILE_PREFS)
except Exception as e:
print(f"[profile] โš ๏ธ failed to prepare profile for {user_id}: {e}")
return profile_dir
def _wipe_firefox_session_data(profile_dir: str):
"""
ูŠู…ุณุญ ู…ู„ูุงุช ุงู„ุฌู„ุณุฉ ุงู„ูุงุณุฏุฉ (sessionstore) + ุฃูŠ lock ู…ุชุจู‚ูู‘.
ู‡ุฐุง ุถุฑูˆุฑูŠ ู„ุฃู† ู…ุฌุฑุฏ ุชุนุทูŠู„ session restore ููŠ user.js ู„ุง ูŠู…ุณุญ ู…ู„ูุงุช
ุฌู„ุณุฉ ุณุงุจู‚ุฉ ู…ูˆุฌูˆุฏุฉ ุจุงู„ูุนู„ ุนู„ู‰ ุงู„ู‚ุฑุต ู…ู† ู‚ุจู„ ุชูุนูŠู„ ู‡ุฐู‡ ุงู„ุฅุนุฏุงุฏุงุช.
"""
try:
subprocess.run(
f"rm -f '{profile_dir}'/lock '{profile_dir}'/.parentlock "
f"'{profile_dir}'/sessionstore.jsonlz4 "
f"'{profile_dir}'/sessionstore-backups/*.jsonlz4 "
f"'{profile_dir}'/sessionCheckpoints.json 2>/dev/null",
shell=True, timeout=5, capture_output=True
)
except Exception:
pass
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Xvfb Management (per-display) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def _next_free_display() -> int:
"""ูŠุฌุฏ ุฑู‚ู… display ุญุฑ ุบูŠุฑ ู…ุณุชุฎุฏู…."""
used = _display_numbers.copy()
for n in range(DISPLAY_BASE, DISPLAY_MAX):
if n not in used:
return n
# ุฅุฐุง ุงู…ุชู„ุฃุช ุงู„ู‚ุงุฆู…ุฉ โ€” ุฃุนุฏ ุงุณุชุฎุฏุงู… ุฃู‚ุฏู… display ุบูŠุฑ ู†ุดุท
return DISPLAY_BASE
def _start_xvfb(display: str) -> "subprocess.Popen | None":
"""ูŠูุดุบู‘ู„ Xvfb ุนู„ู‰ display ู…ุญุฏุฏ ูˆูŠูุนูŠุฏ ุงู„ู€ Popen ุฃูˆ None."""
# ุชุญู‚ู‚ ุฃูˆู„ุงู‹: ู‡ู„ ุงู„ู€ display ูŠุนู…ู„ ุจุงู„ูุนู„ุŸ
try:
r = subprocess.run(
["xdpyinfo", "-display", display],
capture_output=True, timeout=3
)
if r.returncode == 0:
print(f"[xvfb] โœ… Display {display} already active")
return None # ูŠุนู…ู„ ุจุงู„ูุนู„ ุจุฏูˆู† Popen ู†ุดุบู‘ู„ู‡
except Exception:
pass
try:
# โ”€โ”€ ุฏู‚ุฉ 1280x800 ุจุฏู„ 1920x1080 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ู‡ุฐุง ูŠู‚ู„ู„ ุญุฌู… ุจูŠุงู†ุงุช ูƒู„ ู„ู‚ุทุฉ ุดุงุดุฉ ุจู†ุณุจุฉ ~60%ุŒ ูˆุจุงู„ุชุงู„ูŠ ูŠุณุฑู‘ุน ูƒู„
# ู…ุฑุงุญู„ ุงู„ุงู„ุชู‚ุงุท ูˆุงู„ุชุฑู…ูŠุฒ ุจุดูƒู„ ูƒุจูŠุฑ ุนู„ู‰ ุณูŠุฑูุฑ ุจู…ูˆุงุฑุฏ ู…ุญุฏูˆุฏุฉ.
# ู†ูุณ ุงู„ุฏู‚ุฉ ุงู„ู…ุณุชุฎุฏู…ุฉ ููŠ ุงู„ุชุทุจูŠู‚ุงุช ุงู„ู…ุฑุฌุนูŠุฉ ู„ู€ computer-use.
proc = subprocess.Popen(
["Xvfb", display, "-screen", "0", "1280x800x24",
"-nolisten", "tcp", "-ac"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
# ุงู†ุชุธุฑ ุญุชู‰ ูŠุตุจุญ ุฌุงู‡ุฒุงู‹ (max 6 ุซูˆุงู†ูŠ)
for _ in range(60):
time.sleep(0.1)
try:
r = subprocess.run(
["xdpyinfo", "-display", display],
capture_output=True, timeout=2
)
if r.returncode == 0:
print(f"[xvfb] โœ… Xvfb started on {display}")
# ุฅุตู„ุงุญ: Xvfb ุจุฏูˆู† ุฃูŠ ุฑุณู… ู„ุงุญู‚ ูŠูƒูˆู† framebuffer ูุงุฑุบุงู‹ ุชู…ุงู…ุงู‹
# (ู„ุง ู„ูˆู† ุฎู„ููŠุฉ ุฃุตู„ุงู‹)ุŒ ูุฃูˆู„ ู„ู‚ุทุฉ ุดุงุดุฉ ู‚ุฏ ุชุจุฏูˆ "ูุงุฑุบุฉ ุจุดูƒู„
# ู…ุฑุจูƒ" ุฑุบู… ุฃู†ู‡ุง ุงู„ุชู‚ุงุท ู†ุงุฌุญ ุชู‚ู†ูŠุงู‹ ู„ุดุงุดุฉ ู†ุธูŠูุฉ ูุนู„ุงู‹. ู†ุฑุณู…
# ู„ูˆู†ุงู‹ ู…ุญุงูŠุฏุงู‹ ุจุณูŠุทุงู‹ ููˆุฑุงู‹ ุนุจุฑ xsetroot ู„ุชูˆุถูŠุญ ุฃู† ุงู„ุดุงุดุฉ
# ุฌุงู‡ุฒุฉ ูˆูุงุฑุบุฉ ุนู…ุฏุงู‹ (ุณุทุญ ู…ูƒุชุจ ู†ุธูŠู) ู„ุง ุฃู†ู‡ุง ุฎุทุฃ ููŠ ุงู„ุงู„ุชู‚ุงุท.
try:
env_xs = {**os.environ, "DISPLAY": display}
subprocess.run(
["xsetroot", "-solid", "#2b2b2b"],
env=env_xs, timeout=2, capture_output=True
)
except Exception:
pass
return proc
except Exception:
continue
print(f"[xvfb] โš ๏ธ Xvfb may not be ready yet on {display}")
return proc
except FileNotFoundError:
print(f"[xvfb] โŒ Xvfb binary not found")
return None
except Exception as e:
print(f"[xvfb] โŒ Failed to start on {display}: {e}")
return None
def _kill_proc(proc: "subprocess.Popen | None"):
"""ูŠู‚ุชู„ process ุจุฃู…ุงู†."""
if not proc:
return
try:
proc.terminate()
proc.wait(timeout=3)
except Exception:
try:
proc.kill()
except Exception:
pass
def _kill_display_processes(display: str):
"""ูŠู‚ุชู„ ูƒู„ ุงู„ุนู…ู„ูŠุงุช ุงู„ู…ุฑุชุจุทุฉ ุจู€ display ู…ุนูŠู†."""
env_d = {**os.environ, "DISPLAY": display}
# ู‚ุชู„ ุงู„ู…ุชุตูุญ โ€” SIGKILL ู…ุจุงุดุฑ (-9) ู„ุชูุงุฏูŠ ุฃูŠ ุญุงู„ุฉ ุฅุบู„ุงู‚ ู†ุตููŠ ุชูุณุฌูŽู‘ู„
# ู„ุงุญู‚ุงู‹ ูƒุชุญุทู‘ู… (crash) ุฏุงุฎู„ Firefox ูˆุชูู†ุชุฌ ุดุงุดุฉ "Restore Session"
for b in ["firefox", "firefox-esr", "chromium", "chrome"]:
try:
subprocess.run(
["pkill", "-9", "-f", f"[{b[0]}]{b[1:]}.*{display}"],
timeout=3, capture_output=True
)
except Exception:
pass
# ู‚ุชู„ Xvfb ุนู„ู‰ ู‡ุฐุง ุงู„ู€ display
try:
subprocess.run(
["pkill", "-f", f"[X]vfb {display}"],
timeout=3, capture_output=True
)
except Exception:
pass
time.sleep(0.5)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Session Management (per user_id) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
async def get_or_create_user_session(user_id: str, ws: WebSocket) -> dict:
"""
ูŠูุนูŠุฏ session ุงู„ู…ุณุชุฎุฏู… (ุฃูˆ ูŠูู†ุดุฆ ูˆุงุญุฏุฉ ุฌุฏูŠุฏุฉ ุฅุฐุง ู„ู… ุชูƒู† ู…ูˆุฌูˆุฏุฉ).
ูƒู„ user_id โ† display ุฎุงุต + Xvfb ุฎุงุต.
"""
with _display_lock:
if user_id in _user_displays:
sess = _user_displays[user_id]
# ุญุฏู‘ุซ ุงู„ู€ WebSocket ุงู„ุญุงู„ูŠ
sess["active_ws"] = ws
print(f"[session] ๐Ÿ” Reconnected user '{user_id}' on display {sess['display']}")
return sess
# ู…ุณุชุฎุฏู… ุฌุฏูŠุฏ โ€” ุฎุตุต ู„ู‡ display
disp_num = _next_free_display()
_display_numbers.add(disp_num)
display = f":{disp_num}"
sess = {
"user_id": user_id,
"display": display,
"xvfb_proc": None, # ุณูŠูุดุบูŽู‘ู„ ู„ุงุญู‚ุงู‹
"browser_proc": None,
"last_bg_shot_ts": 0.0,
"last_bg_hash": "",
"active_ws": ws,
"created": time.time(),
}
_user_displays[user_id] = sess
print(f"[session] โœ… New user '{user_id}' โ†’ display {display}")
return sess
async def reset_user_computer(user_id: str) -> dict:
"""
ูŠูุนูŠุฏ ุถุจุท ุงู„ูƒู…ุจูŠูˆุชุฑ ุงู„ุงูุชุฑุงุถูŠ ู„ู„ู…ุณุชุฎุฏู…:
ูŠู‚ุชู„ Xvfb ูˆุงู„ู…ุชุตูุญ ูˆูŠูุดุบู‘ู„ Xvfb ุฌุฏูŠุฏุงู‹ ู†ุธูŠูุงู‹.
"""
with _display_lock:
sess = _user_displays.get(user_id)
if not sess:
return {}
display = sess["display"]
print(f"[reset] ๐Ÿ”„ Resetting computer for user '{user_id}' on {display}")
# ู‚ุชู„ ุงู„ู…ุชุตูุญ โ€” SIGKILL ู…ุจุงุดุฑ (ุจุฏู„ terminate ุงู„ู„ุทูŠู) ู„ุฃู† ุงู„ุฃุฎูŠุฑ ู‚ุฏ
# ูŠุชุฑูƒ ูุงูŠุฑููˆูƒุณ ููŠ ุญุงู„ุฉ ู†ุตู-ู…ุบู„ู‚ุฉ ุชูุณุฌูŽู‘ู„ ูƒุชุญุทู‘ู… (crash) ููŠ ุงู„ู…ุฑุฉ
# ุงู„ู‚ุงุฏู…ุฉุŒ ูˆู‡ุฐุง ุจุงู„ุถุจุท ู…ุง ูŠูู†ุชุฌ ุดุงุดุฉ "Restore Session" ู„ุงุญู‚ุงู‹.
browser_proc = sess.get("browser_proc")
if browser_proc:
try:
browser_proc.kill()
browser_proc.wait(timeout=3)
except Exception:
pass
sess["browser_proc"] = None
# ู‚ุชู„ Xvfb
_kill_proc(sess.get("xvfb_proc"))
sess["xvfb_proc"] = None
# ุชู†ุธูŠู ุดุงู…ู„ ู„ู‡ุฐุง ุงู„ู€ display
_kill_display_processes(display)
# โ”€โ”€ ุชู†ุธูŠู ุดุงู…ู„ ู„ุจูŠุงู†ุงุช ุฌู„ุณุฉ Firefox ุงู„ูุงุณุฏุฉ (ุงู„ุณุจุจ ุงู„ุฌุฐุฑูŠ ู„ุดุงุดุฉ
# "Restore Session") โ€” ู„ูŠุณ ูู‚ุท lock files ุจู„ sessionstore ูƒุงู…ู„ุงู‹ุŒ
# ููŠ ูƒู„ ู…ู† ุงู„ุจุฑูˆูุงูŠู„ ุงู„ุงูุชุฑุงุถูŠ ูˆุฃูŠ ุจุฑูˆูุงูŠู„ ู…ุฎุตุต ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู… โ”€โ”€
try:
subprocess.run(
"rm -f ~/.mozilla/firefox/*/lock ~/.mozilla/firefox/*/.parentlock "
"~/.mozilla/firefox/*/sessionstore.jsonlz4 "
"~/.mozilla/firefox/*/sessionstore-backups/*.jsonlz4 "
"~/.mozilla/firefox/*/sessionCheckpoints.json 2>/dev/null",
shell=True, timeout=5, capture_output=True
)
except Exception:
pass
safe_id = re.sub(r"[^a-zA-Z0-9_.-]", "_", user_id) or "anon"
custom_profile = os.path.expanduser(f"~/.zai_ff_profiles/{safe_id}")
_wipe_firefox_session_data(custom_profile)
# ุชุดุบูŠู„ Xvfb ุฌุฏูŠุฏ (ุฎุงุฑุฌ ุงู„ู€ lock ู„ุฃู† start_xvfb ูŠุณุชุบุฑู‚ ูˆู‚ุชุงู‹)
new_proc = await asyncio.to_thread(_start_xvfb, display)
with _display_lock:
if user_id in _user_displays:
_user_displays[user_id]["xvfb_proc"] = new_proc
_user_displays[user_id]["last_bg_shot_ts"] = 0.0
_user_displays[user_id]["last_bg_hash"] = ""
print(f"[reset] โœ… Computer reset done for '{user_id}' on {display}")
return _user_displays.get(user_id, {})
async def ensure_xvfb_for_session(sess: dict):
"""ูŠุชุฃูƒุฏ ุฃู† Xvfb ูŠุนู…ู„ ู„ู‡ุฐุง ุงู„ู€ session โ€” ูŠูุดุบู‘ู„ู‡ ุฅุฐุง ู„ู… ูŠูƒู† ูƒุฐู„ูƒ."""
display = sess["display"]
# ุชุญู‚ู‚ ุฅุฐุง ูƒุงู† ูŠุนู…ู„ ุจุงู„ูุนู„
try:
r = subprocess.run(
["xdpyinfo", "-display", display],
capture_output=True, timeout=3
)
if r.returncode == 0:
return # ูŠุนู…ู„
except Exception:
pass
# ุดุบู‘ู„ู‡
proc = await asyncio.to_thread(_start_xvfb, display)
with _display_lock:
if sess["user_id"] in _user_displays:
_user_displays[sess["user_id"]]["xvfb_proc"] = proc
async def destroy_user_ws(user_id: str, ws: WebSocket):
"""
ูŠูุฒูŠู„ ุงู„ู€ WebSocket ู…ู† ุงู„ู€ session ุนู†ุฏ ุงู†ู‚ุทุงุน ุงู„ุงุชุตุงู„.
ู„ุง ูŠุญุฐู ุงู„ู€ session ู†ูุณู‡ุง โ€” ุงู„ู…ุณุชุฎุฏู… ูŠุญุชูุธ ุจูƒู…ุจูŠูˆุชุฑู‡.
"""
with _display_lock:
sess = _user_displays.get(user_id)
if sess and sess.get("active_ws") is ws:
sess["active_ws"] = None
print(f"[session] ๐Ÿ“ด User '{user_id}' disconnected (session kept)")
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Screenshot Engine (per-display) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def _is_black_screen(img) -> bool:
try:
small = img.resize((100, 100))
pixels = list(small.getdata())
avg = sum(sum(p[:3]) for p in pixels) / (len(pixels) * 3 * 255)
return avg < 0.04
except Exception:
return False
def _load_capture_image(path: str):
from PIL import Image
if not path or not os.path.exists(path):
return None, 0, 0
if os.path.getsize(path) < 1024:
return None, 0, 0
try:
img = Image.open(path).convert("RGB")
w, h = img.size
if w < 100 or h < 100:
return None, 0, 0
return img, w, h
except Exception as ex:
print(f"[cap] load error for {path}: {ex}")
return None, 0, 0
def _capture_via_import_pipe(display: str):
"""
ุงู„ุทุฑูŠู‚ุฉ ุงู„ุฃุณุงุณูŠุฉ ูˆุงู„ูˆุญูŠุฏุฉ: `import -window root` ู…ุน ุงู„ุฅุฎุฑุงุฌ ู…ุจุงุดุฑุฉ
ุนุจุฑ stdout (pipe) โ€” ุจุฏูˆู† ุฃูŠ ู…ู„ู ู…ุคู‚ุช ุนู„ู‰ ุงู„ู‚ุฑุต. ุฃุณุฑุน ุจุดูƒู„ ู…ู„ุญูˆุธ ู…ู†
ุงู„ูƒุชุงุจุฉ ู„ู…ู„ู ุซู… ุฅุนุงุฏุฉ ู‚ุฑุงุกุชู‡ุŒ ุฎุตูˆุตุงู‹ ุนู„ู‰ ุชุฎุฒูŠู† ุดุจูƒูŠ ุจุทูŠุก.
"""
from PIL import Image
env = {**os.environ, "DISPLAY": display}
try:
r = subprocess.run(
["import", "-window", "root", "-silent", "png:-"],
env=env, timeout=6, capture_output=True
)
if r.returncode != 0 or not r.stdout or len(r.stdout) < 500:
return None, 0, 0
img = Image.open(io.BytesIO(r.stdout)).convert("RGB")
w, h = img.size
if w < 100 or h < 100:
return None, 0, 0
return img, w, h
except FileNotFoundError:
print(f"[cap:{display}] import (ImageMagick) not installed")
return None, 0, 0
except Exception as e:
print(f"[cap:{display}] import-pipe: {e}")
return None, 0, 0
def _capture_via_xlib_direct(display: str):
"""
ุฎุท ุงู„ุฏูุงุน ุงู„ุซุงู†ูŠ ูˆุงู„ุฃุฎูŠุฑ: ู‚ุฑุงุกุฉ X11 framebuffer ู…ุจุงุดุฑุฉ ุนุจุฑ
python-xlibุŒ ุจุฏูˆู† subprocess ุฅุทู„ุงู‚ุงู‹ (ุฃุณุฑุน ู…ู† ุฃูŠ ุฃุฏุงุฉ ุฎุงุฑุฌูŠุฉุŒ
ูˆูŠุนู…ู„ ุญุชู‰ ู„ูˆ ImageMagick ุบูŠุฑ ู…ุซุจู‘ุช ุนู„ู‰ ุงู„ุณูŠุฑูุฑ).
"""
from PIL import Image
try:
from Xlib import display as Xdisp, X
xd = Xdisp.Display(display)
root = xd.screen().root
geom = root.get_geometry()
w, h = geom.width, geom.height
raw = root.get_image(0, 0, w, h, X.ZPixmap, 0xFFFFFFFF)
img = Image.frombuffer("RGB", (w, h), raw.data, "raw", "BGRX", 0, 1)
xd.close()
return img, w, h
except Exception as e:
print(f"[cap:{display}] xlib-direct: {e}")
return None, 0, 0
def _capture_raw(display: str) -> tuple:
"""
ูŠู„ุชู‚ุท ุงู„ุดุงุดุฉ ุจุฃุณุฑุน ุทุฑูŠู‚ุฉ ู…ูˆุซูˆู‚ุฉ: import-pipe ุฃูˆู„ุงู‹ (ุณุฑูŠุนุฉ ุฌุฏุงู‹)ุŒ
ูˆุนู†ุฏ ูุดู„ู‡ุง ูู‚ุท xlib-direct ูƒุจุฏูŠู„ ูˆุงุญุฏ. ุจุญุฏ ุฃู‚ุตู‰ ู…ุญุงูˆู„ุชูŠู† ุณุฑูŠุนุชูŠู†
ู„ูƒู„ ุทุฑูŠู‚ุฉ (ูˆู„ูŠุณ 9 ู…ุญุงูˆู„ุงุช ูƒุงู„ุณุงุจู‚) โ€” ู„ุฃู† ูƒู„ุง ุงู„ุทุฑูŠู‚ุชูŠู† ุฅู…ุง ุชู†ุฌุญุงู†
ููˆุฑุงู‹ ุฃูˆ ุชูุดู„ุงู† ู„ุณุจุจ ุฏุงุฆู…ุŒ ูˆุฅุนุงุฏุฉ ุงู„ู…ุญุงูˆู„ุฉ ุฃูƒุซุฑ ู…ู† ู…ุฑุชูŠู† ู„ุง ุชูุบูŠู‘ุฑ
ุงู„ู†ุชูŠุฌุฉุŒ ูู‚ุท ุชูุจุทุฆ ุงู„ุงุณุชุฌุงุจุฉ.
"""
for method_name, method in (
("import-pipe", _capture_via_import_pipe),
("xlib-direct", _capture_via_xlib_direct),
):
for attempt in range(2):
try:
img, w, h = method(display)
if img and not _is_black_screen(img):
if attempt > 0:
print(f"[cap:{display}] โœ… {method_name} succeeded on retry")
return img, w, h
except Exception as e:
print(f"[cap:{display}] {method_name}: {e}")
if attempt == 0:
time.sleep(0.3)
print(f"[cap:{display}] โš ๏ธ All methods failed โ€” placeholder")
from PIL import Image as PILImg, ImageDraw
sw, sh = _get_screen_size(display)
img = PILImg.new("RGB", (sw or 1280, sh or 800), (15, 20, 40))
draw = ImageDraw.Draw(img)
draw.rectangle([(0, 0), (sw, 55)], fill=(30, 40, 80))
draw.text((10, 10), f"โš ๏ธ Screenshot failed โ€” DISPLAY={display}", fill=(255, 120, 80))
draw.text((10, 32), "Methods tried: import-pipe, xlib-direct", fill=(130, 130, 150))
return img, sw or 1280, sh or 800
def _get_screen_size(display: str) -> tuple:
try:
r = subprocess.run(
["xdotool", "getdisplaygeometry"],
env={**os.environ, "DISPLAY": display},
capture_output=True, text=True, timeout=5
)
parts = r.stdout.strip().split()
return int(parts[0]), int(parts[1])
except Exception:
return 1280, 800
def _get_mouse_pos(display: str) -> tuple:
try:
r = subprocess.run(
["xdotool", "getmouselocation"],
env={**os.environ, "DISPLAY": display},
capture_output=True, text=True, timeout=5
)
mx = int(re.search(r"x:(\d+)", r.stdout).group(1))
my = int(re.search(r"y:(\d+)", r.stdout).group(1))
return mx, my
except Exception:
return 0, 0
def _draw_mouse_marker(draw, msx, msy, color=(255, 50, 50, 240)):
"""ูŠุฑุณู… ุนู„ุงู…ุฉ ุงู„ู…ุงูˆุณ (ุฏุงุฆุฑุฉ + ุฎุทูˆุท ุชู‚ุงุทุน) ููŠ ู†ู‚ุทุฉ ู…ุญุฏุฏุฉ ุนู„ู‰ ุตูˆุฑุฉ."""
r = 11
line_color = (color[0], color[1], color[2], 200)
draw.ellipse([(msx-r, msy-r), (msx+r, msy+r)], outline=color, width=2)
draw.line([(msx-18, msy), (msx+18, msy)], fill=line_color, width=1)
draw.line([(msx, msy-18), (msx, msy+18)], fill=line_color, width=1)
def _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
grid_step: int, line_color: tuple, label_every: int,
major_only_labels: bool = True):
"""
ูŠุฑุณู… ู†ุณุฎุฉ ุดุจูƒุฉ ุฅุญุฏุงุซูŠุงุช ูˆุงุญุฏุฉ ููˆู‚ ู†ุณุฎุฉ ู…ู† ุงู„ุตูˆุฑุฉ ุงู„ุฃุณุงุณูŠุฉ.
grid_step โ†’ ุงู„ู…ุณุงูุฉ ุจุงู„ุจูƒุณู„ ุงู„ุญู‚ูŠู‚ูŠ ุจูŠู† ูƒู„ ุฎุท ุดุจูƒุฉ.
line_color โ†’ ู„ูˆู† ุงู„ุฎุทูˆุท ูˆุงู„ุฃุฑู‚ุงู… (RGBA).
label_every โ†’ ูƒู„ ูƒู… ุจูƒุณู„ ุชููƒุชุจ ููŠู‡ ุชุณู…ูŠุฉ ุฅุญุฏุงุซูŠ (x,y) ูƒุงู…ู„ุฉ ุนู†ุฏ ุงู„ุชู‚ุงุทุนุงุช.
major_only_labels โ†’ ุฅุฐุง TrueุŒ ุงู„ุฃุฑู‚ุงู… ุนู„ู‰ ุงู„ุญูˆุงู ุชุธู‡ุฑ ูู‚ุท ุนู†ุฏ ุฎุทูˆุท "ุฑุฆูŠุณูŠุฉ".
"""
from PIL import ImageDraw
img = base_img.copy()
draw = ImageDraw.Draw(img, "RGBA")
step_x = max(1, int(grid_step * sw / ow))
step_y = max(1, int(grid_step * sh / oh))
minor_color = (line_color[0], line_color[1], line_color[2], 35)
major_color = (line_color[0], line_color[1], line_color[2], 110)
text_color = line_color
x_sc, x_r = step_x, grid_step
x_majors = []
while x_sc < sw:
is_major = (x_r % label_every == 0)
draw.line([(x_sc, 0), (x_sc, sh)], fill=(major_color if is_major else minor_color), width=1)
if is_major or not major_only_labels:
draw.rectangle([(x_sc+1, 2), (x_sc+34, 15)], fill=(0, 0, 0, 175))
draw.text((x_sc+2, 3), str(x_r), fill=text_color)
if is_major:
x_majors.append((x_sc, x_r))
x_sc += step_x; x_r += grid_step
y_sc, y_r = step_y, grid_step
y_majors = []
while y_sc < sh:
is_major = (y_r % label_every == 0)
draw.line([(0, y_sc), (sw, y_sc)], fill=(major_color if is_major else minor_color), width=1)
if is_major or not major_only_labels:
draw.rectangle([(2, y_sc+1), (38, y_sc+14)], fill=(0, 0, 0, 175))
draw.text((3, y_sc+2), str(y_r), fill=text_color)
if is_major:
y_majors.append((y_sc, y_r))
y_sc += step_y; y_r += grid_step
for (xs, xr) in x_majors:
for (ys, yr) in y_majors:
label = f"{xr},{yr}"
tw = 6 * len(label) + 4
draw.rectangle([(xs+2, ys+2), (xs+2+tw, ys+13)], fill=(0, 0, 0, 150))
draw.text((xs+4, ys+2), label, fill=text_color)
msx = int(mx * sw / ow)
msy = int(my * sh / oh)
_draw_mouse_marker(draw, msx, msy, color=(line_color[0], line_color[1], line_color[2], 240))
final = img.convert("RGB")
draw2 = ImageDraw.Draw(final)
draw2.rectangle([(0, 0), (sw, 20)], fill=(0, 0, 0))
draw2.text((4, 3), f"SCREEN {ow}x{oh} | MOUSE:({mx},{my}) | GRID={grid_step}px | DSP:{display}", fill=(0, 220, 160))
draw2.rectangle([(0, sh-20), (sw, sh)], fill=(0, 0, 0))
draw2.text((4, sh-17), "CLICKCOORDS = numbers at every intersection (real screen pixels)", fill=(180, 180, 70))
return final
def capture_with_grid(display: str, scale: float = 0.85, quality: int = 72,
force_mx: int | None = None,
force_my: int | None = None,
grid_step: int = 50) -> dict:
"""
ูŠู„ุชู‚ุท ุงู„ุดุงุดุฉ ู…ู† display ู…ุญุฏุฏ ูˆูŠูู†ุชุฌ 3 ู†ุณุฎ ู…ู† ู†ูุณ ุงู„ู„ู‚ุทุฉ ุจุงู„ุถุจุท (ู†ูุณ ุงู„ู„ุญุธุฉ):
- "data" โ†’ ุงู„ู†ุณุฎุฉ ุงู„ุนุงุฏูŠุฉ (ู†ุธูŠูุฉ ุชู…ุงู…ุงู‹) + ุนู„ุงู…ุฉ ุงู„ู…ุงูˆุณ ูู‚ุทุŒ ุจุฏูˆู† ุฃูŠ Grid.
- "data_grid" โ†’ ุดุจูƒุฉ ุฅุญุฏุงุซูŠุงุช ุนุงุฏูŠุฉ (ูƒู„ 50px)ุŒ ุฃุฎุถุฑ/ุณู…ุงูˆูŠ โ€” ู„ู„ุฐูƒุงุก ุงู„ุงุตุทู†ุงุนูŠ.
- "data_grid2" โ†’ ุดุจูƒุฉ ุฅุญุฏุงุซูŠุงุช ุฃุฏู‚ (ูƒู„ 20px)ุŒ ุฃุญู…ุฑ โ€” ู„ู„ู†ู‚ุฑุงุช ุงู„ุฏู‚ูŠู‚ุฉ.
โ€”โ€” ุชุจุณูŠุท v13: ู†ุณุฎุชุงู† ูู‚ุท (ุจุฏู„ 3) ู„ุชู‚ู„ูŠู„ ุนุฏุฏ ุนู…ู„ูŠุงุช resize/encode ู„ูƒู„
ู„ู‚ุทุฉ ุจู†ุณุจุฉ ู‚ุฑุงุจุฉ ุณ4ุฅู„ู‰ ุณ2ุŒ ูˆุฏู‚ุฉ ุงู„ู…ู‚ุงุณ ุงู„ูˆุงุญุฏ 1280x800 ุงู„ุฌุฏูŠุฏุฉ ู„ู…
ุชุนุฏ ุชุญุชุงุฌ ู„ุซู„ุงุซ ุชุฏุฑุฌุงุช ุฏู‚ุฉ ู…ู†ูุตู„ุฉ ู„ุชุญุฏูŠุฏ ุงู„ุฅุญุฏุงุซูŠุงุช.
"""
from PIL import Image, ImageDraw
img, ow, oh = _capture_raw(display)
if img is None:
return {"data": "", "data_grid": "", "data_grid2": "", "data_grid3": "",
"width": 1280, "height": 800, "mouse_x": 0, "mouse_y": 0}
mx, my = (force_mx, force_my) if force_mx is not None else _get_mouse_pos(display)
sw = int(ow * scale)
sh = int(oh * scale)
base_img = img.resize((sw, sh), Image.LANCZOS)
msx = int(mx * sw / ow)
msy = int(my * sh / oh)
clean_img = base_img.copy()
draw_clean = ImageDraw.Draw(clean_img, "RGBA")
_draw_mouse_marker(draw_clean, msx, msy)
clean_final = clean_img.convert("RGB")
buf_clean = io.BytesIO()
clean_final.save(buf_clean, format="JPEG", quality=quality, optimize=True)
data_clean = base64.b64encode(buf_clean.getvalue()).decode()
grid1 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
grid_step=grid_step, line_color=(0, 255, 180, 235),
label_every=100)
buf1 = io.BytesIO(); grid1.save(buf1, format="JPEG", quality=quality, optimize=True)
data_grid1 = base64.b64encode(buf1.getvalue()).decode()
grid2 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
grid_step=20, line_color=(255, 60, 60, 235),
label_every=60, major_only_labels=True)
buf2 = io.BytesIO(); grid2.save(buf2, format="JPEG", quality=max(quality, 78), optimize=True)
data_grid2 = base64.b64encode(buf2.getvalue()).decode()
return {
"data": data_clean,
"data_grid": data_grid1,
"data_grid2": data_grid2,
"data_grid3": data_grid2, # ุชูˆุงูู‚ูŠุฉ ุฑุฌุนูŠุฉ: ู†ูุณ ู‚ูŠู…ุฉ data_grid2 ู„ุฃูŠ ูƒูˆุฏ ู‚ุฏูŠู… ูŠู‚ุฑุฃ data_grid3
"width": ow, "height": oh, "mouse_x": mx, "mouse_y": my,
}
def _frame_hash(data: str) -> str:
"""
ุฅุตู„ุงุญ: ูƒุงู† ูŠูุญุณุจ ุนู„ู‰ ุฃูˆู„ 2000 ุญุฑู ูู‚ุท ู…ู† ุจูŠุงู†ุงุช ุงู„ุตูˆุฑุฉุŒ ู…ู…ุง ูŠุนู†ูŠ ุฃูŠ ุชุบูŠูŠุฑ
ุจุตุฑูŠ ูŠู‚ุน ุฎุงุฑุฌ ุงู„ุฌุฒุก ุงู„ู…ู…ุซูŽู‘ู„ ุถู…ู† ุชู„ูƒ ุงู„ุฃุญุฑู ุงู„ุฃูˆู„ู‰ (ู…ุซู„ ุชุญุฏูŠุฏ ู†ุต ุดุฑูŠุท ุงู„ุนู†ูˆุงู†
ุจุนุฏ Ctrl+LุŒ ุฃูˆ ุฃูŠ ุชุบูŠูŠุฑ ุทููŠู/ููŠ ู…ู†ุทู‚ุฉ ู„ุง ุชุชูˆุงูู‚ ู…ุน ุจุฏุงูŠุฉ ุชุฑู…ูŠุฒ base64) ู„ุง
ูŠููƒุชุดู ุฃุจุฏุงู‹ุŒ ูุชูุญุฌุจ ุงู„ุตูˆุฑุฉ ุงู„ุฌุฏูŠุฏุฉ ุนุจุฑ delta suppression ุฑุบู… ุงุฎุชู„ุงูู‡ุง ูุนู„ูŠุงู‹
ุนู† ุงู„ุณุงุจู‚ุฉุŒ ูˆู„ุง ุชุตู„ ุฃูŠ ู„ู‚ุทุฉ ู…ุญุฏูŽู‘ุซุฉ ู„ู„ุนู…ูŠู„. ุงู„ุญู„: ุญุณุงุจ MD5 ุนู„ู‰ ูƒุงู…ู„ ุงู„ุจูŠุงู†ุงุช
ุจุฏู„ ุชู‚ุทูŠุนู‡ุง โ€” ู‡ุฐุง ุณุฑูŠุน ุฌุฏุงู‹ (ุฃู‚ู„ ู…ู† ู…ู„ู„ูŠ ุซุงู†ูŠุฉ ุญุชู‰ ู„ุตูˆุฑ ูƒุจูŠุฑุฉ) ูู„ุง ุชูƒู„ูุฉ ุฃุฏุงุก
ุญู‚ูŠู‚ูŠุฉุŒ ูˆูŠุถู…ู† ุงูƒุชุดุงู ุฃูŠ ุชุบูŠูŠุฑ ุจุตุฑูŠ ุญู‚ูŠู‚ูŠ ููŠ ุฃูŠ ู…ูƒุงู† ู…ู† ุงู„ุตูˆุฑุฉ.
"""
return hashlib.md5(data.encode()).hexdigest()
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ SafeSearch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
_BING_RE = re.compile(r"https?://(?:www\.)?bing\.com[^\s'\"]*")
_DDG_RE = re.compile(r"https?://(?:www\.)?duckduckgo\.com[^\s'\"]*")
_GOOG_RE = re.compile(r"https?://(?:www\.)?google\.[a-z.]+[^\s'\"]*")
def _safe_search(text: str) -> str:
def _bing(m):
u = m.group(0)
return re.sub(r"adlt=\w+", "adlt=strict", u) if "adlt=" in u else u + ("&" if "?" in u else "?") + "adlt=strict"
def _ddg(m):
u = m.group(0)
return re.sub(r"kp=\d", "kp=1", u) if "kp=" in u else u + ("&" if "?" in u else "?") + "kp=1"
def _goog(m):
u = m.group(0)
return re.sub(r"safe=\w+", "safe=strict", u) if "safe=" in u else u + ("&" if "?" in u else "?") + "safe=strict"
return _GOOG_RE.sub(_goog, _DDG_RE.sub(_ddg, _BING_RE.sub(_bing, text)))
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Terminal Execution (per-display) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
_PKILL_RE = re.compile(r"\b(pkill|killall)\s+(-9\s+)?-f\s+(['\"]?)([a-zA-Z0-9_./-]+)\3")
def _sanitize_pkill(cmd: str) -> str:
def _fix(m):
tool, d9, _q, p = m.group(1), m.group(2) or "", m.group(3), m.group(4)
sp = f"[{p[0]}]{p[1:]}" if len(p) > 1 else p
return f"{tool} {d9}-f '{sp}'"
return _PKILL_RE.sub(_fix, cmd)
async def run_cmd(cmd: str, display: str, timeout: int = 60) -> dict:
"""ูŠู†ูู‘ุฐ ุฃู…ุฑ bash ู…ุน DISPLAY ุฎุงุต ุจุงู„ู…ุณุชุฎุฏู…."""
cmd = _sanitize_pkill(_safe_search(cmd))
async with _terminal_sem:
env = {**os.environ, "DISPLAY": display,
"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
def _exec():
try:
r = subprocess.run(
cmd, shell=True, capture_output=True,
text=True, timeout=timeout, env=env, executable="/bin/bash"
)
return {"stdout": r.stdout[-15000:], "stderr": r.stderr[-3000:], "returncode": r.returncode}
except subprocess.TimeoutExpired:
return {"stdout": "", "stderr": f"โฑ๏ธ Timeout {timeout}s", "returncode": -1}
except Exception as e:
return {"stdout": "", "stderr": str(e), "returncode": -1}
return await asyncio.to_thread(_exec)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ xdotool helpers (per-display) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
async def xdo(args: list, display: str, timeout: int = 10) -> dict:
env = {**os.environ, "DISPLAY": display}
def _run():
r = subprocess.run(
["xdotool"] + args, env=env,
timeout=timeout, capture_output=True, text=True
)
return {"rc": r.returncode, "out": r.stdout, "err": r.stderr}
return await asyncio.to_thread(_run)
async def type_smart(text: str, display: str) -> dict:
"""ูƒุชุงุจุฉ ู†ุต ุฐูƒูŠุฉ ุจู€ display ู…ุญุฏุฏ."""
has_arabic = bool(re.search(r'[\u0600-\u06FF]', text))
env = {**os.environ, "DISPLAY": display}
if has_arabic:
def _paste():
p = subprocess.Popen(
["xclip", "-selection", "clipboard"],
stdin=subprocess.PIPE, env=env
)
p.communicate(text.encode("utf-8"))
await asyncio.to_thread(_paste)
await asyncio.sleep(0.15)
await xdo(["key", "--clearmodifiers", "ctrl+v"], display)
return {"method": "clipboard+paste"}
else:
r = await xdo(["type", "--clearmodifiers", "--delay", "25", text], display)
return {"method": "xdotool", "rc": r["rc"]}
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Search Sources โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def _search_sources(query: str) -> list:
q = urllib.parse.quote_plus(query)
return [
{"name": "DuckDuckGo Instant",
"cmd": f"curl -s --max-time 15 'https://api.duckduckgo.com/?q={q}&format=json&no_html=1&skip_disambig=1' | python3 -c \"import sys,json;d=json.load(sys.stdin);a=d.get('AbstractText','');r=d.get('RelatedTopics',[]);print('ANS:',a or 'none');[print('-',x.get('Text','')[:200]) for x in r[:6] if isinstance(x,dict)]\""},
{"name": "Google News RSS",
"cmd": f"curl -sL --max-time 15 'https://news.google.com/rss/search?q={q}&hl=ar&gl=AR&ceid=AR:ar' | python3 -c \"import sys,re;x=sys.stdin.read();t=re.findall(r'<title><!\\[CDATA\\[(.*?)\\]\\]></title>|<title>(.*?)</title>',x);[(print(str(i+1)+'. '+(a or b).strip()[:160])) for i,(a,b) in enumerate(t[1:8]) if (a or b).strip()]\""},
{"name": "Wikipedia EN",
"cmd": f"curl -s --max-time 12 'https://en.wikipedia.org/api/rest_v1/page/summary/{q}' | python3 -c \"import sys,json;d=json.load(sys.stdin);print(d.get('title','')+'\\n'+d.get('extract','')[:1200])\""},
{"name": "DuckDuckGo HTML",
"cmd": f"curl -sL --max-time 15 -H 'User-Agent: Mozilla/5.0' 'https://html.duckduckgo.com/html/?q={q}' | python3 -c \"import sys,re;h=sys.stdin.read();s=re.findall(r'class=.result__snippet[^>]*>(.*?)</a>',h,re.DOTALL);clean=lambda x:re.sub('<[^>]+>','',x).strip();[print(str(i+1)+'. '+clean(x)[:200]) for i,x in enumerate(s[:7])]\""},
{"name": "HackerNews",
"cmd": f"curl -s --max-time 12 'https://hn.algolia.com/api/v1/search?query={q}&hitsPerPage=6&tags=story' | python3 -c \"import sys,json;d=json.load(sys.stdin);[print(str(i+1)+'. '+h.get('title','')+' Pts:'+str(h.get('points',0))) for i,h in enumerate(d.get('hits',[])[:5])]\""},
{"name": "Reddit",
"cmd": f"curl -sL --max-time 12 -H 'Accept: application/json' 'https://www.reddit.com/search.json?q={q}&sort=new&limit=6' | python3 -c \"import sys,json;d=json.load(sys.stdin);[print(str(i+1)+'. '+p['data'].get('title','')[:160]) for i,p in enumerate(d.get('data',{{}}).get('children',[])[:5])]\""},
]
def _result_empty(s: str) -> bool:
if not s or len(s.strip()) < 15:
return True
return "ans: none" in s.lower()
async def run_cmd_smart(cmd: str, display: str, timeout: int = 60) -> dict:
res = await run_cmd(cmd, display, timeout)
if res["returncode"] == 0 and not _result_empty(res["stdout"]):
return res
is_search = "curl" in cmd and any(
x in cmd for x in ["search", "duckduck", "bing", "google", "wikipedia", "reddit"]
)
if not is_search:
return res
m = re.search(r"[?&]q=([^&'\"\s]+)", cmd)
query = urllib.parse.unquote(m.group(1).replace("+", " ")).strip() if m else ""
if len(query) < 3:
return res
sources = _search_sources(query)
results = []
for src in sources:
r2 = await run_cmd(src["cmd"], display, 20)
if not _result_empty(r2["stdout"]):
results.append(f"[{src['name']}]\n{r2['stdout']}")
if len(results) >= 2:
break
if results:
return {"stdout": "\n\n".join(results)[:15000], "stderr": "", "returncode": 0}
return res
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ FastAPI App โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
app = FastAPI(title="Z-Computer-Mode v11 โ€” Per-User Isolation")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
allow_methods=["*"], allow_headers=["*"])
@app.get("/", response_class=HTMLResponse)
async def root():
n = len(_user_displays)
users_info = ""
with _display_lock:
for uid, sess in _user_displays.items():
ws_status = "๐ŸŸข ู…ุชุตู„" if sess.get("active_ws") else "โšซ ุบูŠุฑ ู…ุชุตู„"
users_info += f"<tr><td>{uid[:30]}...</td><td>{sess['display']}</td><td>{ws_status}</td></tr>"
return f"""<!DOCTYPE html><html><head><meta charset="utf-8">
<title>Z Computer Mode v11</title>
<style>body{{background:#080808;color:#00d4aa;font-family:monospace;padding:40px}}
h1{{color:#4a90d9}}.ok{{color:#00ff88}}.info{{color:#888;font-size:13px}}
table{{border-collapse:collapse;width:100%}}td,th{{border:1px solid #333;padding:6px 10px;font-size:13px}}
th{{background:#1a1a2e;color:#4a90d9}}</style>
</head><body>
<h1>๐Ÿ–ฅ๏ธ THE Z AI โ€” Computer Mode Server v11</h1>
<p class="ok">โœ… Server RUNNING โ€” Per-User Isolated Displays</p>
<p>๐ŸŒ Browser: <b>{BROWSER}</b></p>
<p>๐Ÿ‘ฅ Active users: <b>{n}</b></p>
<p>๐Ÿ“‹ Display range: <b>:{DISPLAY_BASE} โ†’ :{DISPLAY_MAX}</b></p>
<hr style="border-color:#333">
<h3>๐Ÿ‘ค User Sessions</h3>
<table><tr><th>User ID</th><th>Display</th><th>Status</th></tr>
{users_info or '<tr><td colspan="3" style="text-align:center;color:#666">No active sessions</td></tr>'}
</table>
<hr style="border-color:#333">
<p class="info">Endpoints: /health ยท /ws?user_id=email (WebSocket)</p>
</body></html>"""
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Action Handler (per-session) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
async def handle_action(ws: WebSocket, msg: dict, sess: dict):
action = msg.get("action", "")
data = msg.get("data", {})
display = sess["display"]
async def send(obj):
try:
await ws.send_text(json.dumps(obj, ensure_ascii=False))
except Exception:
pass
# โ”€โ”€ ุฅุตู„ุงุญ ุฌุฐุฑูŠ: ู‚ูู„ ุฎุงุต ุจูƒู„ session ู„ู…ู†ุน ุชุฑุงูƒู…/ุชุถุงุฑุจ ุนู…ู„ูŠุงุช capture ุงู„ู…ุชุฒุงู…ู†ุฉ โ”€โ”€
# ุงู„ู…ุดูƒู„ุฉ ุงู„ุฃุตู„ูŠุฉ: action == "screenshot" ูƒุงู† ูŠูู†ููŽู‘ุฐ ุจู€ await ู…ุจุงุดุฑ ุถู…ู† ุญู„ู‚ุฉ
# while True ุงู„ุฑุฆูŠุณูŠุฉ ููŠ websocket_endpointุŒ ูุฅุฐุง ุชุฃุฎุฑุช capture_with_grid (ู‚ุฏ ุชุตู„
# ู„ุฏู‚ูŠู‚ุฉ ูˆู†ุตู ููŠ ุฃุณูˆุฃ ุณูŠู†ุงุฑูŠูˆ ูุดู„/ุชุฃุฎุฑ scrot+import+ffmpeg)ุŒ ุชุจู‚ู‰ ุงู„ุญู„ู‚ุฉ ูƒู„ู‡ุง
# ู…ุญุฌูˆุจุฉ ูˆู„ุง ุชุณุชู‚ุจู„ ุฃูŠ ุฑุณุงู„ุฉ ุนู…ูŠู„ ุฌุฏูŠุฏุฉ (ู†ู‚ุฑุฉุŒ ุทู„ุจ screenshot ุขุฎุฑุŒ ุฅู„ุฎ) ุญุชู‰ ุชู†ุชู‡ูŠ.
# ุงู„ุญู„: ูƒู„ ุทู„ุจ screenshot ูŠูุดุบูŽู‘ู„ ููˆุฑุงู‹ ุนุจุฑ create_task (ู„ุง ูŠุญุฌุจ ุงู„ุญู„ู‚ุฉ ุฃุจุฏุงู‹).
#
# โ”€โ”€ ุฅุตู„ุงุญ ุซุงู†ู (ู…ู‡ู… ุฌุฏุงู‹): ุทู„ุจ screenshot ุงู„ุตุฑูŠุญ ู…ู† ุงู„ุนู…ูŠู„ ู„ู‡ ุฃูˆู„ูˆูŠุฉ ู…ุทู„ู‚ุฉ โ”€โ”€
# ุงู„ู…ุดูƒู„ุฉ ุงู„ู…ูƒุชุดูุฉ ู„ุงุญู‚ุงู‹: ู„ูˆ ุงุณุชูุฎุฏู… ู‚ูู„ ูˆุงุญุฏ ู…ุดุชุฑูƒ ุจูŠู† shot_bg (ุชู„ู‚ุงุฆูŠุฉุŒ ุจุนุฏ
# ูƒู„ ูุนู„ ูƒู†ู‚ุฑุฉ/ูุชุญ ุชุจูˆูŠุจ) ูˆ shot_explicit (ุตุฑูŠุญุฉุŒ ูŠุทู„ุจู‡ุง ุงู„ุนู…ูŠู„ ู…ุจุงุดุฑุฉ ุจุนุฏ ูƒู„
# ุฎุทูˆุฉ ู„ุชุญุฏูŠุซ ุงู„ุตูˆุฑุฉ ุงู„ู…ุนุฑูˆุถุฉ)ุŒ ูุฅู† shot_explicit ู‚ุฏ ุชู†ุชุธุฑ ุฎู„ู shot_bg ุทูˆูŠู„ุงู‹
# ุฅุฐุง ูƒุงู†ุช ุงู„ุฃุฎูŠุฑุฉ ู‚ุฏ ุจุฏุฃุช ูุนู„ุงู‹ ูˆุนู„ู‚ุช ุฏุงุฎู„ capture_with_grid (ู…ุญุงูˆู„ุงุช ูุงุดู„ุฉ
# ู…ุชุชุงู„ูŠุฉ). ู‡ุฐุง ูŠุฌุนู„ ุงู„ุนู…ูŠู„ ูŠุฑู‰ "ู„ู… ุชุตู„ ู„ู‚ุทุฉ ุดุงุดุฉ" ุจุดูƒู„ ู…ุชูƒุฑุฑ ุญุชู‰ ุจุนุฏ ู†ุฌุงุญ
# ุงู„ุฎุทูˆุฉ ุงู„ูุนู„ูŠุฉ (ู…ุซู„ open_tab)ุŒ ู„ุฃู† ุทู„ุจู‡ ุงู„ุนุงุฌู„ ูƒุงู† ูŠุตุทู ุฎู„ู ุนู…ู„ูŠุฉ ุชู„ู‚ุงุฆูŠุฉ ุจุทูŠุฆุฉ.
# ุงู„ุญู„: shot_explicit (ุงู„ุตุฑูŠุญุฉ ูู‚ุท) ุชุญุงูˆู„ ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ู‚ูู„ ู„ูุชุฑุฉ ู‚ุตูŠุฑุฉ ุฌุฏุงู‹
# (1 ุซุงู†ูŠุฉ)ุŒ ูˆุฅู† ู„ู… ุชู†ุฌุญ (ู„ุฃู† shot_bg ุชุณุชุฎุฏู…ู‡)ุŒ ุชูู†ููŽู‘ุฐ ุงู„ุชุตูˆูŠุฑ ู…ุจุงุดุฑุฉ ุจุฏูˆู† ู‚ูู„
# ุจุฏู„ ุงู„ุงู†ุชุธุงุฑ โ€” ู„ุฃู† ุงุณุชุฌุงุจุฉ ุงู„ุนู…ูŠู„ ุงู„ููˆุฑูŠุฉ ุฃู‡ู… ู…ู† ุชูุงุฏูŠ ุชุฒุงุญู… CPU ุนุฑุถูŠ ุจุณูŠุทุŒ
# ูˆู‚ุฑุงุกุฉ ุงู„ุดุงุดุฉ (X11) ุนู…ู„ูŠุฉ ู‚ุฑุงุกุฉ ูู‚ุท ู„ุง ุชูุณุจุจ ุฃูŠ ุชู„ู ุจูŠุงู†ุงุช ุนู†ุฏ ุงู„ุชุฒุงุญู….
if "_shot_lock" not in sess:
sess["_shot_lock"] = asyncio.Lock()
_shot_lock = sess["_shot_lock"]
async def _safe_capture(scale, quality, force_mx=None, force_my=None):
"""
ุชูุณุชุฎุฏู… ู…ู† shot_bg (ุงู„ุชู„ู‚ุงุฆูŠุฉ) โ€” ุชู†ุชุธุฑ ุงู„ู‚ูู„ ุซู… ุชู†ุชุธุฑ ุงู„ุชู‚ุงุท ุงู„ุดุงุดุฉ
ุญุชู‰ ูŠู†ุชู‡ูŠ ูุนู„ูŠุงู‹ุŒ ุจุฏูˆู† ุฃูŠ ุณู‚ู ุฒู…ู†ูŠ ูŠู‚ุทุนู‡ุง. ู‡ุฐุง ูŠุถู…ู† ุฃู† ุงู„ุนู…ู„ูŠุฉ ู„ู†
ุชูู‚ุทุน ุฃุจุฏุงู‹ ููŠ ู…ู†ุชุตูู‡ุง ูˆุชูุฑุฌุน ูุฑุงุบุงู‹ุ› ุณุชูƒู…ู„ ุญุชู‰ ุชู†ุฌุญ (ุฃูˆ ุชูุดู„ ูƒู„
ุงู„ู…ุญุงูˆู„ุงุช ุงู„ุฏุงุฎู„ูŠุฉ ููŠ capture_with_grid ูˆุชูุฑุฌุน placeholder ุตุฑูŠุญ).
"""
async with _shot_lock:
return await asyncio.to_thread(
capture_with_grid, display, scale, quality, force_mx, force_my
)
async def _priority_capture(scale, quality, force_mx=None, force_my=None):
"""
ุชูุณุชุฎุฏู… ู…ู† shot_explicit (ุงู„ุทู„ุจ ุงู„ุตุฑูŠุญ ู…ู† ุงู„ุนู…ูŠู„) ูู‚ุท โ€” ุฃูˆู„ูˆูŠุฉ ู‚ุตูˆู‰.
ุชุญุงูˆู„ ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ู‚ูู„ ู„ู…ุฏุฉ ู‚ุตูŠุฑุฉ (1 ุซุงู†ูŠุฉ) ูู‚ุทุ› ุฅู† ู„ู… ุชู†ุฌุญ (ุงู„ู‚ูู„
ู…ุญุฌูˆุฒ ู…ู† shot_bg ุชู„ู‚ุงุฆูŠุฉ ุจุทูŠุฆุฉ)ุŒ ุชูู†ููŽู‘ุฐ ุงู„ุชุตูˆูŠุฑ ููˆุฑุงู‹ ุจุฏูˆู† ู‚ูู„ ุจุฏู„
ุงู„ุงู†ุชุธุงุฑ ุฎู„ู ุนู…ู„ูŠุฉ ุฃุฎุฑู‰ โ€” ุงู„ุนู…ูŠู„ ูŠุฌุจ ุฃู† ูŠุญุตู„ ุนู„ู‰ ุฑุฏ ุณุฑูŠุน ุฏุงุฆู…ุงู‹.
ุจุนุฏ ุงู„ุญุตูˆู„ ุนู„ู‰ ุงู„ู‚ูู„ (ุฃูˆ ุชุฌุงูˆุฒู‡)ุŒ ู„ุง ูŠูˆุฌุฏ ุฃูŠ ุณู‚ู ุฒู…ู†ูŠ ุนู„ู‰ ุนู…ู„ูŠุฉ
ุงู„ุงู„ุชู‚ุงุท ู†ูุณู‡ุง โ€” ุชู†ุชุธุฑ ุญุชู‰ ุชูƒุชู…ู„ ูุนู„ูŠุงู‹ ุจุฏู„ ุฃู† ุชูู‚ุทุน ููŠ ุงู„ู…ู†ุชุตู.
"""
try:
await asyncio.wait_for(_shot_lock.acquire(), timeout=1.0)
try:
return await asyncio.to_thread(
capture_with_grid, display, scale, quality, force_mx, force_my
)
finally:
_shot_lock.release()
except asyncio.TimeoutError:
# ู„ู… ู†ุญุตู„ ุนู„ู‰ ุงู„ู‚ูู„ ุจุณุฑุนุฉ ูƒุงููŠุฉ โ€” ู†ู†ูู‘ุฐ ุงู„ุชุตูˆูŠุฑ ู…ุจุงุดุฑุฉ ุจุฏูˆู† ู‚ูู„
# ุจุฏู„ ุงู„ุงู†ุชุธุงุฑ ุฎู„ู ุนู…ู„ูŠุฉ ุฃุฎุฑู‰ุŒ ู„ูƒู† ุจุฏูˆู† ุฃูŠ ุณู‚ู ุฒู…ู†ูŠ ุนู„ู‰ ุงู„ุงู„ุชู‚ุงุท
return await asyncio.to_thread(
capture_with_grid, display, scale, quality, force_mx, force_my
)
async def shot_bg(label: str = "", delay: float = 0.5,
force_mx: int | None = None, force_my: int | None = None,
extra_shot_delay: float | None = None):
"""
auto_shot ููŠ ุงู„ุฎู„ููŠุฉ โ€” ู…ุน rate limit + delta suppression.
ุฅุฐุง ุชู… ุชู…ุฑูŠุฑ extra_shot_delayุŒ ุชูุฑุณู„ ู„ู‚ุทุฉ ุซุงู†ูŠุฉ ุฅุถุงููŠุฉ ุจุนุฏ ุฐู„ูƒ ุงู„ุชุฃุฎูŠุฑ
ุงู„ุฅุถุงููŠ (ู…ุญุณูˆุจุงู‹ ู…ู† ูˆู‚ุช ุงู†ุชู‡ุงุก ุงู„ู„ู‚ุทุฉ ุงู„ุฃูˆู„ู‰) โ€” ู‡ุฐุง ูŠุบุทูŠ ุญุงู„ุฉ ุงู„ู†ู‚ุฑ ุนู„ู‰
ุฑุงุจุท ูŠู†ู‚ู„ ู„ุตูุญุฉ ุฌุฏูŠุฏุฉ ู‚ุฏ ู„ุง ุชูƒุชู…ู„ ุชุญู…ูŠู„ู‡ุง ุฎู„ุงู„ ุงู„ุชุฃุฎูŠุฑ ุงู„ุฃูˆู„ูŠ ุงู„ู‚ุตูŠุฑ
(ู…ุซู„ุงู‹ ุตูุญุฉ ุจุทูŠุฆุฉ ุนู„ู‰ ุณูŠุฑูุฑ ู…ุญุฏูˆุฏ ุงู„ู…ูˆุงุฑุฏ)ุŒ ุฏูˆู† ุงู„ุญุงุฌุฉ ู„ู…ุนุฑูุฉ ู…ุณุจู‚ุฉ ุจู†ูˆุน
ุงู„ุนู†ุตุฑ ุงู„ู…ู†ู‚ูˆุฑ ุนู„ูŠู‡. ุชุชุฌุงู‡ู„ delta suppression ู„ู‡ุฐู‡ ุงู„ู„ู‚ุทุฉ ุงู„ุซุงู†ูŠุฉ ุชุญุฏูŠุฏุงู‹
ู„ุฃู† ุงู„ู…ุญุชูˆู‰ ู…ุชูˆู‚ุน ุฃู† ูŠูƒูˆู† ู…ุฎุชู„ูุงู‹ (ุตูุญุฉ ุชุญู…ู‘ู„ุช ุฃูƒุซุฑ) ุญุชู‰ ู„ูˆ ุชุดุงุจู‡ ุงู„ู€ hash
ุฌุฒุฆูŠุงู‹ ู…ุน ุฎู„ููŠุฉ ู…ุดุงุจู‡ุฉ.
"""
try:
await asyncio.sleep(delay)
now = time.time()
if now - sess.get("last_bg_shot_ts", 0) < 0.3:
await asyncio.sleep(0.3 - (now - sess["last_bg_shot_ts"]))
result = await _safe_capture(0.65, 72, force_mx, force_my)
sess["last_bg_shot_ts"] = time.time()
if result["data"]:
fh = _frame_hash(result["data"])
if fh != sess.get("last_bg_hash", ""):
sess["last_bg_hash"] = fh
await send({
"type": "screenshot",
"data": result["data"],
"data_grid": result.get("data_grid", ""),
"data_grid2": result.get("data_grid2", ""),
"data_grid3": result.get("data_grid3", ""),
"ts": int(time.time() * 1000),
"auto": True, "label": label,
"screen_width": result["width"],
"screen_height": result["height"],
"mouse_x": result["mouse_x"],
"mouse_y": result["mouse_y"],
"has_grid": True,
})
if extra_shot_delay:
await asyncio.sleep(extra_shot_delay)
result2 = await _safe_capture(0.65, 72, force_mx, force_my)
sess["last_bg_shot_ts"] = time.time()
if result2["data"]:
fh2 = _frame_hash(result2["data"])
sess["last_bg_hash"] = fh2
await send({
"type": "screenshot",
"data": result2["data"],
"data_grid": result2.get("data_grid", ""),
"data_grid2": result2.get("data_grid2", ""),
"data_grid3": result2.get("data_grid3", ""),
"ts": int(time.time() * 1000),
"auto": True, "label": f"{label} (delayed)",
"screen_width": result2["width"],
"screen_height": result2["height"],
"mouse_x": result2["mouse_x"],
"mouse_y": result2["mouse_y"],
"has_grid": True,
})
except Exception as e:
# ู„ู‚ุทุฉ ุฎู„ููŠุฉ ูุงุดู„ุฉ ู„ุง ูŠุฌุจ ุฃู† ุชูˆู‚ู ุงู„ุฌู„ุณุฉ ุฃูˆ ุชุธู‡ุฑ ุจุตู…ุช ููŠ ุงู„ู„ูˆู‚ ูู‚ุท.
print(f"[shot_bg:{display}] โš ๏ธ {e}")
async def shot_explicit(label: str = ""):
"""screenshot ุตุฑูŠุญ โ€” ูŠูุฑุณู„ ุฏุงุฆู…ุงู‹ ุจุฏูˆู† delta suppressionุŒ ูˆู„ู‡ ุฃูˆู„ูˆูŠุฉ ู…ุทู„ู‚ุฉ
ุนู„ู‰ ุฃูŠ ุนู…ู„ูŠุฉ shot_bg ุชู„ู‚ุงุฆูŠุฉ ุฌุงุฑูŠุฉ (ู„ุง ูŠู†ุชุธุฑ ุฎู„ูู‡ุง ุทูˆูŠู„ุงู‹).
โ”€โ”€ ุฅุตู„ุงุญ ุฌุฐุฑูŠ: try/except ุดุงู…ู„ + ุฅุฑุณุงู„ ุฑุณุงู„ุฉ ุฎุทุฃ ุตุฑูŠุญุฉ ู„ู„ุนู…ูŠู„ โ”€โ”€
ุงู„ู…ุดูƒู„ุฉ ุงู„ู‚ุฏูŠู…ุฉ: ู‡ุฐู‡ ุงู„ุฏุงู„ุฉ ุชูุดุบูŽู‘ู„ ุนุจุฑ asyncio.create_task ุจุฏูˆู† ุฃูŠ
ู…ุนุงู„ุฌุฉ ุงุณุชุซู†ุงุก ู…ุญูŠุทุฉ ุจู‡ุง. ุฃูŠ ุฎุทุฃ ุบูŠุฑ ู…ุชูˆู‚ุน ูƒุงู† ูŠูุณู‚ุท ุงู„ู€ task ุจุงู„ูƒุงู…ู„
ุจุตู…ุช ุชุงู… โ€” ู„ุง ุฑุณุงู„ุฉ ุชุตู„ ู„ู„ุนู…ูŠู„ ูˆู„ุง ุญุชู‰ ุณุทุฑ ููŠ ุงู„ู„ูˆู‚ุŒ ููŠุจู‚ู‰ ุงู„ุนู…ูŠู„
ูŠู†ุชุธุฑ frame ู„ู† ูŠุตู„ ุฃุจุฏุงู‹. ุงู„ุขู† ุฃูŠ ูุดู„ ูŠูุณุฌูŽู‘ู„ ูˆูŠูุฑุณูŽู„ ูƒุฑุณุงู„ุฉ "error"
ุตุฑูŠุญุฉุŒ ูˆุงู„ุนู…ูŠู„ (index.html) ูŠุชุนุงู…ู„ ู…ุนู‡ุง ูˆูŠุนูŠุฏ ุงู„ู…ุญุงูˆู„ุฉ ููˆุฑุงู‹."""
try:
result = await _priority_capture(0.65, 75)
if not result or not result["data"]:
print(f"[shot_explicit:{display}] โš ๏ธ empty capture result")
await send({"type": "error", "action": "screenshot", "msg": "capture returned empty"})
return
sess["last_bg_hash"] = _frame_hash(result["data"])
await send({
"type": "screenshot",
"data": result["data"],
"data_grid": result.get("data_grid", ""),
"data_grid2": result.get("data_grid2", ""),
"data_grid3": result.get("data_grid3", ""),
"ts": int(time.time() * 1000),
"auto": False, "label": label,
"screen_width": result["width"],
"screen_height": result["height"],
"mouse_x": result["mouse_x"],
"mouse_y": result["mouse_y"],
"has_grid": True,
})
except Exception as e:
print(f"[shot_explicit:{display}] โŒ EXCEPTION: {e}")
try:
await send({"type": "error", "action": "screenshot", "msg": str(e)})
except Exception:
pass
# โ”€โ”€ reset_computer: ุฅุนุงุฏุฉ ุถุจุท ุงู„ูƒู…ุจูŠูˆุชุฑ ูƒุฃู†ู‡ ุฌุฏูŠุฏ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if action == "reset_computer":
user_id = sess["user_id"]
await send({"type": "ack", "action": "reset_computer", "status": "resetting"})
await reset_user_computer(user_id)
# ุฃุฑุณู„ ู„ู‚ุทุฉ ุดุงุดุฉ ุจุนุฏ ุงู„ุฑูŠุณุชุงุฑุช
await asyncio.sleep(2.0)
await shot_explicit("after reset")
await send({"type": "computer_reset", "msg": "โœ… ุชู… ุฅุนุงุฏุฉ ุถุจุท ุงู„ูƒู…ุจูŠูˆุชุฑ โ€” ุงู„ุดุงุดุฉ ุฌุฏูŠุฏุฉ ุชู…ุงู…ุงู‹"})
return
# โ”€โ”€ screenshot โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ุฅุตู„ุงุญ: create_task ุจุฏู„ await ู…ุจุงุดุฑ โ€” ู„ุง ูŠุญุฌุจ ุญู„ู‚ุฉ ุงุณุชู‚ุจุงู„ ุงู„ุฑุณุงุฆู„ ุฃุจุฏุงู‹ุŒ
# ุญุชู‰ ู„ูˆ ุชุฃุฎุฑุช capture_with_grid ู„ุฃูŠ ุณุจุจ (CPU ุถุนูŠูุŒ X11 ุจุทูŠุกุŒ ุฅู„ุฎ).
# ู‡ุฐุง ูŠุถู…ู† ุฃู† ุฃูŠ ุฅุฌุฑุงุก ู„ุงุญู‚ (ู†ู‚ุฑุฉุŒ ุทุจุงุนุฉุŒ ุทู„ุจ screenshot ุขุฎุฑ) ูŠุตู„ ูˆูŠูุนุงู„ูŽุฌ
# ููˆุฑุงู‹ ุฏูˆู† ุงู†ุชุธุงุฑ ุงูƒุชู…ุงู„ ู‡ุฐู‡ ุงู„ู„ู‚ุทุฉ ุฃูˆู„ุงู‹.
if action == "screenshot":
# โ”€โ”€ ุทุจู‚ุฉ ุญู…ุงูŠุฉ ุฅุถุงููŠุฉ: ุญุชู‰ ู„ูˆ ุญุตู„ ุฎุทุฃ ุบูŠุฑ ู…ุชูˆู‚ุน ุชู…ุงู…ุงู‹ ุฏุงุฎู„
# shot_explicit ุชูู„ุช ู…ู† ุงู„ู€ try/except ุงู„ุฏุงุฎู„ูŠ (ู…ุซู„ CancelledError
# ุฃูˆ ุฎุทุฃ ุนู†ุฏ ุฅู†ุดุงุก ุงู„ู€ task ู†ูุณู‡)ุŒ ู‡ุฐุง ุงู„ุบู„ุงู ุงู„ุฎุงุฑุฌูŠ ูŠุถู…ู† ุชุณุฌูŠู„ู‡
# ููŠ ุงู„ู„ูˆู‚ ุจุฏู„ ุฃู† ูŠุฎุชููŠ ุจุตู…ุช ูƒู€ "unhandled task exception" ููŠ event
# loop ุจุงูŠุซูˆู† (ูˆู‡ุฐุง ูƒุงู† ู„ู‡ ุณู„ูˆูƒ ุงูุชุฑุงุถูŠ ุจุตุงู…ุช ููŠ ุจุนุถ ุฅุนุฏุงุฏุงุช uvicorn).
_task = asyncio.create_task(shot_explicit("explicit screenshot"))
def _on_shot_done(t: asyncio.Task):
exc = t.exception() if not t.cancelled() else None
if exc:
print(f"[screenshot-task:{display}] โŒ unhandled exception: {exc}")
_task.add_done_callback(_on_shot_done)
# โ”€โ”€ terminal โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "terminal":
cmd = data.get("cmd", "")
if not cmd:
await send({"type": "terminal_result", "stdout": "", "stderr": "no cmd", "returncode": -1})
return
res = await run_cmd_smart(cmd, display, int(data.get("timeout", 60)))
await send({
"type": "terminal_result",
"cmd": cmd,
"stdout": res["stdout"],
"stderr": res.get("stderr", ""),
"returncode": res["returncode"],
})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ: ู„ุง ุชูุคุฎุฐ ู„ู‚ุทุฉ ุดุงุดุฉ ุฅู„ุง ุจุทู„ุจ ุตุฑูŠุญ โ”€โ”€
# โ”€โ”€ mouse_move โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "mouse_move":
x, y = int(data.get("x", 0)), int(data.get("y", 0))
await xdo(["mousemove", "--sync", str(x), str(y)], display)
await send({"type": "ack", "action": "mouse_move", "x": x, "y": y})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ mouse_click โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "mouse_click":
x, y = int(data.get("x", 0)), int(data.get("y", 0))
btn = {"left": "1", "middle": "2", "right": "3"}.get(data.get("button", "left"), "1")
double = data.get("double", False)
await xdo(["mousemove", "--sync", str(x), str(y)], display)
await asyncio.sleep(0.07)
if double:
await xdo(["click", "--repeat", "2", "--delay", "100", btn], display)
else:
await xdo(["click", btn], display)
btn_name = {"1": "left", "2": "middle", "3": "right"}.get(btn, "left")
await send({"type": "ack", "action": "mouse_click", "x": x, "y": y, "button": btn_name})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ mouse_drag โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "mouse_drag":
x1, y1 = int(data.get("x1", 0)), int(data.get("y1", 0))
x2, y2 = int(data.get("x2", 0)), int(data.get("y2", 0))
await xdo(["mousemove", str(x1), str(y1)], display)
await xdo(["mousedown", "1"], display)
await asyncio.sleep(0.1)
await xdo(["mousemove", str(x2), str(y2)], display)
await asyncio.sleep(0.1)
await xdo(["mouseup", "1"], display)
await send({"type": "ack", "action": "mouse_drag"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ keyboard_type โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "keyboard_type":
text = _safe_search(data.get("text", ""))
if text:
res = await type_smart(text, display)
await send({"type": "ack", "action": "keyboard_type", "method": res["method"]})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ keyboard_hotkey โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "keyboard_hotkey":
keys = data.get("keys", [])
if keys:
await xdo(["key", "--clearmodifiers", "+".join(keys)], display)
await send({"type": "ack", "action": "keyboard_hotkey", "keys": keys})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ keyboard_press โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "keyboard_press":
key = data.get("key", "")
if key:
await xdo(["key", "--clearmodifiers", key], display)
await send({"type": "ack", "action": "keyboard_press"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ scroll โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "scroll":
x, y = int(data.get("x", 960)), int(data.get("y", 540))
clicks = max(-5, min(5, int(data.get("clicks", 3))))
btn = "4" if clicks > 0 else "5"
await xdo(["mousemove", str(x), str(y)], display)
for _ in range(abs(clicks)):
await xdo(["click", btn], display)
await asyncio.sleep(0.025)
await send({"type": "ack", "action": "scroll", "clicks": clicks})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ clipboard_write โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "clipboard_write":
text = data.get("text", "")
env = {**os.environ, "DISPLAY": display}
def _clip():
p = subprocess.Popen(["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE, env=env)
p.communicate(text.encode("utf-8"))
await asyncio.to_thread(_clip)
await send({"type": "ack", "action": "clipboard_write", "length": len(text)})
# โ”€โ”€ clipboard_read โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "clipboard_read":
res = await run_cmd("xclip -selection clipboard -o", display, 5)
await send({"type": "clipboard_content", "text": res["stdout"]})
# โ”€โ”€ paste โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "paste":
text = data.get("text", "")
if text:
env = {**os.environ, "DISPLAY": display}
def _clip2():
p = subprocess.Popen(["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE, env=env)
p.communicate(text.encode("utf-8"))
await asyncio.to_thread(_clip2)
await asyncio.sleep(0.1)
await xdo(["key", "--clearmodifiers", "ctrl+v"], display)
await send({"type": "ack", "action": "paste"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ open_app โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "open_app":
cmd = _safe_search(data.get("cmd", ""))
if not cmd:
await send({"type": "ack", "action": "open_app"})
return
if "firefox" in cmd.lower():
profile_dir = _ensure_firefox_profile(sess["user_id"], display)
# ู‚ุชู„ ููˆุฑูŠ ูˆู‚ูˆูŠ (SIGKILL) ุจุฏู„ pkill ุงู„ู„ุทูŠู: ูŠู…ู†ุน ูุงูŠุฑููˆูƒุณ ู…ู†
# "ุงู„ุชู‚ุงุท" ุญุงู„ุฉ ู†ุตู-ู…ูุชูˆุญุฉ ุชูุณุฌูŽู‘ู„ ู„ุงุญู‚ุงู‹ ูƒุชุญุทู‘ู… (crash) ูŠูู†ุชุฌ
# ุดุงุดุฉ "Restore Session" ููŠ ุงู„ู…ุฑุฉ ุงู„ู‚ุงุฏู…ุฉ.
await run_cmd(
"pkill -9 -f '[f]irefox' 2>/dev/null; sleep 0.6; echo CLEANED",
display, timeout=8
)
_wipe_firefox_session_data(profile_dir)
# ุญู‚ู† --profile ุฏุงุฎู„ ุฃู…ุฑ ูุงูŠุฑููˆูƒุณ (ุฅู† ู„ู… ูŠูƒู† ู…ุญู‚ูˆู†ุงู‹ ู…ุณุจู‚ุงู‹)
if "--profile" not in cmd and "-P " not in cmd:
flags_str = " ".join(FIREFOX_CLI_FLAGS)
cmd = cmd.replace("firefox", f"firefox --profile '{profile_dir}' {flags_str}", 1)
env = {**os.environ, "DISPLAY": display, **(FIREFOX_ENV_EXTRA if "firefox" in cmd.lower() else {})}
proc = subprocess.Popen(cmd, shell=True, env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if any(b in cmd for b in ("firefox", "chromium", "chrome")):
with _display_lock:
if sess["user_id"] in _user_displays:
_user_displays[sess["user_id"]]["browser_proc"] = proc
await send({"type": "ack", "action": "open_app", "cmd": cmd})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ ุจุนุฏ ูุชุญ ุงู„ุชุทุจูŠู‚ โ”€โ”€
# โ”€โ”€ open_browser โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "open_browser":
url = _safe_search(data.get("url", "") or "about:blank")
profile_dir = _ensure_firefox_profile(sess["user_id"], display)
# ู‚ุชู„ ููˆุฑูŠ ูˆู‚ูˆูŠ (SIGKILL) ุจุฏู„ pkill ุงู„ู„ุทูŠู โ€” ูŠู…ู†ุน ุชุณุฌูŠู„ ุญุงู„ุฉ
# "ุชุญุทู‘ู…" ููŠ sessionstoreุŒ ูˆู‡ูŠ ุงู„ุณุจุจ ุงู„ุฌุฐุฑูŠ ู„ุดุงุดุฉ "Restore Session"
# ุงู„ุชูŠ ุชุญุฌุจ ุงู„ู…ุญุชูˆู‰ ุงู„ูุนู„ูŠ ูˆุชูู„ุชู‚ุท ูƒู„ู‚ุทุฉ ุดุงุดุฉ ุตุงู„ุญุฉ (ุบูŠุฑ ุณูˆุฏุงุก)
# ู„ูƒู†ู‡ุง ู„ูŠุณุช ุงู„ุตูุญุฉ ุงู„ู…ุทู„ูˆุจุฉ.
await run_cmd(
"pkill -9 -f '[f]irefox' 2>/dev/null; sleep 0.6; echo CLEANED",
display, timeout=8
)
_wipe_firefox_session_data(profile_dir)
env = {**os.environ, "DISPLAY": display, **FIREFOX_ENV_EXTRA}
proc = subprocess.Popen(
[BROWSER, "--profile", profile_dir, *FIREFOX_CLI_FLAGS, url], env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
with _display_lock:
if sess["user_id"] in _user_displays:
_user_displays[sess["user_id"]]["browser_proc"] = proc
await send({"type": "ack", "action": "open_browser", "url": url})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ ุจุนุฏ ูุชุญ ุงู„ู…ุชุตูุญ โ”€โ”€
# โ”€โ”€ open_tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "open_tab":
# ุฅุตู„ุงุญ: ุฒูŠุงุฏุฉ ุงู„ุชุฃุฎูŠุฑุงุช ุจูŠู† ูƒู„ ุฎุทูˆุฉ ูุฑุนูŠุฉ ู‡ู†ุง (Ctrl+T โ†’ Ctrl+L โ†’ ูƒุชุงุจุฉ ุงู„ุฑุงุจุท)
# ุงู„ู…ุดูƒู„ุฉ ุงู„ุฃุตู„ูŠุฉ: ุงู„ุชุฃุฎูŠุฑ ุงู„ู‚ุตูŠุฑ (0.4s) ุจุนุฏ Ctrl+T ู„ู… ูŠูƒู† ูƒุงููŠุงู‹ ุฏุงุฆู…ุงู‹ุŒ ุฎุตูˆุตุงู‹
# ุนู„ู‰ ุณูŠุฑูุฑ ู…ุญุฏูˆุฏ ุงู„ู…ูˆุงุฑุฏ ู‚ุฏ ูŠุณุชุบุฑู‚ ูุงูŠุฑููˆูƒุณ ููŠู‡ ูˆู‚ุชุงู‹ ุฃุทูˆู„ ู„ุฅู†ุดุงุก ุงู„ุชุจูˆูŠุจ ุงู„ุฌุฏูŠุฏ
# ูุนู„ูŠุงู‹ ูˆุชุญูˆูŠู„ ุงู„ุชุฑูƒูŠุฒ (focus) ุฅู„ูŠู‡. ุฅุฐุง ูˆุตู„ Ctrl+L ูˆุงู„ูƒุชุงุจุฉ ู‚ุจู„ ุงูƒุชู…ุงู„ ุฐู„ูƒุŒ
# ูุฅู†ู‡ุง ู‚ุฏ ุชุฐู‡ุจ ู„ู„ุชุจูˆูŠุจ ุงู„ู‚ุฏูŠู… ุจุงู„ุฎุทุฃุŒ ูู„ุง ุชู†ุชู‚ู„ ุงู„ุตูุญุฉ ูุนู„ูŠุงู‹ ุฑุบู… ู†ุฌุงุญ ุงู„ุฃู…ุฑ
# ุธุงู‡ุฑูŠุงู‹ (ุจุฏูˆู† ุฃูŠ ุฑุณุงู„ุฉ ุฎุทุฃ)ุŒ ููŠุจู‚ู‰ ุงู„ู…ุณุชุฎุฏู… ูŠุฑู‰ ู†ูุณ ุงู„ุตูุญุฉ ุงู„ู‚ุฏูŠู…ุฉ ู„ู„ุฃุจุฏ.
url = _safe_search(data.get("url", "") or "about:blank")
await xdo(["key", "--clearmodifiers", "ctrl+t"], display)
await asyncio.sleep(0.8)
await xdo(["key", "--clearmodifiers", "ctrl+l"], display)
await asyncio.sleep(0.3)
await type_smart(url, display)
await asyncio.sleep(0.3)
await xdo(["key", "--clearmodifiers", "Return"], display)
await send({"type": "ack", "action": "open_tab", "url": url})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ close_tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "close_tab":
await xdo(["key", "--clearmodifiers", "ctrl+w"], display)
await send({"type": "ack", "action": "close_tab"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ browser_back โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "browser_back":
await xdo(["key", "--clearmodifiers", "alt+Left"], display)
await send({"type": "ack", "action": "browser_back"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ browser_forward โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "browser_forward":
await xdo(["key", "--clearmodifiers", "alt+Right"], display)
await send({"type": "ack", "action": "browser_forward"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ browser_search โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "browser_search":
url = _safe_search(data.get("url", "") or data.get("query", ""))
await xdo(["key", "--clearmodifiers", "ctrl+l"], display)
await asyncio.sleep(0.2)
await type_smart(url, display)
await asyncio.sleep(0.15)
await xdo(["key", "--clearmodifiers", "Return"], display)
await send({"type": "ack", "action": "browser_search"})
# โ”€โ”€ ุชุนุทูŠู„ ุงู„ุงู„ุชู‚ุงุท ุงู„ุชู„ู‚ุงุฆูŠ โ”€โ”€
# โ”€โ”€ screen_info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
elif action == "screen_info":
w, h = await asyncio.to_thread(_get_screen_size, display)
mx, my = await asyncio.to_thread(_get_mouse_pos, display)
await send({
"type": "screen_info",
"width": w, "height": h,
"mouse_x": mx, "mouse_y": my,
"browser": BROWSER,
"display": display,
})
# โ”€โ”€ unknown โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
else:
await send({"type": "error", "msg": f"Unknown action: '{action}'"})
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ WebSocket Endpoint โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@app.websocket("/ws")
async def websocket_endpoint(
ws: WebSocket,
user_id: str = Query(default="anonymous")
):
await ws.accept()
# ุงู„ุญุตูˆู„ ุนู„ู‰ session ุงู„ู…ุณุชุฎุฏู… ุฃูˆ ุฅู†ุดุงุก ูˆุงุญุฏุฉ ุฌุฏูŠุฏุฉ
sess = await get_or_create_user_session(user_id, ws)
# ุชุฃูƒุฏ ุฃู† Xvfb ูŠุนู…ู„ ู„ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู…
await ensure_xvfb_for_session(sess)
display = sess["display"]
async def _heartbeat():
while True:
await asyncio.sleep(20)
try:
await ws.send_text(json.dumps({"type": "ping", "ts": int(time.time()*1000)}))
except Exception:
break
hb_task = asyncio.create_task(_heartbeat())
try:
w, h = await asyncio.to_thread(_get_screen_size, display)
await ws.send_text(json.dumps({
"type": "connected",
"screen_width": w, "screen_height": h,
"browser": BROWSER,
"display": display,
"user_id": user_id,
"session_id": id(ws),
"msg": f"Z Computer Mode v11 | User: {user_id} | Display: {display} | Browser: {BROWSER} | Screen: {w}x{h}",
}, ensure_ascii=False))
# ู„ู‚ุทุฉ ุดุงุดุฉ ุฃูˆู„ูŠุฉ
result = await asyncio.to_thread(capture_with_grid, display, 0.65, 72)
if result["data"]:
await ws.send_text(json.dumps({
"type": "screenshot",
"data": result["data"],
"data_grid": result.get("data_grid", ""),
"data_grid2": result.get("data_grid2", ""),
"data_grid3": result.get("data_grid3", ""),
"ts": int(time.time() * 1000),
"label": "Initial screen",
"screen_width": result["width"],
"screen_height": result["height"],
"mouse_x": result["mouse_x"],
"mouse_y": result["mouse_y"],
"has_grid": True,
}, ensure_ascii=False))
sess["last_bg_hash"] = _frame_hash(result["data"])
except Exception as e:
print(f"[ws:{user_id}] init error: {e}")
try:
while True:
raw = await ws.receive_text()
try:
msg = json.loads(raw)
if msg.get("type") == "pong":
continue
await handle_action(ws, msg, sess)
except json.JSONDecodeError:
pass
except WebSocketDisconnect:
pass
except Exception as e:
print(f"[ws:{user_id}] error: {e}")
finally:
hb_task.cancel()
await destroy_user_ws(user_id, ws)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ REST Endpoints โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@app.get("/screenshot")
async def rest_screenshot(user_id: str = "anonymous"):
with _display_lock:
sess = _user_displays.get(user_id)
display = sess["display"] if sess else f":{DISPLAY_BASE}"
result = await asyncio.to_thread(capture_with_grid, display, 0.7, 75)
return JSONResponse({
"image": result["data"],
"image_grid": result.get("data_grid", ""),
"image_grid2": result.get("data_grid2", ""),
"image_grid3": result.get("data_grid3", ""),
"ts": int(time.time() * 1000),
"screen_width": result["width"],
"screen_height": result["height"],
"mouse_x": result["mouse_x"],
"mouse_y": result["mouse_y"],
"has_grid": True,
"display": display,
"user_id": user_id,
})
@app.post("/terminal")
async def rest_terminal(body: dict):
user_id = body.get("user_id", "anonymous")
with _display_lock:
sess = _user_displays.get(user_id)
display = sess["display"] if sess else f":{DISPLAY_BASE}"
return JSONResponse(await run_cmd_smart(body.get("cmd", ""), display, body.get("timeout", 60)))
@app.get("/health")
async def health():
with _display_lock:
n = len(_user_displays)
users = [
{"user_id": uid, "display": s["display"],
"connected": bool(s.get("active_ws"))}
for uid, s in _user_displays.items()
]
return {
"status": "ok",
"version": "v13-fast-capture-fixed-firefox",
"browser": BROWSER,
"active_users": n,
"users": users,
}
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# โ”€โ”€ Background Tasks โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
async def _cleanup_tmp():
"""ูŠู†ุธู‘ู /tmp ูƒู„ 5 ุฏู‚ุงุฆู‚. ู…ู„ุงุญุธุฉ v13: ู…ุญุฑูƒ ุงู„ุงู„ุชู‚ุงุท
ุงู„ุฌุฏูŠุฏ (import-pipe/xlib-direct) ู„ุง ูŠูƒุชุจ ุฃูŠ ู…ู„ูุงุช zss_* ุนู„ู‰ ุงู„ุฅุทู„ุงู‚ (ูƒู„ุดูŠุก
ููŠ ุงู„ุฐุงูƒุฑุฉ ุนุจุฑ pipe)ุŒ ูู‡ุฐู‡ ุงู„ุฏุงู„ุฉ ุฃุตุจุญุช ุบูŠุฑ ุถุฑูˆุฑูŠุฉ ูุนู„ูŠุงู‹ ู„ู„ู‚ุทุงุช
ุงู„ุดุงุดุฉุŒ ู„ูƒู†ู‡ุง ุชูุฑูƒ ูƒุดุจูƒุฉ ุฃู…ุงู† ุฅุถุงููŠุฉ (ู…ุซู„ุงู‹ ู„ูˆ ุฃุถูŠู ูƒูˆุฏ ู…ุณุชู‚ุจู„ุงู‹
ูŠูƒุชุจ ู…ู„ูุงุช ู…ุคู‚ุชุฉ ุจู†ูุณ ุงู„ุจุงุฏุฆุฉ)."""
while True:
await asyncio.sleep(300)
try:
subprocess.run(
["find", "/tmp", "-name", "zss_*", "-mmin", "+10", "-delete"],
capture_output=True, timeout=10
)
except Exception as e:
print(f"[cleanup] {e}")
async def _cleanup_idle_sessions():
"""
ูŠูุฒูŠู„ sessions ุงู„ู…ุณุชุฎุฏู…ูŠู† ุบูŠุฑ ุงู„ู†ุดุทูŠู† (ู„ุง ุงุชุตุงู„ ู…ู†ุฐ ุฃูƒุซุฑ ู…ู† ุณุงุนุฉ)
ู„ุชุญุฑูŠุฑ ุงู„ู€ displays ูˆุงู„ุฐุงูƒุฑุฉ.
"""
while True:
await asyncio.sleep(1800) # ูƒู„ 30 ุฏู‚ูŠู‚ุฉ
now = time.time()
to_remove = []
with _display_lock:
for uid, sess in list(_user_displays.items()):
if sess.get("active_ws"):
continue # ู„ุง ุชุญุฐู sessions ุงู„ู†ุดุทุฉ
if now - sess.get("created", now) > 3600: # ุฃูƒุซุฑ ู…ู† ุณุงุนุฉ
to_remove.append((uid, sess))
for uid, sess in to_remove:
print(f"[cleanup] ๐Ÿ—‘๏ธ Removing idle session for '{uid}' on {sess['display']}")
_kill_proc(sess.get("browser_proc"))
_kill_proc(sess.get("xvfb_proc"))
_kill_display_processes(sess["display"])
with _display_lock:
_user_displays.pop(uid, None)
try:
_display_numbers.discard(int(sess["display"].lstrip(":")))
except Exception:
pass
@app.on_event("startup")
async def startup():
asyncio.create_task(_cleanup_tmp())
asyncio.create_task(_cleanup_idle_sessions())
print("โœ… Z Computer Mode v11 ready โ€” Per-User Isolated Displays")
print(f" Display range: :{DISPLAY_BASE} โ†’ :{DISPLAY_MAX}")
print(f" Connect: wss://your-space.hf.space/ws?user_id=EMAIL")
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
uvicorn.run("app:app", host="0.0.0.0", port=port, log_level="info")