vindree

Second Keyboard w/ Gamescope, best version

May 22nd, 2026 (edited)
14,211
0
Never
11
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.74 KB | Gaming | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. VTTGrabber - Working Version with Driving + Fixed TAB
  4. """
  5.  
  6. import asyncio
  7. import json
  8. import evdev
  9. from evdev import ecodes
  10. import websockets
  11. import subprocess
  12. import sys
  13. import time
  14. import signal
  15. import urllib.request
  16.  
  17. SECONDARY_MOUSE = "/dev/input/by-id/usb-30fa_USB_OPTICAL_MOUSE-event-mouse"
  18. SECONDARY_KBD = "/dev/input/by-id/usb-Usb_KeyBoard_Usb_KeyBoard-event-kbd"
  19. CDP_URL = "ws://localhost:9222/devtools/browser"
  20. SCREEN_WIDTH = 1920
  21. SCREEN_HEIGHT = 1080
  22.  
  23. class VTTGrabber:
  24.     def __init__(self):
  25.         self.mouse = None
  26.         self.keyboard = None
  27.         self.ws = None
  28.         self.target_id = None
  29.         self.mouse_x = SCREEN_WIDTH // 2
  30.         self.mouse_y = SCREEN_HEIGHT // 2
  31.         self.running = True
  32.         self.scroll_accumulator = 0
  33.         self.msg_counter = 0
  34.        
  35.     async def find_chromium_target(self):
  36.         try:
  37.             req = urllib.request.Request("http://localhost:9222/json", headers={"Host": "localhost:9222"})
  38.             with urllib.request.urlopen(req, timeout=5) as response:
  39.                 targets = json.loads(response.read().decode())
  40.             for target in targets:
  41.                 if target["type"] == "page":
  42.                     self.target_id = target["id"]
  43.                     print(f"Found target: {target.get('title', 'Unknown')}")
  44.                     return target["webSocketDebuggerUrl"]
  45.         except Exception as e:
  46.             print(f"Error: {e}")
  47.             return None
  48.            
  49.     async def connect_cdp(self):
  50.         ws_url = await self.find_chromium_target()
  51.         if not ws_url:
  52.             raise ConnectionError("No Chromium target")
  53.         self.ws = await websockets.connect(ws_url, ping_interval=None)
  54.         print("Connected to CDP")
  55.         await self.send_cdp("Input.enable", {})
  56.         await self.send_cdp("Runtime.enable", {})
  57.         asyncio.create_task(self._drain_ws())
  58.        
  59.     async def _drain_ws(self):
  60.         try:
  61.             while self.running and self.ws:
  62.                 try:
  63.                     msg = await asyncio.wait_for(self.ws.recv(), timeout=0.1)
  64.                     data = json.loads(msg)
  65.                     if "error" in data:
  66.                         print(f"CDP Error: {data['error']}")
  67.                 except asyncio.TimeoutError:
  68.                     continue
  69.                 except Exception:
  70.                     break
  71.         except Exception:
  72.             pass
  73.            
  74.     async def send_cdp(self, method, params):
  75.         if not self.ws:
  76.             return
  77.         self.msg_counter += 1
  78.         msg = {"id": self.msg_counter, "method": method, "params": params}
  79.         try:
  80.             await self.ws.send(json.dumps(msg))
  81.         except Exception as e:
  82.             print(f"CDP error: {e}")
  83.        
  84.     async def setup_input_devices(self):
  85.         try:
  86.             self.mouse = evdev.InputDevice(SECONDARY_MOUSE)
  87.             self.keyboard = evdev.InputDevice(SECONDARY_KBD)
  88.             print(f"Mouse: {self.mouse.name}")
  89.             print(f"Keyboard: {self.keyboard.name}")
  90.             self.mouse.grab()
  91.             self.keyboard.grab()
  92.             print("Devices grabbed")
  93.         except PermissionError:
  94.             print("Run with sudo")
  95.             sys.exit(1)
  96.         except Exception as e:
  97.             print(f"Error: {e}")
  98.             sys.exit(1)
  99.    
  100.     async def inject_cursor(self):
  101.         cursor_script = """
  102.        (function() {
  103.            if (document.getElementById('vtt-cursor')) return;
  104.            const c = document.createElement('div');
  105.            c.id = 'vtt-cursor';
  106.            c.style.cssText = 'position:fixed;width:20px;height:20px;background:#ff0055;border:2px solid white;border-radius:50%;pointer-events:none;z-index:999999;left:0;top:0;transform:translate(960px,540px);';
  107.            document.body.appendChild(c);
  108.        })();
  109.        """
  110.         await self.send_cdp("Runtime.evaluate", {"expression": cursor_script})
  111.  
  112.     async def update_cursor_position(self, x, y):
  113.         move_script = f"const c=document.getElementById('vtt-cursor');if(c)c.style.transform='translate({x}px,{y}px)';"
  114.         await self.send_cdp("Runtime.evaluate", {"expression": move_script})
  115.        
  116.     async def route_mouse(self):
  117.         print("Mouse routing started")
  118.         async for event in self.mouse.async_read_loop():
  119.             if not self.running:
  120.                 break
  121.             if event.type == ecodes.EV_REL:
  122.                 if event.code == ecodes.REL_X:
  123.                     self.mouse_x = max(0, min(SCREEN_WIDTH - 1, self.mouse_x + event.value))
  124.                 elif event.code == ecodes.REL_Y:
  125.                     self.mouse_y = max(0, min(SCREEN_HEIGHT - 1, self.mouse_y + event.value))
  126.                 await self.send_cdp("Input.dispatchMouseEvent", {
  127.                     "type": "mouseMoved", "x": self.mouse_x, "y": self.mouse_y, "modifiers": 0
  128.                 })
  129.                 await self.update_cursor_position(self.mouse_x, self.mouse_y)
  130.             elif event.type == ecodes.EV_REL and event.code == ecodes.REL_WHEEL:
  131.                 self.scroll_accumulator += event.value
  132.                 if abs(self.scroll_accumulator) >= 1:
  133.                     await self.send_cdp("Input.dispatchMouseEvent", {
  134.                         "type": "mouseWheel", "x": self.mouse_x, "y": self.mouse_y,
  135.                         "deltaX": 0, "deltaY": int(self.scroll_accumulator * 100), "modifiers": 0
  136.                     })
  137.                     self.scroll_accumulator = 0
  138.             elif event.type == ecodes.EV_KEY:
  139.                 if event.code in (ecodes.BTN_LEFT, ecodes.BTN_RIGHT, ecodes.BTN_MIDDLE):
  140.                     button = {ecodes.BTN_LEFT: "left", ecodes.BTN_RIGHT: "right", ecodes.BTN_MIDDLE: "middle"}[event.code]
  141.                     action = "mousePressed" if event.value == 1 else "mouseReleased"
  142.                     await self.send_cdp("Input.dispatchMouseEvent", {
  143.                         "type": action, "x": self.mouse_x, "y": self.mouse_y,
  144.                         "button": button, "clickCount": 1, "modifiers": 0
  145.                     })
  146.                    
  147.     async def route_keyboard(self):
  148.         print("Keyboard routing started")
  149.        
  150.         # Full key mapping (base_key, code, shifted_key)
  151.         key_map = {
  152.             ecodes.KEY_A: ("a", "KeyA", "A"), ecodes.KEY_B: ("b", "KeyB", "B"),
  153.             ecodes.KEY_C: ("c", "KeyC", "C"), ecodes.KEY_D: ("d", "KeyD", "D"),
  154.             ecodes.KEY_E: ("e", "KeyE", "E"), ecodes.KEY_F: ("f", "KeyF", "F"),
  155.             ecodes.KEY_G: ("g", "KeyG", "G"), ecodes.KEY_H: ("h", "KeyH", "H"),
  156.             ecodes.KEY_I: ("i", "KeyI", "I"), ecodes.KEY_J: ("j", "KeyJ", "J"),
  157.             ecodes.KEY_K: ("k", "KeyK", "K"), ecodes.KEY_L: ("l", "KeyL", "L"),
  158.             ecodes.KEY_M: ("m", "KeyM", "M"), ecodes.KEY_N: ("n", "KeyN", "N"),
  159.             ecodes.KEY_O: ("o", "KeyO", "O"), ecodes.KEY_P: ("p", "KeyP", "P"),
  160.             ecodes.KEY_Q: ("q", "KeyQ", "Q"), ecodes.KEY_R: ("r", "KeyR", "R"),
  161.             ecodes.KEY_S: ("s", "KeyS", "S"), ecodes.KEY_T: ("t", "KeyT", "T"),
  162.             ecodes.KEY_U: ("u", "KeyU", "U"), ecodes.KEY_V: ("v", "KeyV", "V"),
  163.             ecodes.KEY_W: ("w", "KeyW", "W"), ecodes.KEY_X: ("x", "KeyX", "X"),
  164.             ecodes.KEY_Y: ("y", "KeyY", "Y"), ecodes.KEY_Z: ("z", "KeyZ", "Z"),
  165.             ecodes.KEY_1: ("1", "Digit1", "!"), ecodes.KEY_2: ("2", "Digit2", "@"),
  166.             ecodes.KEY_3: ("3", "Digit3", "#"), ecodes.KEY_4: ("4", "Digit4", "$"),
  167.             ecodes.KEY_5: ("5", "Digit5", "%"), ecodes.KEY_6: ("6", "Digit6", "^"),
  168.             ecodes.KEY_7: ("7", "Digit7", "&"), ecodes.KEY_8: ("8", "Digit8", "*"),
  169.             ecodes.KEY_9: ("9", "Digit9", "("), ecodes.KEY_0: ("0", "Digit0", ")"),
  170.             ecodes.KEY_SPACE: (" ", "Space", " "),
  171.             ecodes.KEY_ENTER: ("Enter", "Enter", "Enter"),
  172.             ecodes.KEY_BACKSPACE: ("Backspace", "Backspace", "Backspace"),
  173.             ecodes.KEY_ESC: ("Escape", "Escape", "Escape"),
  174.             ecodes.KEY_LEFT: ("ArrowLeft", "ArrowLeft", "ArrowLeft"),
  175.             ecodes.KEY_RIGHT: ("ArrowRight", "ArrowRight", "ArrowRight"),
  176.             ecodes.KEY_UP: ("ArrowUp", "ArrowUp", "ArrowUp"),
  177.             ecodes.KEY_DOWN: ("ArrowDown", "ArrowDown", "ArrowDown"),
  178.             ecodes.KEY_MINUS: ("-", "Minus", "_"),
  179.             ecodes.KEY_EQUAL: ("=", "Equal", "+"),
  180.             ecodes.KEY_LEFTBRACE: ("[", "BracketLeft", "{"),
  181.             ecodes.KEY_RIGHTBRACE: ("]", "BracketRight", "}"),
  182.             ecodes.KEY_BACKSLASH: ("\\", "Backslash", "|"),
  183.             ecodes.KEY_SEMICOLON: (";", "Semicolon", ":"),
  184.             ecodes.KEY_APOSTROPHE: ("'", "Quote", '"'),
  185.             ecodes.KEY_GRAVE: ("`", "Backquote", "~"),
  186.             ecodes.KEY_COMMA: (",", "Comma", "<"),
  187.             ecodes.KEY_DOT: (".", "Period", ">"),
  188.             ecodes.KEY_SLASH: ("/", "Slash", "?"),
  189.         }
  190.        
  191.         shift_pressed = False
  192.        
  193.         async for event in self.keyboard.async_read_loop():
  194.             if not self.running:
  195.                 break
  196.                
  197.             if event.type == ecodes.EV_KEY:
  198.                 keycode = event.code
  199.                 value = event.value
  200.                
  201.                 # Track shift
  202.                 if keycode in (ecodes.KEY_LEFTSHIFT, ecodes.KEY_RIGHTSHIFT):
  203.                     shift_pressed = value > 0
  204.                
  205.                 # Handle TAB via JavaScript injection (cycles Foundry elements)
  206.                 if keycode == ecodes.KEY_TAB and value == 1:
  207.                     js = """
  208.                    (function() {
  209.                        const evt = new KeyboardEvent('keydown', {
  210.                            key: 'Tab',
  211.                            code: 'Tab',
  212.                            keyCode: 9,
  213.                            which: 9,
  214.                            bubbles: true,
  215.                            cancelable: true
  216.                        });
  217.                        (document.activeElement || document.body).dispatchEvent(evt);
  218.                    })();
  219.                    """
  220.                     await self.send_cdp("Runtime.evaluate", {"expression": js})
  221.                     continue
  222.                
  223.                 if keycode in key_map:
  224.                     base_key, code, shifted_key = key_map[keycode]
  225.                     key = shifted_key if shift_pressed else base_key
  226.                    
  227.                     if value == 1:  # Key down
  228.                         await self.send_cdp("Input.dispatchKeyEvent", {
  229.                             "type": "keyDown", "key": key, "code": code, "modifiers": 0
  230.                         })
  231.                     elif value == 0:  # Key up
  232.                         await self.send_cdp("Input.dispatchKeyEvent", {
  233.                             "type": "keyUp", "key": key, "code": code, "modifiers": 0
  234.                         })
  235.                                                                        
  236.     async def run(self):
  237.         print("VTTGrabber starting...")
  238.         await self.setup_input_devices()
  239.         await self.connect_cdp()
  240.         print("Connected!")
  241.         await self.inject_cursor()
  242.         await asyncio.gather(self.route_mouse(), self.route_keyboard())
  243.        
  244.     def cleanup(self):
  245.         self.running = False
  246.         if self.mouse:
  247.             try: self.mouse.ungrab()
  248.             except: pass
  249.         if self.keyboard:
  250.             try: self.keyboard.ungrab()
  251.             except: pass
  252.         print("Released devices")
  253.  
  254. if __name__ == "__main__":
  255.     grabber = VTTGrabber()
  256.     def signal_handler(sig, frame):
  257.         grabber.cleanup()
  258.         sys.exit(0)
  259.     signal.signal(signal.SIGINT, signal_handler)
  260.     try:
  261.         asyncio.run(grabber.run())
  262.     except KeyboardInterrupt:
  263.         grabber.cleanup()
  264.     except Exception as e:
  265.         print(f"Error: {e}")
  266.         grabber.cleanup()
Advertisement