├── README.md ├── linux_keyboard.py ├── ps2.py ├── ps2recv.py ├── ps2tn.py └── pygame_keyboard_mouse.py /README.md: -------------------------------------------------------------------------------- 1 | # ESP32->PS/2 2 | 3 | Micropython ESP32 code that listens at TCP port and 4 | retransmits received packets using PS/2 protocol. 5 | 6 | Intended use is to emulate PS/2 keyboard and mouse. 7 | 8 | python code receives keyboard events, 9 | converts them to "PS/2 SET2" scancodes and 10 | sends over TCP to ESP32. 11 | 12 | PS/2 SET2 is standard power-on default scancode setting 13 | for PS/2 keyboards. 14 | 15 | Currently this code doesn't support bidirectional PS/2 so 16 | emulated PS/2 keyboard and mouse port can only send but 17 | can't receive commands from PS/2 for e.g. blink keyboard LEDs, 18 | change scancode set or enable mouse wheel. 19 | 20 | # ESP32 PS/2 pins 21 | 22 | This is default pinout recommended for ULX3S. 23 | Edit "ps2tn.py" or "ps2recv.py" to use other ESP32 pins. 24 | 25 | assign ps2_keyboard_clk = gp[11]; // wifi_gpio26 26 | assign ps2_keyboard_data = gn[11]; // wifi_gpio25 27 | assign ps2_mouse_clk = wifi_gpio17; 28 | assign ps2_mouse_data = wifi_gpio16; 29 | 30 | # telnet input 31 | 32 | telnet input is convenient and readily available on most platforms. 33 | Requires no windowing support. 34 | Supports only keyboard (typing), doesnt support mouse. 35 | 36 | ESP32: upload "ps2tn.py" and "ps2.py" 37 | 38 | import ps2tn 39 | 40 | telnet to ESP32 and start typing. "ps2tn" should echo typed chars. 41 | 42 | telnet 192.168.4.1 43 | 44 | # pygame input 45 | 46 | pygame input is available on most platforms. 47 | Requires windowing support. 48 | Supports keyboard and mouse. 49 | 50 | ESP32: upload "ps2recv.py" and "ps2.py" 51 | 52 | import ps2recv 53 | 54 | host: edit "pygame_keyboard_mouse.py" to set IP address of ESP32 and mouse type 55 | (wheel/no_wheel). pygame will open a window that will grab 56 | mouse and keyboard: 57 | 58 | ./pygame_keyboard_mouse.py 59 | 60 | Press "PAUSE" key to quit. 61 | 62 | # linux input 63 | 64 | linux input is available only on linux. 65 | Requires no windowing support. 66 | Supports keyboard (will support mouse later). 67 | 68 | ESP32: upload "ps2recv.py" and "ps2.py" 69 | 70 | import ps2recv 71 | 72 | for linux input, you need to give user "rw" access to "/dev/input/eventX" 73 | device which represents your keyboard: 74 | 75 | lsinput 76 | chmod a+rw /dev/input/event3 77 | 78 | and then you should maybe edit "linux_keyboard.py" to place keyboard name 79 | (you have seen it using "lsinput") and run the host-side input client 80 | as normal user: 81 | 82 | ./linux_keyboard.py 83 | 84 | # TODO 85 | 86 | [x] E0-scancodes 87 | [x] telnet interface 88 | [x] unify keyboard mouse 89 | [ ] linux input mouse support 90 | [ ] joystick 91 | [x] mouse/keyboard visual feedback (local) 92 | [ ] visual echo in pygame 93 | [ ] pygame->ps2recv: mouse and keypress together will freeze 94 | -------------------------------------------------------------------------------- /linux_keyboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # AUTHOR=EMARD 4 | # LICENSE=GPL 5 | 6 | # use ps2recv.py on ESP32 7 | 8 | # Reads linux mouse input device (evdev). 9 | # Converts mouse events to ps2 serial commands. 10 | # Currently it can only move mouse pointer. 11 | # Mouse clicks are received but not supported yet. 12 | 13 | # lsinput 14 | # /dev/input/event7 15 | # name : "Logitech USB-PS/2 Optical Mouse" 16 | # chmod a+rw /dev/input/event7 17 | 18 | import evdev 19 | import serial 20 | import struct 21 | import socket 22 | 23 | # fix packet with header, escapes, trailer 24 | def escape(p): 25 | retval = struct.pack("BB", 0, 0) 26 | for char in p: 27 | if char == 0 or char == 0x5C: 28 | retval += struct.pack("B", 0x5C) 29 | retval += struct.pack("B", char) 30 | retval += struct.pack("B", 0) 31 | return retval 32 | 33 | def pointer(x,y): 34 | rgtr = (y & 0xFFF)*(2**12) + (x & 0xFFF) 35 | return struct.pack(">BBI", 0x15, 3, rgtr) 36 | 37 | def mouse_report(dx,dy,dz,btn_left,btn_middle,btn_right): 38 | return struct.pack(">BBBBBB", 0x0F, 3, (-dz) & 0xFF, (-dy) & 0xFF, dx & 0xFF, (btn_left & 1) + (btn_right & 1)*(2**1) + (btn_middle & 1)*(2**2)) 39 | 40 | def print_packet(x): 41 | for c in x: 42 | print("%02X" % (c), end=''); 43 | print("") 44 | 45 | if __name__ == '__main__': 46 | # this string will search for mouse in list of evdev inputs 47 | # Usually this should match a part of USB mouse device name 48 | keyboard_input_name = "TypeM" 49 | # ps2 network host or ip 50 | tcp_host = "192.168.48.181" 51 | tcp_port = 3252 # use UDP, not serial port 52 | 53 | # from http://www.vetra.com/scancodes.html 54 | keymap_ps2_scan2 = { 55 | 'KEY_GRAVE' : 0x0E, 56 | 'KEY_1' : 0x16, 57 | 'KEY_2' : 0x1E, 58 | 'KEY_3' : 0x26, 59 | 'KEY_4' : 0x25, 60 | 'KEY_5' : 0x2E, 61 | 'KEY_6' : 0x36, 62 | 'KEY_7' : 0x3D, 63 | 'KEY_8' : 0x3E, 64 | 'KEY_9' : 0x46, 65 | 'KEY_0' : 0x45, 66 | 'KEY_MINUS' : 0x4E, 67 | 'KEY_EQUAL' : 0x55, 68 | 'KEY_BACKSPACE' : 0x66, 69 | 'KEY_TAB' : 0x0D, 70 | 'KEY_Q' : 0x15, 71 | 'KEY_W' : 0x1D, 72 | 'KEY_E' : 0x24, 73 | 'KEY_R' : 0x2D, 74 | 'KEY_T' : 0x2C, 75 | 'KEY_Y' : 0x35, 76 | 'KEY_U' : 0x3C, 77 | 'KEY_I' : 0x43, 78 | 'KEY_O' : 0x44, 79 | 'KEY_P' : 0x4D, 80 | 'KEY_LEFTBRACE' : 0x54, 81 | 'KEY_RIGHTBRACE': 0x5B, 82 | 'KEY_CAPSLOCK' : 0x58, 83 | 'KEY_A' : 0x1C, 84 | 'KEY_S' : 0x1B, 85 | 'KEY_D' : 0x23, 86 | 'KEY_F' : 0x2B, 87 | 'KEY_G' : 0x34, 88 | 'KEY_H' : 0x33, 89 | 'KEY_J' : 0x3B, 90 | 'KEY_K' : 0x42, 91 | 'KEY_L' : 0x4B, 92 | 'KEY_SEMICOLON' : 0x4C, 93 | 'KEY_APOSTROPHE': 0x52, 94 | 'KEY_ENTER' : 0x5A, 95 | 'KEY_LEFTSHIFT' : 0x12, 96 | 'KEY_Z' : 0x1A, 97 | 'KEY_X' : 0x22, 98 | 'KEY_C' : 0x21, 99 | 'KEY_V' : 0x2A, 100 | 'KEY_B' : 0x32, 101 | 'KEY_N' : 0x31, 102 | 'KEY_M' : 0x3A, 103 | 'KEY_COMMA' : 0x41, 104 | 'KEY_DOT' : 0x49, 105 | 'KEY_SLASH' : 0x4A, 106 | 'KEY_RIGHTSHIFT': 0x59, 107 | 'KEY_LEFTCTRL' : 0x14, 108 | 'KEY_LEFTALT' : 0x11, 109 | 'KEY_SPACE' : 0x29, 110 | 'KEY_RIGHTALT' :(0x11 | 0x80), 111 | 'KEY_RIGHTCTRL' :(0x14 | 0x80), 112 | 'KEY_INSERT' :(0x70 | 0x80), 113 | 'KEY_DELETE' :(0x71 | 0x80), 114 | 'KEY_HOME' :(0x6C | 0x80), 115 | 'KEY_END' :(0x69 | 0x80), 116 | 'KEY_PAGEUP' :(0x7D | 0x80), 117 | 'KEY_PAGEDOWN' :(0x7A | 0x80), 118 | 'KEY_UP' :(0x75 | 0x80), 119 | 'KEY_DOWN' :(0x72 | 0x80), 120 | 'KEY_LEFT' :(0x6B | 0x80), 121 | 'KEY_RIGHT' :(0x74 | 0x80), 122 | 'KEY_NUMLOCK' :(0x77 | 0x80), 123 | 'KEY_KP7' : 0x6C, 124 | 'KEY_KP4' : 0x6B, 125 | 'KEY_KP1' : 0x69, 126 | 'KEY_KPSLASH' :(0x4A | 0x80), 127 | 'KEY_KP8' : 0x75, 128 | 'KEY_KP5' : 0x73, 129 | 'KEY_KP2' : 0x72, 130 | 'KEY_KP0' : 0x70, 131 | 'KEY_KPASTERISK': 0x7C, 132 | 'KEY_KP9' : 0x7D, 133 | 'KEY_KP6' : 0x74, 134 | 'KEY_KP3' : 0x7A, 135 | 'KEY_KPPLUS' : 0x79, 136 | 'KEY_KPENTER' :(0x5A | 0x80), 137 | 'KEY_ESC' : 0x76, 138 | 'KEY_F1' : 0x05, 139 | 'KEY_F2' : 0x06, 140 | 'KEY_F3' : 0x04, 141 | 'KEY_F4' : 0x0C, 142 | 'KEY_F5' : 0x03, 143 | 'KEY_F6' : 0x0B, 144 | 'KEY_F7' : 0x83, 145 | 'KEY_F8' : 0x0A, 146 | 'KEY_F9' : 0x01, 147 | 'KEY_F10' : 0x09, 148 | 'KEY_F11' : 0x78, 149 | 'KEY_F12' : 0x07, 150 | 'KEY_SCROLLLOCK': 0x7E, 151 | 'KEY_BACKSLASH' : 0x5D, 152 | } 153 | 154 | # convert keys to input events evdev.ecodes.ecodes[key] 155 | event2ps2 = { } 156 | for key in keymap_ps2_scan2: 157 | event2ps2[evdev.ecodes.ecodes[key]] = keymap_ps2_scan2[key] 158 | 159 | X = 0 160 | Y = 0 161 | Z = 0 162 | DX = 0 163 | DY = 0 164 | DZ = 0 165 | BTN_LEFT = 0 166 | BTN_RIGHT = 0 167 | BTN_MIDDLE = 0 168 | TOUCH = 0 169 | 170 | DEVICE = None 171 | 172 | DEVICES = [evdev.InputDevice(fn) for fn in evdev.list_devices()] 173 | 174 | for d in DEVICES: 175 | if keyboard_input_name in d.name: 176 | DEVICE = d 177 | print('Found %s at %s...' % (d.name, d.path)) 178 | break 179 | 180 | if DEVICE: 181 | ps2_tcp=socket.create_connection((tcp_host, tcp_port)) 182 | print("Sending keyboard events to %s:%s" % (tcp_host,tcp_port)) 183 | ps2_tcp.sendall(bytearray([0xAA, 0xFA])) # keyboard sends 0xAA after being plugged 184 | for event in DEVICE.read_loop(): 185 | if event.type == evdev.ecodes.EV_REL and False: # TODO support mouse properly 186 | if event.code == evdev.ecodes.REL_X: 187 | DX = event.value 188 | X += DX 189 | if event.code == evdev.ecodes.REL_Y: 190 | DY = event.value 191 | Y += DY 192 | if event.code == evdev.ecodes.REL_WHEEL: 193 | DZ = event.value 194 | Z += DZ 195 | #print('X=%d Y=%d Z=%d' % (X, Y, Z)) 196 | #packet = pointer(X,Y) 197 | packet = mouse_report(DX,DY,DZ,BTN_LEFT,BTN_MIDDLE,BTN_RIGHT) 198 | DZ = 0 199 | DX = 0 200 | DY = 0 201 | ps2_tcp.sendall(packet) 202 | 203 | if event.type == evdev.ecodes.EV_KEY: 204 | if event.code in event2ps2: 205 | packet = None 206 | code = event2ps2[event.code] 207 | if event.value == 1: # key press 208 | if code & 0x80: 209 | packet = bytearray([ord('K'), 2, 0xE0, code & 0x7F]) 210 | else: 211 | packet = bytearray([ord('K'), 1, code & 0x7F]) 212 | #if event.value == 2: # key autorepeat 213 | # packet = bytearray([event2ps2[event.code]]) 214 | if event.value == 0: # key release 215 | if code & 0x80: 216 | packet = bytearray([ord('K'), 3, 0xE0, 0xF0, code & 0x7F]) 217 | else: 218 | packet = bytearray([ord('K'), 2, 0xF0, code & 0x7F]) 219 | if packet: 220 | ps2_tcp.sendall(packet) 221 | -------------------------------------------------------------------------------- /ps2.py: -------------------------------------------------------------------------------- 1 | # micropython ESP32 2 | # PS/2 protocol emulator (keyboard and mouse, transmit-only) 3 | 4 | # AUTHOR=EMARD 5 | # LICENSE=BSD 6 | 7 | from time import sleep_us 8 | from machine import Pin 9 | from micropython import const 10 | from uctypes import addressof 11 | 12 | class ps2: 13 | def __init__(self, kbd_clk=26, kbd_data=25, mouse_clk=17, mouse_data=16, qbit_us=16, byte_us=150): 14 | self.gpio_kbd_clk = kbd_clk 15 | self.gpio_kbd_data = kbd_data 16 | self.gpio_mouse_clk = mouse_clk 17 | self.gpio_mouse_data = mouse_data 18 | self.keyboard() 19 | self.qbit_us = qbit_us # quarter-bit delay 20 | self.byte_us = byte_us # byte-to-byte delay 21 | 22 | def keyboard(self): 23 | self.ps2_clk = Pin(self.gpio_kbd_clk, Pin.OPEN_DRAIN, Pin.PULL_UP) 24 | self.ps2_data = Pin(self.gpio_kbd_data, Pin.OPEN_DRAIN, Pin.PULL_UP) 25 | self.ps2_clk.on() 26 | self.ps2_data.on() 27 | 28 | def mouse(self): 29 | self.ps2_clk = Pin(self.gpio_mouse_clk, Pin.OPEN_DRAIN, Pin.PULL_UP) 30 | self.ps2_data = Pin(self.gpio_mouse_data, Pin.OPEN_DRAIN, Pin.PULL_UP) 31 | self.ps2_clk.on() 32 | self.ps2_data.on() 33 | 34 | @micropython.viper 35 | def write(self, data): 36 | qbit_us = int(self.qbit_us) 37 | p = ptr8(addressof(data)) 38 | l = int(len(data)) 39 | for i in range(l): 40 | val = p[i] 41 | parity = 1 42 | self.ps2_data.off() 43 | sleep_us(qbit_us) 44 | self.ps2_clk.off() 45 | sleep_us(qbit_us+qbit_us) 46 | self.ps2_clk.on() 47 | sleep_us(qbit_us) 48 | for nf in range(8): 49 | if val & 1: 50 | self.ps2_data.on() 51 | parity ^= 1 52 | else: 53 | self.ps2_data.off() 54 | parity ^= 0 # keep timing the same as above 55 | sleep_us(qbit_us) 56 | self.ps2_clk.off() 57 | val >>= 1 58 | sleep_us(qbit_us+qbit_us) 59 | self.ps2_clk.on() 60 | sleep_us(qbit_us) 61 | if parity: 62 | self.ps2_data.on() 63 | else: 64 | self.ps2_data.off() 65 | sleep_us(qbit_us) 66 | self.ps2_clk.off() 67 | sleep_us(qbit_us+qbit_us) 68 | self.ps2_clk.on() 69 | sleep_us(qbit_us) 70 | self.ps2_data.on() 71 | sleep_us(qbit_us) 72 | self.ps2_clk.off() 73 | sleep_us(qbit_us+qbit_us) 74 | self.ps2_clk.on() 75 | sleep_us(self.byte_us) 76 | -------------------------------------------------------------------------------- /ps2recv.py: -------------------------------------------------------------------------------- 1 | # AUTHOR=EMARD 2 | # LICENSE=BSD 3 | 4 | # PS/2 receiver converts from TCP to PS/2 5 | # use linux_keyboard.py or pygame_mouse.py on host side 6 | # edit ps2port (see below) 7 | 8 | import socket 9 | import network 10 | import uos 11 | from gc import collect 12 | from time import sleep_us 13 | from struct import unpack 14 | from micropython import alloc_emergency_exception_buf 15 | from micropython import const 16 | from uctypes import addressof 17 | import ps2 18 | 19 | ps2port=ps2.ps2( 20 | kbd_clk = 26, # gp[11] 21 | kbd_data = 25, # gn[11] 22 | mouse_clk = 17, # wifi_gpio17 23 | mouse_data = 16, # wifi_gpio16 24 | qbit_us=16, 25 | byte_us=150 26 | ) 27 | 28 | # constant definitions 29 | _SO_REGISTER_HANDLER = const(20) 30 | _COMMAND_TIMEOUT = const(300) 31 | 32 | # Global variables 33 | ps2socket = None 34 | client_list = [] 35 | verbose_l = 0 36 | client_busy = False 37 | 38 | mouse = 0 # global tracker: 0 keyboard, 1 mouse 39 | 40 | class PS2_client: 41 | 42 | def __init__(self, ps2socket): 43 | self.command_client, self.remote_addr = ps2socket.accept() 44 | self.remote_addr = self.remote_addr[0] 45 | self.command_client.settimeout(_COMMAND_TIMEOUT) 46 | log_msg(1, "PS2 Command connection from:", self.remote_addr) 47 | self.command_client.setsockopt(socket.SOL_SOCKET, 48 | _SO_REGISTER_HANDLER, 49 | self.exec_ps2_command) 50 | # simple PS/2 packet parser state 51 | self.state = 0 52 | self.length = 0 53 | self.index = 0 54 | self.wait = 0 # 0 sending, 1 waiting 55 | self.packet = bytearray(256) 56 | 57 | def packet_parser(self, data): 58 | global mouse 59 | for val in data: 60 | if self.state == 0: # K/M/W 61 | if val == 75: # K 62 | if mouse != 0: 63 | mouse = 0 64 | ps2port.keyboard() 65 | self.state = 1 66 | if val == 77: # M 67 | if mouse != 1: 68 | mouse = 1 69 | ps2port.mouse() 70 | self.state = 1 71 | if val == 87: # W 72 | self.wait = 1 73 | self.state = 1 74 | continue 75 | if self.state == 1: # length 76 | self.length = val 77 | self.index = 0 78 | self.state = 2 79 | continue 80 | if self.state == 2: # packet data 81 | self.packet[self.index] = val 82 | self.index += 1 83 | if self.index >= self.length: 84 | if self.wait: 85 | sleep_us(unpack("= level: 121 | print(*args) 122 | 123 | 124 | # close client and remove it from the list 125 | def close_client(cl): 126 | cl.setsockopt(socket.SOL_SOCKET, _SO_REGISTER_HANDLER, None) 127 | cl.close() 128 | for i, client in enumerate(client_list): 129 | if client.command_client == cl: 130 | del client_list[i] 131 | break 132 | 133 | 134 | def accept_ps2_connect(ps2socket): 135 | # Accept new calls for the server 136 | try: 137 | client_list.append(PS2_client(ps2socket)) 138 | except: 139 | log_msg(1, "Attempt to connect failed") 140 | # try at least to reject 141 | try: 142 | temp_client, temp_addr = ps2socket.accept() 143 | temp_client.close() 144 | except: 145 | pass 146 | 147 | 148 | def stop(): 149 | global ps2socket 150 | global client_list 151 | global client_busy 152 | global ps2port 153 | 154 | for client in client_list: 155 | client.command_client.setsockopt(socket.SOL_SOCKET, 156 | _SO_REGISTER_HANDLER, None) 157 | client.command_client.close() 158 | del client_list 159 | client_list = [] 160 | client_busy = False 161 | if ps2socket is not None: 162 | ps2socket.setsockopt(socket.SOL_SOCKET, _SO_REGISTER_HANDLER, None) 163 | ps2socket.close() 164 | del ps2port 165 | 166 | 167 | # start listening for ftp connections on port 21 168 | def start(port=3252, verbose=0, splash=True): 169 | global ps2socket 170 | global verbose_l 171 | global client_list 172 | global client_busy 173 | global ps2port 174 | 175 | 176 | alloc_emergency_exception_buf(100) 177 | verbose_l = verbose 178 | client_list = [] 179 | client_busy = False 180 | 181 | ps2socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 182 | ps2socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 183 | ps2socket.bind(('0.0.0.0', port)) 184 | ps2socket.listen(0) 185 | ps2socket.setsockopt(socket.SOL_SOCKET, 186 | _SO_REGISTER_HANDLER, accept_ps2_connect) 187 | 188 | 189 | def restart(port=3252, verbose=0, splash=True): 190 | stop() 191 | sleep_us(200000) 192 | start(port, verbose, splash) 193 | 194 | 195 | start(splash=True) 196 | collect() 197 | -------------------------------------------------------------------------------- /ps2tn.py: -------------------------------------------------------------------------------- 1 | # AUTHOR=EMARD 2 | # LICENSE=BSD 3 | 4 | # telnet to ESP32 and type 5 | # keystrokes should be converted to PS/2 signals 6 | 7 | import socket 8 | import network 9 | import uos 10 | from gc import collect 11 | from time import sleep_us, localtime 12 | from micropython import alloc_emergency_exception_buf 13 | from micropython import const 14 | from uctypes import addressof 15 | import ps2 16 | 17 | ps2port=ps2.ps2(kbd_clk=26,kbd_data=25,qbit_us=16,byte_us=150) 18 | 19 | # constant definitions 20 | _SO_REGISTER_HANDLER = const(20) 21 | _COMMAND_TIMEOUT = const(300) 22 | 23 | # Global variables 24 | ps2socket = None 25 | client_list = [] 26 | verbose_l = 0 27 | client_busy = False 28 | 29 | # ASCII to PS/2 SET2 scancode conversion table 30 | # from http://www.vetra.com/scancodes.html 31 | asc2scan = { 32 | '`' : bytearray(b'\x0E\xF0\x0E'), '~' : bytearray(b'\x12\x0E\xF0\x0E\xF0\x12'), 33 | '1' : bytearray(b'\x16\xF0\x16'), '!' : bytearray(b'\x12\x16\xF0\x16\xF0\x12'), 34 | '2' : bytearray(b'\x1E\xF0\x1E'), '@' : bytearray(b'\x12\x1E\xF0\x1E\xF0\x12'), 35 | '3' : bytearray(b'\x26\xF0\x26'), '#' : bytearray(b'\x12\x26\xF0\x26\xF0\x12'), 36 | '4' : bytearray(b'\x25\xF0\x25'), '$' : bytearray(b'\x12\x25\xF0\x25\xF0\x12'), 37 | '5' : bytearray(b'\x2E\xF0\x2E'), '%' : bytearray(b'\x12\x2E\xF0\x2E\xF0\x12'), 38 | '6' : bytearray(b'\x36\xF0\x36'), '^' : bytearray(b'\x12\x36\xF0\x36\xF0\x12'), 39 | '7' : bytearray(b'\x3D\xF0\x3D'), '&' : bytearray(b'\x12\x3D\xF0\x3D\xF0\x12'), 40 | '8' : bytearray(b'\x3E\xF0\x3E'), '*' : bytearray(b'\x12\x3E\xF0\x3E\xF0\x12'), 41 | '9' : bytearray(b'\x46\xF0\x46'), '(' : bytearray(b'\x12\x46\xF0\x46\xF0\x12'), 42 | '0' : bytearray(b'\x45\xF0\x45'), ')' : bytearray(b'\x12\x45\xF0\x45\xF0\x12'), 43 | '-' : bytearray(b'\x4E\xF0\x4E'), '_' : bytearray(b'\x12\x4E\xF0\x4E\xF0\x12'), 44 | '=' : bytearray(b'\x55\xF0\x55'), '+' : bytearray(b'\x12\x55\xF0\x55\xF0\x12'), 45 | '\x7F': bytearray(b'\x66\xF0\x66'),# BACKSPACE 46 | '\t' : bytearray(b'\x0D\xF0\x0D'),# TAB 47 | 'q' : bytearray(b'\x15\xF0\x15'), 48 | 'w' : bytearray(b'\x1D\xF0\x1D'), 49 | 'e' : bytearray(b'\x24\xF0\x24'), 50 | 'r' : bytearray(b'\x2D\xF0\x2D'), 51 | 't' : bytearray(b'\x2C\xF0\x2C'), 52 | 'y' : bytearray(b'\x35\xF0\x35'), 53 | 'u' : bytearray(b'\x3C\xF0\x3C'), 54 | 'i' : bytearray(b'\x43\xF0\x43'), 55 | 'o' : bytearray(b'\x44\xF0\x44'), 56 | 'p' : bytearray(b'\x4D\xF0\x4D'), 57 | '[' : bytearray(b'\x54\xF0\x54'), '{' : bytearray(b'\x12\x54\xF0\x54\xF0\x12'), 58 | u'š' : bytearray(b'\x54\xF0\x54'), u'Š' : bytearray(b'\x12\x54\xF0\x54\xF0\x12'), 59 | ']' : bytearray(b'\x5B\xF0\x5B'), '}' : bytearray(b'\x12\x5B\xF0\x5B\xF0\x12'), 60 | u'đ' : bytearray(b'\x5B\xF0\x5B'), u'Đ' : bytearray(b'\x12\x5B\xF0\x5B\xF0\x12'), 61 | #'CAPSLOCK' : \x58, 62 | 'a' : bytearray(b'\x1C\xF0\x1C'), 63 | 's' : bytearray(b'\x1B\xF0\x1B'), 64 | 'd' : bytearray(b'\x23\xF0\x23'), 65 | 'f' : bytearray(b'\x2B\xF0\x2B'), 66 | 'g' : bytearray(b'\x34\xF0\x34'), 67 | 'h' : bytearray(b'\x33\xF0\x33'), 68 | 'j' : bytearray(b'\x3B\xF0\x3B'), 69 | 'k' : bytearray(b'\x42\xF0\x42'), 70 | 'l' : bytearray(b'\x4B\xF0\x4B'), 71 | ';' : bytearray(b'\x4C\xF0\x4C'), ':' : bytearray(b'\x12\x4C\xF0\x4C\xF0\x12'), 72 | u'č' : bytearray(b'\x4C\xF0\x4C'), u'Č' : bytearray(b'\x12\x4C\xF0\x4C\xF0\x12'), 73 | '\'' : bytearray(b'\x52\xF0\x52'), '\"' : bytearray(b'\x12\x52\xF0\x52\xF0\x12'), 74 | u'ć' : bytearray(b'\x52\xF0\x52'), u'Ć' : bytearray(b'\x12\x52\xF0\x52\xF0\x12'), 75 | '\\' : bytearray(b'\x5D\xF0\x5D'), '|' : bytearray(b'\x12\x5D\xF0\x5D\xF0\x12'), 76 | u'ž' : bytearray(b'\x5D\xF0\x5D'), u'Ž' : bytearray(b'\x12\x5D\xF0\x5D\xF0\x12'), 77 | '\r' : bytearray(b'\x5A\xF0\x5A'),# ENTER 78 | #'LEFTSHIFT' : \x12, 79 | 'z' : bytearray(b'\x1A\xF0\x1A'), 80 | 'x' : bytearray(b'\x22\xF0\x22'), 81 | 'c' : bytearray(b'\x21\xF0\x21'), 82 | 'v' : bytearray(b'\x2A\xF0\x2A'), 83 | 'b' : bytearray(b'\x32\xF0\x32'), 84 | 'n' : bytearray(b'\x31\xF0\x31'), 85 | 'm' : bytearray(b'\x3A\xF0\x3A'), 86 | ',' : bytearray(b'\x41\xF0\x41'), '<' : bytearray(b'\x12\x41\xF0\x41\xF0\x12'), 87 | '.' : bytearray(b'\x49\xF0\x49'), '>' : bytearray(b'\x12\x49\xF0\x49\xF0\x12'), 88 | '/' : bytearray(b'\x4A\xF0\x4A'), '?' : bytearray(b'\x12\x4A\xF0\x4A\xF0\x12'), 89 | #'RIGHTSHIFT': \x59, 90 | #'LEFTCTRL' : \x14, 91 | #'LEFTALT' : \x11, 92 | ' ' : bytearray(b'\x29\xF0\x29'), 93 | #'RIGHTALT' :(\x11 | \x80), 94 | #'RIGHTCTRL' :(\x14 | \x80), 95 | #'INSERT' :(\x70 | \x80), 96 | #'DELETE' :(\x71 | \x80), 97 | #'HOME' :(\x6C | \x80), 98 | #'END' :(\x69 | \x80), 99 | #'PAGEUP' :(\x7D | \x80), 100 | #'PAGEDOWN' :(\x7A | \x80), 101 | #'UP' :(\x75 | \x80), 102 | #'DOWN' :(\x72 | \x80), 103 | #'LEFT' :(\x6B | \x80), 104 | #'RIGHT' :(\x74 | \x80), 105 | #'NUMLOCK' :(\x77 | \x80), 106 | #'KP7' : \x6C, 107 | #'KP4' : \x6B, 108 | #'KP1' : \x69, 109 | #'KPSLASH' :(\x4A | \x80), 110 | #'KP8' : \x75, 111 | #'KP5' : \x73, 112 | #'KP2' : \x72, 113 | #'KP0' : \x70, 114 | #'KPASTERISK': \x7C, 115 | #'KP9' : \x7D, 116 | #'KP6' : \x74, 117 | #'KP3' : \x7A, 118 | #'KPPLUS' : \x79, 119 | #'KPENTER' :(\x5A | \x80), 120 | '\x1B': bytearray(b'\x76\xF0\x76'),# ESC 121 | #'F1' : \x05, 122 | #'F2' : \x06, 123 | #'F3' : \x04, 124 | #'F4' : \x0C, 125 | #'F5' : \x03, 126 | #'F6' : \x0B, 127 | #'F7' : \x83, 128 | #'F8' : \x0A, 129 | #'F9' : \x01, 130 | #'F10' : \x09, 131 | #'F11' : \x78, 132 | #'F12' : \x07, 133 | #'SCROLLLOCK': \x7E, 134 | '\x01': bytearray(b'\x14\x1C\xF0\x1C\xF0\x14'),# Ctrl-A 135 | '\x02': bytearray(b'\x14\x32\xF0\x32\xF0\x14'),# Ctrl-B 136 | '\x03': bytearray(b'\x14\x21\xF0\x21\xF0\x14'),# Ctrl-C 137 | '\x04': bytearray(b'\x14\x23\xF0\x23\xF0\x14'),# Ctrl-D 138 | '\x05': bytearray(b'\x14\x24\xF0\x24\xF0\x14'),# Ctrl-E 139 | '\x06': bytearray(b'\x14\x2B\xF0\x2B\xF0\x14'),# Ctrl-F 140 | '\x07': bytearray(b'\x14\x34\xF0\x34\xF0\x14'),# Ctrl-G 141 | '\x08': bytearray(b'\x14\x33\xF0\x33\xF0\x14'),# Ctrl-H 142 | '\x09': bytearray(b'\x14\x43\xF0\x43\xF0\x14'),# Ctrl-I 143 | '\x0A': bytearray(b'\x14\x3B\xF0\x3B\xF0\x14'),# Ctrl-J 144 | '\x0B': bytearray(b'\x14\x42\xF0\x42\xF0\x14'),# Ctrl-K 145 | '\x0C': bytearray(b'\x14\x4B\xF0\x4B\xF0\x14'),# Ctrl-L 146 | #'\x0D' : bytearray(b'\x14\x3A\xF0\x3A\xF0\x14'),# Ctrl-M ENTER 147 | '\x0E': bytearray(b'\x14\x31\xF0\x31\xF0\x14'),# Ctrl-N 148 | '\x0F': bytearray(b'\x14\x44\xF0\x44\xF0\x14'),# Ctrl-O 149 | '\x10': bytearray(b'\x14\x4D\xF0\x4D\xF0\x14'),# Ctrl-P 150 | '\x11': bytearray(b'\x14\x15\xF0\x15\xF0\x14'),# Ctrl-Q 151 | '\x12': bytearray(b'\x14\x2D\xF0\x2D\xF0\x14'),# Ctrl-R 152 | '\x13': bytearray(b'\x14\x1B\xF0\x1B\xF0\x14'),# Ctrl-S 153 | '\x14': bytearray(b'\x14\x2C\xF0\x2C\xF0\x14'),# Ctrl-T 154 | '\x15': bytearray(b'\x14\x3C\xF0\x3C\xF0\x14'),# Ctrl-U 155 | '\x16': bytearray(b'\x14\x2A\xF0\x2A\xF0\x14'),# Ctrl-V 156 | '\x17': bytearray(b'\x14\x1D\xF0\x1D\xF0\x14'),# Ctrl-W 157 | '\x18': bytearray(b'\x14\x22\xF0\x22\xF0\x14'),# Ctrl-X 158 | '\x19': bytearray(b'\x14\x35\xF0\x35\xF0\x14'),# Ctrl-Y 159 | '\x1A': bytearray(b'\x14\x1A\xF0\x1A\xF0\x14'),# Ctrl-Z 160 | 'A' : bytearray(b'\x12\x1C\xF0\x1C\xF0\x12'), 161 | 'B' : bytearray(b'\x12\x32\xF0\x32\xF0\x12'), 162 | 'C' : bytearray(b'\x12\x21\xF0\x21\xF0\x12'), 163 | 'D' : bytearray(b'\x12\x23\xF0\x23\xF0\x12'), 164 | 'E' : bytearray(b'\x12\x24\xF0\x24\xF0\x12'), 165 | 'F' : bytearray(b'\x12\x2B\xF0\x2B\xF0\x12'), 166 | 'G' : bytearray(b'\x12\x34\xF0\x34\xF0\x12'), 167 | 'H' : bytearray(b'\x12\x33\xF0\x33\xF0\x12'), 168 | 'I' : bytearray(b'\x12\x43\xF0\x43\xF0\x12'), 169 | 'J' : bytearray(b'\x12\x3B\xF0\x3B\xF0\x12'), 170 | 'K' : bytearray(b'\x12\x42\xF0\x42\xF0\x12'), 171 | 'L' : bytearray(b'\x12\x4B\xF0\x4B\xF0\x12'), 172 | 'M' : bytearray(b'\x12\x3A\xF0\x3A\xF0\x12'), 173 | 'N' : bytearray(b'\x12\x31\xF0\x31\xF0\x12'), 174 | 'O' : bytearray(b'\x12\x44\xF0\x44\xF0\x12'), 175 | 'P' : bytearray(b'\x12\x4D\xF0\x4D\xF0\x12'), 176 | 'Q' : bytearray(b'\x12\x15\xF0\x15\xF0\x12'), 177 | 'R' : bytearray(b'\x12\x2D\xF0\x2D\xF0\x12'), 178 | 'S' : bytearray(b'\x12\x1B\xF0\x1B\xF0\x12'), 179 | 'T' : bytearray(b'\x12\x2C\xF0\x2C\xF0\x12'), 180 | 'U' : bytearray(b'\x12\x3C\xF0\x3C\xF0\x12'), 181 | 'V' : bytearray(b'\x12\x2A\xF0\x2A\xF0\x12'), 182 | 'W' : bytearray(b'\x12\x1D\xF0\x1D\xF0\x12'), 183 | 'X' : bytearray(b'\x12\x22\xF0\x22\xF0\x12'), 184 | 'Y' : bytearray(b'\x12\x35\xF0\x35\xF0\x12'), 185 | 'Z' : bytearray(b'\x12\x1A\xF0\x1A\xF0\x12'), 186 | } 187 | 188 | 189 | class PS2_client: 190 | 191 | def __init__(self, ps2socket): 192 | self.command_client, self.remote_addr = ps2socket.accept() 193 | self.command_client.setblocking(False) 194 | self.command_client.sendall(bytes([255, 252, 34])) # dont allow line mode 195 | self.command_client.sendall(bytes([255, 251, 1])) # turn off local echo 196 | self.command_client.recv(32) # drain junk 197 | sleep_us(20000) 198 | self.command_client.recv(32) # drain junk 199 | self.remote_addr = self.remote_addr[0] 200 | #self.command_client.settimeout(_COMMAND_TIMEOUT) 201 | log_msg(1, "PS2 Command connection from:", self.remote_addr) 202 | self.command_client.setsockopt(socket.SOL_SOCKET, 203 | _SO_REGISTER_HANDLER, 204 | self.exec_ps2_command) 205 | self.active = True 206 | 207 | 208 | @micropython.viper 209 | def send_ps2(self, sequence): 210 | p = ptr8(addressof(sequence)) 211 | l = int(len(sequence)) 212 | f0c = 0 213 | for i in range(l): 214 | scancode = p[i] 215 | if scancode == 0xF0: 216 | sleep_us(50000) 217 | f0c = 2 218 | ps2port.write(bytearray([scancode])) 219 | if f0c > 0: 220 | f0c -= 1 221 | if f0c == 0: 222 | sleep_us(50000) 223 | 224 | 225 | def exec_ps2_command(self, cl): 226 | global client_busy 227 | global my_ip_addr 228 | global ps2port 229 | 230 | try: 231 | collect() 232 | 233 | data = cl.recv(32) 234 | 235 | if len(data) <= 0: 236 | # No data, close 237 | log_msg(1, "*** No data, assume QUIT") 238 | close_client(cl) 239 | return 240 | 241 | if client_busy: # check if another client is busy 242 | return # and quit 243 | 244 | client_busy = True # now it's my turn 245 | sdata = str(data, "utf-8") 246 | for keystroke in sdata: 247 | if keystroke in asc2scan: 248 | self.send_ps2(asc2scan[keystroke]) 249 | cl.sendall(data) 250 | client_busy = False 251 | return 252 | except: 253 | close_client(cl) 254 | 255 | 256 | def log_msg(level, *args): 257 | global verbose_l 258 | if verbose_l >= level: 259 | print(*args) 260 | 261 | 262 | # close client and remove it from the list 263 | def close_client(cl): 264 | cl.setsockopt(socket.SOL_SOCKET, _SO_REGISTER_HANDLER, None) 265 | cl.close() 266 | for i, client in enumerate(client_list): 267 | if client.command_client == cl: 268 | del client_list[i] 269 | break 270 | 271 | 272 | def accept_ps2_connect(ps2socket): 273 | # Accept new calls for the server 274 | try: 275 | client_list.append(PS2_client(ps2socket)) 276 | except: 277 | log_msg(1, "Attempt to connect failed") 278 | # try at least to reject 279 | try: 280 | temp_client, temp_addr = ps2socket.accept() 281 | temp_client.close() 282 | except: 283 | pass 284 | 285 | 286 | def stop(): 287 | global ps2socket 288 | global client_list 289 | global client_busy 290 | global ps2port 291 | 292 | for client in client_list: 293 | client.command_client.setsockopt(socket.SOL_SOCKET, 294 | _SO_REGISTER_HANDLER, None) 295 | client.command_client.close() 296 | del client_list 297 | client_list = [] 298 | client_busy = False 299 | if ps2socket is not None: 300 | ps2socket.setsockopt(socket.SOL_SOCKET, _SO_REGISTER_HANDLER, None) 301 | ps2socket.close() 302 | del ps2port 303 | 304 | 305 | # start listening for ftp connections on telnet default port 23 306 | def start(port=23, verbose=0, splash=True): 307 | global ps2socket 308 | global verbose_l 309 | global client_list 310 | global client_busy 311 | global ps2port 312 | 313 | alloc_emergency_exception_buf(100) 314 | verbose_l = verbose 315 | client_list = [] 316 | client_busy = False 317 | 318 | ps2socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 319 | ps2socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 320 | ps2socket.bind(('0.0.0.0', port)) 321 | ps2socket.listen(0) 322 | ps2socket.setsockopt(socket.SOL_SOCKET, 323 | _SO_REGISTER_HANDLER, accept_ps2_connect) 324 | 325 | 326 | def restart(port=23, verbose=0, splash=True): 327 | stop() 328 | sleep_us(200000) 329 | start(port, verbose, splash) 330 | 331 | 332 | start(splash=True) 333 | collect() 334 | -------------------------------------------------------------------------------- /pygame_keyboard_mouse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # AUTHOR=EMARD 4 | # LICENSE=GPL 5 | 6 | # use ps2recv.py on ESP32 (set pinout at ps2recv.py) 7 | # edit "mouse_wheel" (below) 8 | # False: send 3-byte reports as no-wheel mouse (legacy/uninitialized PS/2) 9 | # True: send 4-byte reports as wheel mouse (modern PS/2) 10 | 11 | # apt-get install fonts-dseg 12 | 13 | import pygame 14 | import struct 15 | import socket 16 | 17 | tcp_host = "192.168.48.181" 18 | tcp_port = 3252 19 | mouse_wheel = False 20 | 21 | ps2_tcp=socket.create_connection((tcp_host, tcp_port)) 22 | print("Sending mouse events to %s:%s" % (tcp_host,tcp_port)) 23 | #ps2_tcp.sendall(bytearray([0xAA, 0x00, 0xFA])) 24 | # mouse sends 0xAA 0x00 after being plugged 25 | # 0xFA is ACK what mouse sends after being configured 26 | 27 | def mouse_wheel_report(dx,dy,dz,btn_left,btn_middle,btn_right): 28 | return struct.pack("> 8) & 1)<<4) + 35 | (((((-dy) & 0x100) >> 8) & 1)<<5), 36 | dx & 0xFF, 37 | (-dy) & 0xFF, 38 | (-dz) & 0x0F, 39 | ord('W'), 2, # 2-byte wait value (us, LSB first) 40 | 1000 # us wait 41 | ) 42 | 43 | def mouse_nowheel_report(dx,dy,btn_left,btn_middle,btn_right): 44 | return struct.pack("> 8) & 1)<<4) + 49 | (((((-dy) & 0x100) >> 8) & 1)<<5), 50 | dx & 0xFF, 51 | (-dy) & 0xFF, 52 | ord('W'), 2, # 2-byte wait value (us, LSB first) 53 | 1000 # us wait 54 | ) 55 | 56 | 57 | pygame.init() 58 | (width, height) = (320, 200) 59 | screen = pygame.display.set_mode((width, height)) 60 | pygame.display.set_caption(u'Press PAUSE to quit') 61 | pygame.display.flip() 62 | pygame.event.set_grab(True) 63 | pygame.mouse.set_visible(False) 64 | font = pygame.font.SysFont('DSEG14 Classic', height) 65 | 66 | event2ps2 = { 67 | pygame.K_1 : 0x16, 68 | pygame.K_2 : 0x1E, 69 | pygame.K_3 : 0x26, 70 | pygame.K_4 : 0x25, 71 | pygame.K_5 : 0x2E, 72 | pygame.K_6 : 0x36, 73 | pygame.K_7 : 0x3D, 74 | pygame.K_8 : 0x3E, 75 | pygame.K_9 : 0x46, 76 | pygame.K_0 : 0x45, 77 | pygame.K_MINUS : 0x4E, 78 | pygame.K_EQUALS : 0x55, 79 | pygame.K_BACKSPACE : 0x66, 80 | pygame.K_TAB : 0x0D, 81 | pygame.K_q : 0x15, 82 | pygame.K_w : 0x1D, 83 | pygame.K_e : 0x24, 84 | pygame.K_r : 0x2D, 85 | pygame.K_t : 0x2C, 86 | pygame.K_y : 0x35, 87 | pygame.K_u : 0x3C, 88 | pygame.K_i : 0x43, 89 | pygame.K_o : 0x44, 90 | pygame.K_p : 0x4D, 91 | pygame.K_LEFTBRACKET : 0x54, 92 | pygame.K_RIGHTBRACKET : 0x5B, 93 | pygame.K_CAPSLOCK : 0x58, 94 | pygame.K_a : 0x1C, 95 | pygame.K_s : 0x1B, 96 | pygame.K_d : 0x23, 97 | pygame.K_f : 0x2B, 98 | pygame.K_g : 0x34, 99 | pygame.K_h : 0x33, 100 | pygame.K_j : 0x3B, 101 | pygame.K_k : 0x42, 102 | pygame.K_l : 0x4B, 103 | pygame.K_SEMICOLON : 0x4C, 104 | pygame.K_QUOTE : 0x52, 105 | pygame.K_RETURN : 0x5A, 106 | pygame.K_LSHIFT : 0x12, 107 | pygame.K_z : 0x1A, 108 | pygame.K_x : 0x22, 109 | pygame.K_c : 0x21, 110 | pygame.K_v : 0x2A, 111 | pygame.K_b : 0x32, 112 | pygame.K_n : 0x31, 113 | pygame.K_m : 0x3A, 114 | pygame.K_COMMA : 0x41, 115 | pygame.K_PERIOD : 0x49, 116 | pygame.K_SLASH : 0x4A, 117 | pygame.K_RSHIFT : 0x59, 118 | pygame.K_LCTRL : 0x14, 119 | pygame.K_LALT : 0x11, 120 | pygame.K_SPACE : 0x29, 121 | pygame.K_RALT :(0x11 | 0x80), 122 | pygame.K_RCTRL :(0x14 | 0x80), 123 | pygame.K_INSERT :(0x70 | 0x80), 124 | pygame.K_DELETE :(0x71 | 0x80), 125 | pygame.K_HOME :(0x6C | 0x80), 126 | pygame.K_END :(0x69 | 0x80), 127 | pygame.K_PAGEUP :(0x7D | 0x80), 128 | pygame.K_PAGEDOWN :(0x7A | 0x80), 129 | pygame.K_UP :(0x75 | 0x80), 130 | pygame.K_DOWN :(0x72 | 0x80), 131 | pygame.K_LEFT :(0x6B | 0x80), 132 | pygame.K_RIGHT :(0x74 | 0x80), 133 | pygame.K_NUMLOCK :(0x77 | 0x80), 134 | pygame.K_KP7 : 0x6C, 135 | pygame.K_KP4 : 0x6B, 136 | pygame.K_KP1 : 0x69, 137 | pygame.K_KP_DIVIDE :(0x4A | 0x80), 138 | pygame.K_KP8 : 0x75, 139 | pygame.K_KP5 : 0x73, 140 | pygame.K_KP2 : 0x72, 141 | pygame.K_KP0 : 0x70, 142 | pygame.K_KP_MULTIPLY : 0x7C, 143 | pygame.K_KP9 : 0x7D, 144 | pygame.K_KP6 : 0x74, 145 | pygame.K_KP3 : 0x7A, 146 | pygame.K_KP_PLUS : 0x79, 147 | pygame.K_KP_ENTER :(0x5A | 0x80), 148 | pygame.K_ESCAPE : 0x76, 149 | pygame.K_F1 : 0x05, 150 | pygame.K_F2 : 0x06, 151 | pygame.K_F3 : 0x04, 152 | pygame.K_F4 : 0x0C, 153 | pygame.K_F5 : 0x03, 154 | pygame.K_F6 : 0x0B, 155 | pygame.K_F7 : 0x83, 156 | pygame.K_F8 : 0x0A, 157 | pygame.K_F9 : 0x01, 158 | pygame.K_F10 : 0x09, 159 | pygame.K_F11 : 0x78, 160 | pygame.K_F12 : 0x07, 161 | pygame.K_SCROLLOCK : 0x7E, 162 | pygame.K_BACKSLASH : 0x5D, 163 | } 164 | 165 | # mouse pointer visual feedback 166 | x=0 167 | y=0 168 | 169 | while(True): 170 | event = pygame.event.wait() 171 | if event.type == pygame.KEYDOWN: 172 | if event.key == pygame.K_PAUSE: 173 | print("PAUSE") 174 | break 175 | text = str(event.unicode) 176 | screen.blit(font.render(text, True, (255, 255, 255)), (0, 0)) 177 | pygame.display.flip() 178 | if event.key in event2ps2: 179 | code = event2ps2[event.key] 180 | if code & 0x80: 181 | packet = bytearray([ord('K'), 2, 0xE0, code & 0x7F, ord('W'), 2, 0x50, 0xC3]) 182 | else: 183 | packet = bytearray([ord('K'), 1, code & 0x7F, ord('W'), 2, 0x50, 0xC3]) 184 | ps2_tcp.sendall(packet) 185 | continue 186 | if event.type == pygame.KEYUP: 187 | screen.fill((0,0,0)) 188 | pygame.display.flip() 189 | if event.key in event2ps2: 190 | code = event2ps2[event.key] 191 | if code & 0x80: 192 | packet = bytearray([ord('K'), 3, 0xE0, 0xF0, code & 0x7F, ord('W'), 2, 0x50, 0xC3]) 193 | else: 194 | packet = bytearray([ord('K'), 2, 0xF0, code & 0x7F, ord('W'), 2, 0x50, 0xC3]) 195 | ps2_tcp.sendall(packet) 196 | continue 197 | wheel = 0 198 | if event.type == pygame.MOUSEBUTTONDOWN: # for wheel events 199 | if event.button == 4: # wheel UP 200 | wheel = -1 201 | if event.button == 5: # wheel DOWN 202 | wheel = 1 203 | (dx, dy) = pygame.mouse.get_rel() 204 | dz = wheel 205 | (btn_left, btn_middle, btn_right) = pygame.mouse.get_pressed() 206 | 207 | if mouse_wheel: 208 | # mouse with wheel 209 | report = mouse_wheel_report(dx, dy, dz, btn_left, btn_middle, btn_right) 210 | ps2_tcp.sendall(bytearray(report)) 211 | #print("0x%08X: X=%4d, Y=%4d, Z=%2d, L=%2d, M=%2d, R=%2d" % (struct.unpack("I",report)[0], dx, dy, dz, btn_left, btn_middle, btn_right)) 212 | else: 213 | # mouse without wheel 214 | report = mouse_nowheel_report(dx, dy, btn_left, btn_middle, btn_right) 215 | ps2_tcp.sendall(bytearray(report)) 216 | #print(report) 217 | #print("X=%4d, Y=%4d, L=%2d, M=%2d, R=%2d" % (dx, dy, btn_left, btn_middle, btn_right)) 218 | 219 | # visual feedback for mouse 220 | pygame.draw.line(screen, (0, 0, 0), (x, 0), (x, height-1)) 221 | pygame.draw.line(screen, (0, 0, 0), (0, y), (width-1, y)) 222 | x += dx 223 | x %= width 224 | y += dy 225 | y %= height 226 | pygame.draw.line(screen, (255, 255, 255), (x, 0), (x, height-1)) 227 | pygame.draw.line(screen, (255, 255, 255), (0, y), (width-1, y)) 228 | pygame.display.flip() 229 | --------------------------------------------------------------------------------