# HOST/main-host.py
import asyncio
import logging
import sys
from HOST.aplication_host import Application
from load_config.loading_config_general import load_config_file_general
from load_config.loading_config_host import load_config_file_host
from core.logging_config import setup_logging
async def main_init():
logger = setup_logging()
Config_g = load_config_file_general()
Config_h = load_config_file_host()
application = Application(Config_g, Config_h)
try:
logger.info("iniciando application pyremote")
await application.start()
logger.info("pyremote iniciado correctamente")
await application.wait_until_shutdown()
except KeyboardInterrupt:
logger.info("interrupción recibida")
except Exception:
logger.exception("Error durante la ejecución de PyRemote.")
raise
finally:
logger.info("cerrando pyremote...")
await application.stop()
if __name__ == "__main__":
try:
asyncio.run(main_init())
except Exception:
logger.info("error inesperado al ejecutar pyremote")
sys.exit(1)
#___________________________________
# HOST/aplication_host.py
import asyncio
from core.logging_config import setup_logging
from server.server import Server
class Application:
def __init__(self, Config_g, Config_h):
self.shutdown_event=asyncio.Event()
self.conf_general=Config_g
self.conf_host=Config_h
self.server=Server(self.conf_general)
async def start(self):
await self.server.start_server()
async def wait_until_shutdown(self):
#ligar esto al cierre del pyremote
await self.shutdown_event.wait()
async def request_shutdown(self):
self.shutdown_event.set()
async def stop(self):
pass
#___________________________________
#server/server.py
import asyncio
import uvicorn
from import
class Server:
def __init__(self, config):
self.config=config
self.task=1
async def start_server(self):
pass
#___________________________________
#load_config/loading_config_general.py
import json
from dataclasses import dataclass
from core.config import CONFIG_GENERAL
@dataclass
class Application:
name: str
version: str
environment: str
@dataclass
class Discovery:
protocol: str
broadcast_address: str
port: int
broadcast_interval: int
response_timeout: int
device_expiration: int
@dataclass
class Signaling:
protocol: str
port: int
path: str
connection_timeout: int
reconnect_attempts: int
reconnect_interval: int
@dataclass
class Webrtc:
video_codec: str
max_fps: int
max_bitrate: int
ice_servers: list
@dataclass
class Security:
require_authorization: bool
authorization_timeout: int
@dataclass
class Logging:
level: str
file: str
@dataclass
class App_config:
application: Application
discovery: Discovery
signaling: Signaling
webrtc: Webrtc
security: Security
logging: Logging
def load_config_file_general(path=CONFIG_GENERAL) -> App_config:
with open(path, "r", encoding="utf-8") as file:
data = json.load(file)
return App_config(
application=Application(**data["application"]),
discovery=Discovery(**data["discovery"]),
signaling=Signaling(**data["signaling"]),
webrtc=Webrtc(**data["webrtc"]),
security=Security(**data["security"])
logging=Logging(**data["logging"])
)
#___________________________________
#load_config/loading_config_host.py
import json
from dataclasses import dataclass
from core.config import CONFIG_HOST
@dataclass
class Device:
device_id: str
device_name: str
mode: str
@dataclass
class Host:
enabled: bool
server_host: str
server_port: int
signaling_server: bool
@dataclass
class Client:
enabled: boom
signaling_server_url: null
@dataclass
class Services:
discovery: bool
device_manager: bool
connection_manager: bool
screen_capture: bool
screen_receiver: bool
@dataclass
class Preferences:
auto_start: bool
start_minimized: bool
@dataclass
class Config_host:
device: Device
host: Host
client: Client
services: Services
preferences: Preferences
def load_config_file_host(path=CONFIG_HOST) -> Config_host:
with open(path, "r", encoding="utf-8") as file:
data = json.load(file)
return Config_host(
device=Device(**data["device"]),
host=Host(**data["host"]),
client=Client(**data["client"]),
services=Services(**data["services"]),
preferences=Preferences(**data["preferences"])
)
#___________________________________
#core/config.py
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv(Path(__file__).resolve().parent.parent / ".env") # raíz del repo, no core/
def get_env(name: str, default: str | None = None) -> str:
value = os.getenv(name, default)
if value is None or value = "":
raise RuntimeError(f"Falta la variable de entorno requerida: {name}")
return value
CONFIG_GENERAL=get_env("CONFIG_GENERAL")
CONFIG_HOST=get_env("CONFIG_HOST")
#___________________________________
#.env
CONFIG_GENERAL="config_general.json"
CONFIG_HOST="config_host.json"
#___________________________________
#conf/config_general.json
{
"application": {
"name": "PyRemote",
"version": "1.0.0",
"environment": "production"
},
"discovery": {
"protocol": "UDP",
"broadcast_address": "255.255.255.255",
"port": 50000,
"broadcast_interval": 3,
"response_timeout": 2,
"device_expiration": 10
},
"signaling": {
"protocol": "WebSocket",
"port": 8000,
"path": "/ws",
"connection_timeout": 15,
"reconnect_attempts": 5,
"reconnect_interval": 3
},
"webrtc": {
"video_codec": "VP8",
"max_fps": 30,
"max_bitrate": 3000000,
"ice_servers": []
},
"security": {
"require_authorization": true,
"authorization_timeout": 60
},
"logging": {
"level": "INFO",
"file": "logs/pyremote.log"
}
}
#___________________________________
#conf/config_host.json
{
"device": {
"device_id": "GENERATED-UUID",
"device_name": "PC-A",
"mode": "HOST"
},
"host": {
"enabled": true,
"server_host": "0.0.0.0",
"server_port": 8000,
"signaling_server": true
},
"client": {
"enabled": false,
"signaling_server_url": null
},
"services": {
"discovery": true,
"device_manager": true,
"connection_manager": true,
"screen_capture": true,
"screen_receiver": true
},
"preferences": {
"auto_start": true,
"start_minimized": false
}
}
#___________________________________
#core/logging_config.py
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path
# Directorio de logs
BASE_DIR = Path(__file__).resolve().parent
LOG_DIR = BASE_DIR / "logs"
# Crear carpeta si no existe
LOG_DIR.mkdir(parents=True, exist_ok=True)
# Archivo de registros
LOG_FILE = LOG_DIR / "pyremote.log"
def setup_logging():
logger = logging.getLogger("PyRemote")
# Evitar configurar varias veces el mismo logger
if logger.handlers:
return logger
logger.setLevel(logging.DEBUG)
# Formato de los registros
formatter = logging.Formatter(
"%(asctime)s | %(levelname)s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# Registro en archivo con rotación
file_handler = RotatingFileHandler(
LOG_FILE,
maxBytes=5 * 1024 * 1024,
backupCount=5,
encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# Registro en consola
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
# Registrar manejadores
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
#___________________________________
#pyremote.log
2026-11-13 02:00:00 | INFO | HOST | Aplicación iniciada.
2026-11-13 02:00:01 | INFO | CONFIG | Configuración general cargada.
2026-11-13 02:00:01 | INFO | CONFIG | Configuración local HOST cargada.
2026-11-13 02:00:02 | INFO | UDP | Servicio de descubrimiento iniciado.
2026-11-13 02:00:03 | INFO | FASTAPI | Servidor iniciado en 0.0.0.0:8000.
2026-11-13 02:00:05 | INFO | UDP | Anuncio HOST enviado.
2026-11-13 02:00:10 | INFO | UDP | Solicitud de descubrimiento recibida.
2026-11-13 02:00:10 | INFO | UDP | Respuesta enviada a 192.168.1.21.
2026-11-13 02:00:11 | INFO | WEBSOCKET | Cliente conectado: PC-B.
2026-11-13 02:00:12 | INFO | CONNECTION | Solicitud de conexión recibida.
2026-11-13 02:00:15 | INFO | CONNECTION | Solicitud aceptada.
2026-11-13 02:00:16 | INFO | WEBRTC | Negociación iniciada.
2026-11-13 02:00:18 | INFO | WEBRTC | Conexión establecida.
2026-11-13 02:00:19 | INFO | SCREEN | Captura de pantalla iniciada.
2026-11-13 02:00:20 | INFO | WEBRTC | Transmisión de vídeo iniciada.
2026-11-13 02:10:00 | INFO | CONNECTION | Sesión finalizada.
2026-11-13 02:10:01 | INFO | HOST | Aplicación cerrada.
#___________________________________
# CLIENT/main_client.py
#___________________________________
#conf_client/config_general.json
{
"application": {
"name": "PyRemote",
"version": "1.0.0",
"environment": "production"
},
"discovery": {
"protocol": "UDP",
"broadcast_address": "255.255.255.255",
"port": 50000,
"broadcast_interval": 3,
"response_timeout": 2,
"device_expiration": 10
},
"signaling": {
"protocol": "WebSocket",
"port": 8000,
"path": "/ws",
"connection_timeout": 15,
"reconnect_attempts": 5,
"reconnect_interval": 3
},
"webrtc": {
"video_codec": "VP8",
"max_fps": 30,
"max_bitrate": 3000000,
"ice_servers": []
},
"security": {
"require_authorization": true,
"authorization_timeout": 60
},
"logging": {
"level": "INFO",
"file": "logs/pyremote.log"
}
}
#___________________________________
#conf_client/config_client.json
{
"device": {
"device_id": "GENERATED-UUID",
"device_name": "PC-B",
"mode": "CLIENT"
},
"host": {
"enabled": false,
"server_host": null,
"server_port": null,
"signaling_server": false
},
"client": {
"enabled": true,
"signaling_server_url": null,
"auto_discovery": true
},
"services": {
"discovery": true,
"device_manager": true,
"connection_manager": true,
"screen_capture": true,
"screen_receiver": true
},
"preferences": {
"auto_start": true,
"start_minimized": false
}
}
To embed this project on your website, copy the following code and paste it into your website's HTML: