import os, logging, subprocess, threading DEBUG = os.environ.get('DEBUG') logging.basicConfig( format='[%(asctime)s] %(levelname)s %(module)s/%(funcName)s - %(message)s', level=logging.DEBUG if DEBUG else logging.INFO) from flask import Flask, request AUTO_STOP_TIMEOUT_SECONDS = 3600 # 1 hour LAST_CAST_ADDRESS = None app = Flask(__name__) auto_stop_timer = None # Timer for automatic VNC stop @app.route('/cast', methods=['POST']) def cast_spell(): global auto_stop_timer, LAST_CAST_ADDRESS ip_address = request.form.get('ip_address') logging.info(f"Received POST request on /cast. Requested ip_address: {ip_address}") if not ip_address: logging.warning(f"Invalid or missing ip_address parameter: {ip_address}") return f"Invalid or missing 'ip_address' parameter.", 400 logging.info(f"Casting to {ip_address}.") kill_vnc() # Kill any existing VNC session first cast_machine(ip_address) LAST_CAST_ADDRESS = ip_address # Restart auto-stop timer if auto_stop_timer is not None and auto_stop_timer.is_alive(): auto_stop_timer.cancel() logging.info("Cancelled previous auto-stop timer.") auto_stop_timer = threading.Timer(AUTO_STOP_TIMEOUT_SECONDS, _auto_stop_vnc) auto_stop_timer.start() logging.info(f"Scheduled auto-stop for VNC in {AUTO_STOP_TIMEOUT_SECONDS} seconds.") return f"Successfully cast to {ip_address}.", 200 @app.route('/stop', methods=['POST']) def stop_cast(): global auto_stop_timer, LAST_CAST_ADDRESS ip_address = request.form.get('ip_address') logging.info(f"Received POST request on /stop for ip_address: {ip_address}") if not ip_address: logging.warning(f"Invalid or missing ip_address parameter: {ip_address}") return f"Invalid or missing 'ip_address' parameter.", 400 if ip_address != LAST_CAST_ADDRESS: logging.warning(f"Stop request for {ip_address} does not match last cast address {LAST_CAST_ADDRESS}.") return f"Stop request for {ip_address} does not match last cast address.", 403 if auto_stop_timer is not None and auto_stop_timer.is_alive(): auto_stop_timer.cancel() auto_stop_timer = None logging.info("Manual stop: auto-stop timer cancelled.") kill_vnc() LAST_CAST_ADDRESS = None return "Attempted to stop VNC viewers.", 200 def _auto_stop_vnc(): """Called by the timer to automatically stop VNC.""" global auto_stop_timer logging.info("Auto-stopping VNC viewers due to inactivity timer.") kill_vnc() auto_stop_timer = None # Clear the timer reference def cast_machine(ip_address): """Executes the xtightvncviewer command for a given IP address.""" command = f"DISPLAY=:1 xtightvncviewer -viewonly -fullscreen {ip_address}" try: logging.info(f"Launching command: {command}") # Use Popen to run the command in the background (non-blocking) subprocess.Popen(command, shell=True) logging.info(f"Command '{command}' launched successfully.") except Exception as e: # Catch other potential errors during Popen logging.error(f"An error occurred while launching VNC for {ip_address} with Popen: {e}") def kill_vnc(): """Executes the killall command for xtightvncviewer.""" command = "killall xtightvncviewer" try: logging.info(f"Executing command: {command}") # We don't use check=True here because killall returns a non-zero exit code # if no processes were killed, which is not necessarily an error in this context. result = subprocess.run(command, shell=True, capture_output=True, text=True) if result.returncode == 0: logging.info("killall command executed successfully. Processes were likely terminated.") else: # killall returns 1 if no matching processes were found. # Other non-zero codes might indicate other errors. logging.warning(f"killall command finished. Exit code: {result.returncode}. Stderr: {result.stderr.strip()}") if "no process found" in result.stderr.lower(): logging.info("No xtightvncviewer processes were found to kill.") except Exception as e: logging.error(f"An unexpected error occurred while trying to run killall: {e}") def main(): app.run(debug=DEBUG, host='0.0.0.0', port=int(os.environ.get('PORT', 5000))) if __name__ == '__main__': main()