|
| 1 | +import argparse |
| 2 | +import json |
1 | 3 | import threading |
| 4 | +import time |
2 | 5 | import xml.etree.ElementTree as ET |
| 6 | +from http.server import HTTPServer, BaseHTTPRequestHandler |
| 7 | + |
3 | 8 | import tkinter as tk |
4 | 9 | from tkinter import ttk, scrolledtext |
5 | 10 | import os |
6 | 11 | import pyperclip # Add this import for clipboard functionality |
7 | 12 |
|
8 | 13 | from inspector import WindowsInspector, Authenticate, inspect |
9 | 14 |
|
| 15 | +# Server-only mode: in-memory latest inspection for GET /latest |
| 16 | +LATEST_RESULT = {"xml": "", "paths": [], "window_name": "", "log": [], "captured_at": 0} |
| 17 | +LATEST_LOCK = threading.Lock() |
| 18 | +HTTP_PORT = 8765 |
| 19 | + |
| 20 | + |
| 21 | +def _update_latest(xml_str: str, paths: list, window_name: str, log: list): |
| 22 | + with LATEST_LOCK: |
| 23 | + LATEST_RESULT["xml"] = xml_str or "" |
| 24 | + LATEST_RESULT["paths"] = list(paths) if paths else [] |
| 25 | + LATEST_RESULT["window_name"] = window_name or "" |
| 26 | + LATEST_RESULT["log"] = list(log) if log else [] |
| 27 | + LATEST_RESULT["captured_at"] = time.time() |
| 28 | + |
| 29 | + |
| 30 | +def _get_latest(): |
| 31 | + with LATEST_LOCK: |
| 32 | + return { |
| 33 | + "xml": LATEST_RESULT["xml"], |
| 34 | + "paths": LATEST_RESULT["paths"], |
| 35 | + "window_name": LATEST_RESULT["window_name"], |
| 36 | + "log": LATEST_RESULT["log"], |
| 37 | + "captured_at": LATEST_RESULT["captured_at"], |
| 38 | + } |
| 39 | + |
| 40 | + |
| 41 | +class InspectorHTTPHandler(BaseHTTPRequestHandler): |
| 42 | + """Serves GET /latest with CORS for the web Inspector panel.""" |
| 43 | + |
| 44 | + def do_GET(self): |
| 45 | + if self.path == "/latest" or self.path == "/latest/": |
| 46 | + body = json.dumps(_get_latest()).encode("utf-8") |
| 47 | + self.send_response(200) |
| 48 | + self.send_header("Content-Type", "application/json; charset=utf-8") |
| 49 | + self.send_header("Content-Length", str(len(body))) |
| 50 | + self.send_header("Access-Control-Allow-Origin", "*") |
| 51 | + self.end_headers() |
| 52 | + self.wfile.write(body) |
| 53 | + else: |
| 54 | + self.send_response(404) |
| 55 | + self.end_headers() |
| 56 | + |
| 57 | + def log_message(self, format, *args): |
| 58 | + pass # quiet by default in server mode |
| 59 | + |
| 60 | + |
| 61 | +def run_server_only(): |
| 62 | + """Run without Tkinter: HTTP server on port 8765 + hover+Ctrl inspection loop.""" |
| 63 | + inspector = WindowsInspector() |
| 64 | + log_lines = [] |
| 65 | + |
| 66 | + def log(msg: str): |
| 67 | + log_lines.append(msg) |
| 68 | + print(msg) |
| 69 | + |
| 70 | + def inspection_loop(): |
| 71 | + while True: |
| 72 | + log("Hover over the element you want to inspect and press Ctrl...") |
| 73 | + try: |
| 74 | + x, y = inspect() |
| 75 | + log(f"Captured at x={x}, y={y}") |
| 76 | + cleanup = inspector.inspect_element(x, y) |
| 77 | + try: |
| 78 | + root = ET.fromstring(inspector.xml_str) |
| 79 | + path_list = [{"path": p["path"], "area": p.get("area", "")} for p in inspector.paths] |
| 80 | + _update_latest(inspector.xml_str, path_list, inspector.window_name, list(log_lines)) |
| 81 | + except ET.ParseError: |
| 82 | + log("Failed to parse XML from inspector.") |
| 83 | + finally: |
| 84 | + cleanup() |
| 85 | + except Exception as e: |
| 86 | + log(f"Inspection error: {e}") |
| 87 | + |
| 88 | + server = HTTPServer(("", HTTP_PORT), InspectorHTTPHandler) |
| 89 | + server_thread = threading.Thread(target=server.serve_forever, daemon=True) |
| 90 | + server_thread.start() |
| 91 | + log(f"Inspector server listening on http://localhost:{HTTP_PORT}/latest (no UI mode)") |
| 92 | + |
| 93 | + inspection_loop() |
| 94 | + |
10 | 95 |
|
11 | 96 | class InspectorGUI(tk.Tk): |
12 | 97 | def __init__(self): |
@@ -204,6 +289,17 @@ def _inspect_element(self): |
204 | 289 | self.create_path_section(text, path["path"]) |
205 | 290 |
|
206 | 291 |
|
207 | | -if __name__ == '__main__': |
208 | | - app = InspectorGUI() |
209 | | - app.mainloop() |
| 292 | +if __name__ == "__main__": |
| 293 | + parser = argparse.ArgumentParser(description="ZeuZ Windows Inspector") |
| 294 | + parser.add_argument( |
| 295 | + "--no-ui", |
| 296 | + action="store_true", |
| 297 | + help="Run without GUI: start HTTP server on port 8765 and serve GET /latest for the web Inspector panel. Use hover+Ctrl in the terminal to capture.", |
| 298 | + ) |
| 299 | + args = parser.parse_args() |
| 300 | + |
| 301 | + if args.no_ui: |
| 302 | + run_server_only() |
| 303 | + else: |
| 304 | + app = InspectorGUI() |
| 305 | + app.mainloop() |
0 commit comments