Alvin3y1 commited on
Commit
e603465
·
verified ·
1 Parent(s): 3966aff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -34
app.py CHANGED
@@ -9,7 +9,7 @@ import mss
9
  import threading
10
  from io import BytesIO
11
  from PIL import Image
12
- from Xlib import display, X, error
13
  from Xlib.ext import xtest
14
  from aiohttp import web
15
  from aiortc import RTCPeerConnection, RTCSessionDescription, RTCIceServer, RTCConfiguration
@@ -19,8 +19,6 @@ HOST = "0.0.0.0"
19
  PORT = 7860
20
  DISPLAY_NUM = ":99"
21
  WIDTH, HEIGHT = 1280, 720
22
- FPS = 60
23
- INTERVAL = 1.0 / FPS
24
 
25
  TURN_USER = "g08abe68c81a07f098bb5f0914549bb32440e5aad0b216c7fba2b61e76fd62c6"
26
  TURN_PASS = "aed1a10dd10eba9401ad9d99e5c66036d8a970eab5ba8e6dc9845ab57c771a7d"
@@ -30,20 +28,30 @@ logger = logging.getLogger("X11-RTC")
30
 
31
  class X11Engine:
32
  def __init__(self):
 
 
 
 
 
33
  try:
34
  self.d = display.Display(DISPLAY_NUM)
35
  self.root = self.d.screen().root
36
- if not self.d.has_extension('DAMAGE'):
37
- logger.error("DAMAGE extension not found")
38
- self.damage_ext = self.d.damage_create(self.root, X.DamageReportLevelNonEmpty)
39
- self.sct = mss.mss()
40
- self.dirty_rects = []
41
- self.lock = threading.Lock()
 
 
42
  except Exception as e:
43
- logger.error(f"X11 Init Failed: {e}")
44
- raise
 
45
 
46
  def collect_damage(self):
 
 
47
  try:
48
  while self.d.pending_events() > 0:
49
  ev = self.d.next_event()
@@ -54,9 +62,13 @@ class X11Engine:
54
  except: pass
55
 
56
  def get_patches(self):
 
 
 
 
57
  with self.lock:
58
  if not self.dirty_rects: return []
59
- rects = list(self.dirty_rects[-10:]) # Limit patches per frame
60
  self.dirty_rects = []
61
 
62
  patches = []
@@ -70,7 +82,16 @@ class X11Engine:
70
  except: continue
71
  return patches
72
 
 
 
 
 
 
 
 
 
73
  def inject_input(self, msg):
 
74
  try:
75
  t = msg.get("type")
76
  if t == "mousemove":
@@ -89,12 +110,8 @@ async def patch_stream_loop(channel, engine):
89
  patches = await asyncio.to_thread(engine.get_patches)
90
  if patches:
91
  channel.send(json.dumps({"type": "patch", "p": patches}))
92
- await asyncio.sleep(max(0, INTERVAL - (time.perf_counter() - start)))
93
-
94
- # --- ROUTES ---
95
-
96
- async def index(request):
97
- return web.Response(text="hello world")
98
 
99
  async def offer(request):
100
  try:
@@ -117,28 +134,25 @@ async def offer(request):
117
  await pc.setRemoteDescription(RTCSessionDescription(sdp=params["sdp"], type=params["type"]))
118
  answer = await pc.createAnswer()
119
  await pc.setLocalDescription(answer)
120
-
121
- return web.json_response(
122
- {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type},
123
- headers={"Access-Control-Allow-Origin": "*"}
124
- )
125
  except Exception as e:
126
- logger.error(f"Offer Error: {e}")
127
  return web.json_response({"error": str(e)}, status=500)
128
 
129
- async def options(r):
130
- return web.Response(headers={
131
- "Access-Control-Allow-Origin": "*",
132
- "Access-Control-Allow-Methods": "POST, OPTIONS",
133
- "Access-Control-Allow-Headers": "Content-Type"
134
- })
135
 
136
  if __name__ == "__main__":
137
  os.environ["DISPLAY"] = DISPLAY_NUM
138
- # Use -ac to disable access control (essential for Docker/HF)
139
- subprocess.Popen(["Xvfb", DISPLAY_NUM, "-screen", "0", f"{WIDTH}x{HEIGHT}x24", "+extension", "DAMAGE", "-ac"])
140
- time.sleep(3)
141
- subprocess.Popen("opera --no-sandbox --start-maximized", shell=True)
 
 
 
 
 
 
142
 
143
  app = web.Application()
144
  app.router.add_get("/", index)
 
9
  import threading
10
  from io import BytesIO
11
  from PIL import Image
12
+ from Xlib import display, X
13
  from Xlib.ext import xtest
