#!/usr/bin/env python3 # SPDX-License-Identifier: MIT # Copyright (c) 2026 Maurii LLC — see LICENSE in the repository / download bundle. """ VNC4MAC.xyz — Mac VNC Client with Clipboard Injection Runs entirely on your Mac. Nothing to install on the VPS. USAGE: python3 vnc-portal-v3.py # Opens browser, configure there python3 vnc-portal-v3.py 217.216.62.242 5901 # Pre-fill and auto-connect """ import sys, os, subprocess, threading, time, webbrowser import socket, signal, http.server, socketserver, json, urllib.parse WS_PORT = 16900 HTTP_PORT = 16901 __version__ = '1.0.0' def _is_frozen_bundle(): """True inside PyInstaller or py2app bundles (not a normal python3 script).""" return bool(getattr(sys, 'frozen', False)) def _websockify_child_main(): sys.argv = ['websockify', sys.argv[2], sys.argv[3]] from websockify.websocketproxy import websockify_init websockify_init() if __name__ == '__main__' and len(sys.argv) >= 4 and sys.argv[1] == '--websockify-child': _websockify_child_main() raise SystemExit(0) # ══════════════════════════════════════════════════════════ # EMBEDDED HTML — TigerVNC-style single field UI # ══════════════════════════════════════════════════════════ HTML = r""" VNC4MAC.xyz
🖥
VNC4MAC.xyz
MAC · DIRECT VNC · CLIPBOARD INJECTION
VNC Server :
Password : 👁
Connects via local websockify bridge (started by the launcher).
Nothing installed on your VPS — connects to VNC port directly.
Connecting…
Connected To
Quick Keys
Clipboard · Command Injector
Paste or type your commands / scripts below
Text console (login: prompt)? Use Type Text. Send to VNC needs a GUI session for clipboard paste.
Saved Snippets
🖥
Connecting…
Establishing connection to VNC server.
1Use the real VNC TCP port (often 5901 for display :1, not a random high port unless you know it listens there).
2On the VPS: ss -tlnp | grep 590 and open that port in your provider firewall / security group.
3SSH tunnel? Connect to 127.0.0.1:LOCAL_PORT (the forwarded port on this Mac), not the public IP.
4Websockify must be available to the launcher (e.g. project venv: .venv/bin/pip install websockify and run with that Python).
""" # ══════════════════════════════════════════════════════════ # PYTHON LAUNCHER # ══════════════════════════════════════════════════════════ processes = [] _bridges = {} # { "host:port": ws_local_port } def find_free_port(start=17100): for p in range(start, start+500): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: if s.connect_ex(('127.0.0.1', p)) != 0: return p return None def probe_vnc_target(host, port, timeout=6.0): """TCP reachability before starting websockify. Returns (ok, error_message).""" if not host or not str(host).strip(): return False, 'Host is required' try: port_i = int(port) if not (1 <= port_i <= 65535): return False, f'Invalid port {port}' except (TypeError, ValueError): return False, f'Invalid port: {port}' host = host.strip() try: with socket.create_connection((host, port_i), timeout=timeout): pass return True, '' except socket.timeout: return False, ( f'No reply from {host}:{port_i} (timed out). ' 'Is the server up and is this port open in the cloud firewall / security group?' ) except ConnectionRefusedError: return False, ( f'Connection refused on {host}:{port_i} — nothing is listening there. ' 'Linux VNC is usually 5900 + display (e.g. :1 → 5901). ' 'On the VPS run: ss -tlnp | grep 590' ) except OSError as e: msg = getattr(e, 'strerror', None) or str(e) return False, f'Cannot reach {host}:{port_i}: {msg}' def ws_bin(): for c in ['websockify', 'websockify3']: try: r = subprocess.run([c, '--help'], capture_output=True, timeout=4) if r.returncode == 0: return c except (FileNotFoundError, subprocess.TimeoutExpired): pass return None def ensure_websockify(): if _is_frozen_bundle(): try: import websockify.websocketproxy # noqa: F401 return True except ImportError: print("[VNC4MAC.xyz] websockify is missing from this app bundle — rebuild the Mac app.") return False if ws_bin(): return True print("[VNC4MAC.xyz] Installing websockify…") r = subprocess.run([sys.executable, '-m', 'pip', 'install', '--quiet', 'websockify']) return r.returncode == 0 def _websockify_spawn_argv(local_port, target_host_port): """Argv to spawn websockify. target_host_port is 'host:port'.""" if _is_frozen_bundle(): return [sys.executable, '--websockify-child', str(local_port), target_host_port] cli = ws_bin() if cli: return [cli, str(local_port), target_host_port] return [sys.executable, __file__, '--websockify-child', str(local_port), target_host_port] def start_ws(local_port, host, port): target = f'{host}:{port}' args = _websockify_spawn_argv(local_port, target) print(f"[VNC4MAC.xyz] Bridge: localhost:{local_port} → {target}") try: p = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) processes.append(p) time.sleep(1.0) return p except Exception as e: print(f"[VNC4MAC.xyz] websockify failed: {e}") return None class Handler(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass def do_GET(self): p = urllib.parse.urlparse(self.path) qs = urllib.parse.parse_qs(p.query) if p.path in ('/', ''): body = HTML.encode('utf-8') self.send_response(200) self.send_header('Content-Type','text/html;charset=utf-8') self.send_header('Content-Length', len(body)) self.end_headers(); self.wfile.write(body) elif p.path == '/api/connect': host = (qs.get('host', [''])[0] or '').strip() port = (qs.get('port', ['5901'])[0] or '5901').strip() if not host: self._json({'error': 'Missing host'}) return key = f'{host}:{port}' if key in _bridges: self._json({'ws_port': _bridges[key]}) return ok, probe_err = probe_vnc_target(host, port) if not ok: self._json({'error': probe_err}) return ws_p = find_free_port() proc = start_ws(ws_p, host, port) if proc: _bridges[key] = ws_p self._json({'ws_port': ws_p}) else: self._json({'error': 'websockify failed to start'}) else: self.send_response(404); self.end_headers() def _json(self, obj): b = json.dumps(obj).encode() self.send_response(200) self.send_header('Content-Type','application/json') self.send_header('Access-Control-Allow-Origin','*') self.send_header('Content-Length',len(b)) self.end_headers(); self.wfile.write(b) def cleanup(*_): print("\n[VNC4MAC.xyz] Shutting down…") for p in processes: try: p.terminate() except: pass sys.exit(0) def _setup_bundle_logging(): """Console-less .app: append traces under ~/Library/Logs/VNC4MAC.xyz/.""" if not _is_frozen_bundle(): return None if sys.stdout is not None and hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): return None log_root = os.path.join(os.path.expanduser('~'), 'Library', 'Logs', 'VNC4MAC.xyz') os.makedirs(log_root, exist_ok=True) path = os.path.join(log_root, 'launcher.log') try: _lf = open(path, 'a', encoding='utf-8', buffering=1) os.dup2(_lf.fileno(), 1) os.dup2(_lf.fileno(), 2) print(f"\n--- session start v{__version__} ---") return path except OSError: return None def main(): signal.signal(signal.SIGINT, cleanup) signal.signal(signal.SIGTERM, cleanup) import argparse ap = argparse.ArgumentParser(description='VNC4MAC.xyz — Mac VNC in the browser') ap.add_argument('--version', action='version', version=f'VNC4MAC.xyz {__version__}') ap.add_argument('host', nargs='?', default=None) ap.add_argument('port', nargs='?', default='5901') ap.add_argument('--ws-port', type=int, default=WS_PORT) ap.add_argument('--http-port', type=int, default=HTTP_PORT) args = ap.parse_args() log_path = _setup_bundle_logging() bar = '═' * 42 print('╔' + bar + '╗') inner = f' VNC4MAC.xyz v{__version__} — Mac + Clipboard' inner = (inner + ' ' * 42)[:42] print('║' + inner + '║') print('╚' + bar + '╝') if log_path: print(f"[VNC4MAC.xyz] Log file: {log_path}") ensure_websockify() if args.host: start_ws(args.ws_port, args.host, args.port) _bridges[f'{args.host}:{args.port}'] = args.ws_port print(f"[VNC4MAC.xyz] Pre-bridged: localhost:{args.ws_port} → {args.host}:{args.port}") class _ReuseTCPServer(socketserver.TCPServer): allow_reuse_address = True httpd = _ReuseTCPServer(('127.0.0.1', args.http_port), Handler) url = f"http://127.0.0.1:{args.http_port}/?wsport={args.ws_port}" print(f"[VNC4MAC.xyz] Running at: {url}") print("[VNC4MAC.xyz] Opening browser… (Ctrl+C to stop)\n") threading.Timer(1.2, lambda: webbrowser.open(url)).start() try: httpd.serve_forever() except KeyboardInterrupt: cleanup() if __name__ == '__main__': main()