Fix compatibility with NFCNetworkClient v1.1.0 - properly pass root parameter
This commit is contained in:
@@ -1 +1,290 @@
|
||||
$(cat /home/claude/nfc_autoconnect.py)
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Smart Auto-Connect NFC Network Client
|
||||
Moonlight Drive-In Theater System
|
||||
|
||||
Enhanced client with smart DNS resolution and automatic connection.
|
||||
Compatible with NFCNetworkClient v1.1.0 (global input capture).
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
# Import the base client
|
||||
try:
|
||||
from nfc_network_client import NFCNetworkClient, CLIENT_VERSION as BASE_VERSION
|
||||
except ImportError:
|
||||
print("ERROR: Cannot find nfc_network_client.py")
|
||||
print("Make sure nfc_network_client.py is in the same directory")
|
||||
sys.exit(1)
|
||||
|
||||
SMART_CLIENT_VERSION = "2.0.0"
|
||||
|
||||
class DNSResolver:
|
||||
"""Smart DNS resolution with multiple fallback methods"""
|
||||
|
||||
def __init__(self, device_name: str, fallback_ip: str, port: int = 8547):
|
||||
self.device_name = device_name
|
||||
self.fallback_ip = fallback_ip
|
||||
self.port = port
|
||||
self.last_known_ip: Optional[str] = None
|
||||
|
||||
def resolve(self) -> Tuple[Optional[str], str]:
|
||||
"""
|
||||
Try multiple DNS resolution methods
|
||||
Returns: (ip_address, method_name)
|
||||
"""
|
||||
print(f"\nAttempting to locate device: {self.device_name}")
|
||||
|
||||
# Method 1: Standard DNS
|
||||
result = self._try_standard_dns()
|
||||
if result:
|
||||
return result, "STANDARD_DNS"
|
||||
|
||||
# Method 2: mDNS (.local)
|
||||
result = self._try_mdns()
|
||||
if result:
|
||||
return result, "MDNS"
|
||||
|
||||
# Method 3: Tailscale DNS
|
||||
result = self._try_tailscale_dns()
|
||||
if result:
|
||||
return result, "TAILSCALE_DNS"
|
||||
|
||||
# Method 4: Fallback to known IP
|
||||
if self.fallback_ip:
|
||||
print(f"\nMethod: Fallback to known IP")
|
||||
if self._check_host_reachable(self.fallback_ip):
|
||||
print(f"SUCCESS: Fallback IP is reachable: {self.fallback_ip}")
|
||||
return self.fallback_ip, "FALLBACK_IP"
|
||||
|
||||
print("\nERROR: Could not locate device using any method")
|
||||
return None, "FAILED"
|
||||
|
||||
def _try_standard_dns(self) -> Optional[str]:
|
||||
"""Try standard DNS resolution"""
|
||||
print(f"\nMethod: Standard DNS")
|
||||
try:
|
||||
ip = socket.gethostbyname(self.device_name)
|
||||
print(f" [DNS] ✓ Resolved: {self.device_name} -> {ip}")
|
||||
|
||||
if self._check_host_reachable(ip):
|
||||
print(f"SUCCESS: Found {self.device_name} at {ip} via Standard DNS")
|
||||
return ip
|
||||
except socket.gaierror:
|
||||
print(f" [DNS] ✗ Failed to resolve: {self.device_name}")
|
||||
|
||||
return None
|
||||
|
||||
def _try_mdns(self) -> Optional[str]:
|
||||
"""Try mDNS resolution (.local)"""
|
||||
mdns_name = f"{self.device_name}.local"
|
||||
print(f"\nMethod: mDNS (Bonjour)")
|
||||
|
||||
try:
|
||||
ip = socket.gethostbyname(mdns_name)
|
||||
print(f" [mDNS] ✓ Resolved: {mdns_name} -> {ip}")
|
||||
|
||||
if self._check_host_reachable(ip):
|
||||
print(f"SUCCESS: Found {self.device_name} at {ip} via mDNS")
|
||||
return ip
|
||||
except socket.gaierror:
|
||||
print(f" [mDNS] ✗ Failed to resolve: {mdns_name}")
|
||||
|
||||
return None
|
||||
|
||||
def _try_tailscale_dns(self) -> Optional[str]:
|
||||
"""Try Tailscale DNS resolution"""
|
||||
ts_name = f"{self.device_name}.tail-scale.ts.net"
|
||||
print(f"\nMethod: Tailscale VPN DNS")
|
||||
|
||||
try:
|
||||
ip = socket.gethostbyname(ts_name)
|
||||
print(f" [Tailscale] ✓ Resolved: {ts_name} -> {ip}")
|
||||
|
||||
if self._check_host_reachable(ip):
|
||||
print(f"SUCCESS: Found {self.device_name} at {ip} via Tailscale")
|
||||
return ip
|
||||
except socket.gaierror:
|
||||
print(f" [Tailscale] ✗ Failed to resolve: {ts_name}")
|
||||
|
||||
return None
|
||||
|
||||
def _check_host_reachable(self, ip: str) -> bool:
|
||||
"""Check if host is reachable via ping"""
|
||||
try:
|
||||
# Try quick ping (1 packet, 1 second timeout)
|
||||
if sys.platform == "win32":
|
||||
result = subprocess.run(
|
||||
["ping", "-n", "1", "-w", "1000", ip],
|
||||
capture_output=True,
|
||||
timeout=2
|
||||
)
|
||||
else:
|
||||
result = subprocess.run(
|
||||
["ping", "-c", "1", "-W", "1", ip],
|
||||
capture_output=True,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f" [Ping] ✓ Host reachable: {ip}")
|
||||
return True
|
||||
else:
|
||||
print(f" [Ping] ✗ Host unreachable: {ip}")
|
||||
return False
|
||||
|
||||
except (subprocess.TimeoutExpired, Exception) as e:
|
||||
print(f" [Ping] ✗ Ping failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class SmartAutoConnectClient(NFCNetworkClient):
|
||||
"""Enhanced NFC client with smart auto-connect"""
|
||||
|
||||
def __init__(self, root: tk.Tk, device_name: str, fallback_ip: str, port: int = 8547):
|
||||
# Store smart connect settings
|
||||
self.device_name = device_name
|
||||
self.fallback_ip = fallback_ip
|
||||
self.target_port = port
|
||||
self.resolver = DNSResolver(device_name, fallback_ip, port)
|
||||
|
||||
# Initialize parent with root
|
||||
super().__init__(root)
|
||||
|
||||
# Update title to show smart connect
|
||||
self.root.title(f"NFC Network Client - Smart Connect v{SMART_CLIENT_VERSION}")
|
||||
|
||||
# Customize UI for smart connect
|
||||
self._customize_ui()
|
||||
|
||||
def _customize_ui(self):
|
||||
"""Add smart connect specific UI elements"""
|
||||
# Update server entry to show device name
|
||||
self.server_entry.delete(0, tk.END)
|
||||
self.server_entry.insert(0, f"{self.device_name} (auto-resolved)")
|
||||
self.server_entry.config(state="disabled")
|
||||
|
||||
def connect_to_server(self):
|
||||
"""Override connect to use smart DNS resolution"""
|
||||
self.log(f"Smart connecting to: {self.device_name}")
|
||||
self.update_status("Resolving...", "orange")
|
||||
self.connect_btn.config(state="disabled")
|
||||
|
||||
# Resolve in background thread
|
||||
def resolve_and_connect():
|
||||
ip, method = self.resolver.resolve()
|
||||
|
||||
if ip:
|
||||
print(f"\n{'='*70}")
|
||||
print(f"WILL CONNECT TO: {ip}:{self.target_port}")
|
||||
print(f"Resolution method: {method}")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
# Try to connect
|
||||
url = f"http://{ip}:{self.target_port}"
|
||||
|
||||
# Test the connection
|
||||
import requests
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{url}/api/status",
|
||||
timeout=2
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get("server_type") == "moonlight_drivein":
|
||||
# Success!
|
||||
self.root.after(0, lambda: self.connection_success(url))
|
||||
return
|
||||
except:
|
||||
pass
|
||||
|
||||
# Failed
|
||||
self.root.after(0, self.connection_failed)
|
||||
|
||||
import threading
|
||||
threading.Thread(target=resolve_and_connect, daemon=True).start()
|
||||
|
||||
def connection_failed(self):
|
||||
"""Handle connection failure with smart reconnect"""
|
||||
super().connection_failed()
|
||||
self.log("Will retry in 5 seconds...", "INFO")
|
||||
|
||||
# Auto-retry after 5 seconds
|
||||
self.root.after(5000, self.connect_to_server)
|
||||
|
||||
|
||||
def print_banner():
|
||||
"""Print startup banner"""
|
||||
print("\n" + "="*70)
|
||||
print(" " * 20 + "NFC Network Client - Smart Auto-Connect")
|
||||
print("="*70)
|
||||
print(f"\nTarget device: {CONFIG['device_name']}")
|
||||
print(f"Fallback IP: {CONFIG['fallback_ip']}")
|
||||
print(f"Port: {CONFIG['port']}")
|
||||
print("\n" + "="*70)
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
"""Load configuration from environment or defaults"""
|
||||
return {
|
||||
'device_name': os.environ.get('DRIVEIN_DEVICE', 'drive-in'),
|
||||
'fallback_ip': os.environ.get('DRIVEIN_IP', '100.94.163.117'),
|
||||
'port': int(os.environ.get('DRIVEIN_PORT', '8547'))
|
||||
}
|
||||
|
||||
|
||||
# Global config
|
||||
CONFIG = load_config()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
print(f"\nNFC Network Client - Smart Auto-Connect v{SMART_CLIENT_VERSION}")
|
||||
print(f"Based on NFC Network Client v{BASE_VERSION}")
|
||||
print("="*70)
|
||||
|
||||
print_banner()
|
||||
|
||||
# Create Tkinter root
|
||||
root = tk.Tk()
|
||||
|
||||
try:
|
||||
# Create and run smart client
|
||||
app = SmartAutoConnectClient(
|
||||
root,
|
||||
device_name=CONFIG['device_name'],
|
||||
fallback_ip=CONFIG['fallback_ip'],
|
||||
port=CONFIG['port']
|
||||
)
|
||||
|
||||
print("Client started successfully!")
|
||||
print("Window should appear shortly...")
|
||||
print("="*70 + "\n")
|
||||
|
||||
root.mainloop()
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nERROR: Client failed to start")
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
finally:
|
||||
if hasattr(app, 'keyboard_listener') and app.keyboard_listener:
|
||||
app.keyboard_listener.stop()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user