14
  from aiohttp import web
15
  from aiortc import RTCPeerConnection, RTCSessionDescription, RTCIceServer, RTCConfiguration
 
19
  PORT = 7860
20
  DISPLAY_NUM = ":99"
21
  WIDTH, HEIGHT = 1280, 720
 
 
22
 
23
  TURN_USER = "g08abe68c81a07f098bb5f0914549bb32440e5aad0b216c7fba2b61e76fd62c6"
24
  TURN_PASS = "aed1a10dd10eba9401ad9d99e5c66036d8a970eab5ba8e6dc9845ab57c771a7d"
 
28
 
29
  class X11Engine:
30
  def __init__(self):
31
+ self.sct = mss.mss()
32
+ self.dirty_rects = []
33
+ self.lock = threading.Lock()
34
+ self.use_damage = False
35
+
36
  try:
37
  self.d = display.Display(DISPLAY_NUM)
38
  self.root = self.d.screen().root
39
+
40
+ # Check for Damage Extension
41
+ if self.d.has_extension('DAMAGE'):
42
+ self.damage_ext = self.d.damage_create(self.root, X.DamageReportLevelNonEmpty)
43
+ self.use_damage = True
44
+ logger.info("X11 Damage Extension enabled.")
45
+ else:
46
+ logger.warning("DAMAGE extension missing. Falling back to polling mode.")
47
  except Exception as e:
48
+ logger.error(f"X11 Connection failed: {e}. Ensure Xvfb is running.")
49
+ # We don't raise here; we allow the engine to exist in polling-only mode
50
+ self.d = None
51
 
52
  def collect_damage(self):
53
+ if not self.d or not self.use_damage:
54
+ return
55
  try:
56
  while self.d.pending_events() > 0:
57
  ev = self.d.next_event()
 
62
  except: pass
63
 
64
  def get_patches(self):
65
+ # Fallback: If no damage events, capture a small central patch or full screen periodically
66
+ if not self.use_damage:
67
+ return [{"x": 0, "y": 0, "w": WIDTH, "h": HEIGHT, "d": self.capture_full()}]
68
+
69
  with self.lock:
70
  if not self.dirty_rects: return []
71
+ rects = list(self.dirty_rects[-5:]) # Limit to 5 patches per frame for stability
72
  self.dirty_rects = []
73
 
74
  patches = []
 
82
  except: continue
83
  return patches
84
 
85
+ def capture_full(self):
86
+ """Standard polling capture."""
87
+ sct_img = self.sct.grab({"top": 0, "left": 0, "width": WIDTH, "height": HEIGHT})
88
+ img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
89
+ buf = BytesIO()
90
+ img.save(buf, format="JPEG", quality=50)
91
+ return base64.b64encode(buf.getvalue()).decode()
92
+
93
  def inject_input(self, msg):
94
+ if not self.d: return
95
  try:
96
  t = msg.get("type")
97
  if t == "mousemove":
 
110
  patches = await asyncio.to_thread(engine.get_patches)
111
  if patches:
112
  channel.send(json.dumps({"type": "patch", "p": patches}))
113
+ # 30 FPS target for stability in HF
114
+ await asyncio.sleep(max(0, 0.033 - (time.perf_counter() - start)))
 
 
 
 
115
 
116
  async def offer(request):
117
  try:
 
134
  await pc.setRemoteDescription(RTCSessionDescription(sdp=params["sdp"], type=params["type"]))
135
  answer = await pc.createAnswer()
136
  await pc.setLocalDescription(answer)
137
+ return web.json_response({"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}, headers={"Access-Control-Allow-Origin": "*"})
 
 
 
 
138
  except Exception as e:
 
139
  return web.json_response({"error": str(e)}, status=500)
140
 
141
+ async def index(request): return web.Response(text="hello world")
142
+ async def options(r): return web.Response(headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type"})
 
 
 
 
143
 
144
  if __name__ == "__main__":
145
  os.environ["DISPLAY"] = DISPLAY_NUM
146
+ # UPDATED XVFB COMMAND FOR HF SPACES
147
+ subprocess.Popen([
148
+ "Xvfb", DISPLAY_NUM,
149
+ "-screen", "0", f"{WIDTH}x{HEIGHT}x24",
150
+ "+extension", "DAMAGE",
151
+ "+extension", "RANDR",
152
+ "+render", "-ac", "-noreset"
153
+ ])
154
+ time.sleep(5) # Increased wait time for Xvfb
155
+ subprocess.Popen("opera --no-sandbox --disable-gpu --start-maximized", shell=True)
156
 
157
  app = web.Application()
158
  app.router.add_get("/", index)