#!/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
—
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.
🖥
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()