wep2 / app.py
Alvin3y1's picture
Create app.py
6540af4 verified
import asyncio
import json
import os
import shutil
import subprocess
import time
import logging
from aiohttp import web
from aiortc import RTCPeerConnection, RTCSessionDescription, RTCIceServer, RTCConfiguration
# --- Configuration ---
HOST = "0.0.0.0"
PORT = 7860
DISPLAY_NUM = ":99"
VNC_PORT = 5900
DEFAULT_RES = "1280x720"
TURN_USER = "g08abe68c81a07f098bb5f0914549bb32440e5aad0b216c7fba2b61e76fd62c6"
TURN_PASS = "aed1a10dd10eba9401ad9d99e5c66036d8a970eab5ba8e6dc9845ab57c771a7d"
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("TigerVNC-Bridge")
def start_system():
os.environ["DISPLAY"] = DISPLAY_NUM
# 1. Start TigerVNC (Xvnc)
# SecurityTypes=None: Allows connection without password (since it's only on localhost)
# -alwaysshared: Allows multiple WebRTC clients to see the same screen
logger.info(f"Starting TigerVNC (Xvnc) on {DISPLAY_NUM}...")
subprocess.Popen([
"Xvnc", DISPLAY_NUM,
"-geometry", DEFAULT_RES,
"-depth", "24",
"-rfbport", str(VNC_PORT),
"-SecurityTypes", "None",
"-alwaysshared",
"-AcceptKeyEvents", "-AcceptPointerEvents", "-AcceptSetDesktopSize",
"-localhost" # Only allow connections from this script
])
time.sleep(2)
# 2. Start Window Manager
if shutil.which("matchbox-window-manager"):
subprocess.Popen("matchbox-window-manager -use_titlebar no", shell=True)
# 3. Start Opera
opera_cmd = "opera --no-sandbox --start-maximized --user-data-dir=/home/user/opera-data"
subprocess.Popen(opera_cmd, shell=True)
async def bridge_vnc_to_datachannel(channel):
try:
# Connect to TigerVNC TCP socket
reader, writer = await asyncio.open_connection('127.0.0.1', VNC_PORT)
logger.info("Connected to TigerVNC.")
async def vnc_to_webrtc():
try:
while True:
data = await reader.read(32768) # Larger buffer for TigerVNC speed
if not data: break
channel.send(data)
except: pass
@channel.on("message")
def on_message(message):
if isinstance(message, bytes):
writer.write(message)
elif isinstance(message, str):
try:
d = json.loads(message)
if d.get("type") == "resize":
# TigerVNC supports dynamic resizing via xrandr
subprocess.run(["xrandr", "-s", f"{d['width']}x{d['height']}"], check=False)
except: pass
await vnc_to_webrtc()
except Exception as e:
logger.error(f"Bridge error: {e}")
finally:
if 'writer' in locals(): writer.close()
# --- Standard WebRTC Signaling Handlers ---
pcs = set()
async def offer(request):
params = await request.json()
pc = RTCPeerConnection(RTCConfiguration(iceServers=[
RTCIceServer(urls=["stun:stun.l.google.com:19302"]),
RTCIceServer(urls=["turns:turn.cloudflare.com:443?transport=tcp"], username=TURN_USER, credential=TURN_PASS)
]))
pcs.add(pc)
@pc.on("datachannel")
def on_dc(channel):
asyncio.create_task(bridge_vnc_to_datachannel(channel))
@pc.on("connectionstatechange")
async def on_state():
if pc.connectionState in ["failed", "closed"]:
await pc.close()
pcs.discard(pc)
await pc.setRemoteDescription(RTCSessionDescription(sdp=params["sdp"], type=params["type"]))
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
return web.Response(
content_type="application/json",
text=json.dumps({"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}),
headers={"Access-Control-Allow-Origin": "*"}
)
async def options(request):
return web.Response(headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type"
})
if __name__ == "__main__":
start_system()
app = web.Application()
app.router.add_post("/offer", offer)
app.router.add_options("/offer", options)
web.run_app(app, host=HOST, port=PORT)