├── .gitignore ├── kvmc_in ├── Makefile ├── .gitignore ├── uart.h ├── support │ └── 49-teensy.rules ├── kvmc_in.c ├── uart.c ├── usb_serial.h └── usb_serial.c ├── README.md ├── kvmc_out └── kvmc_out.ino └── kvmc.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /kvmc_in/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmurdocca/kvmc/HEAD/kvmc_in/Makefile -------------------------------------------------------------------------------- /kvmc_in/.gitignore: -------------------------------------------------------------------------------- 1 | .dep/ 2 | *.eep 3 | *.elf 4 | *.hex 5 | *.lss 6 | *.map 7 | *.o 8 | *.sym 9 | *.lst 10 | 11 | 12 | *.swp 13 | 14 | -------------------------------------------------------------------------------- /kvmc_in/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _uart_included_h_ 2 | #define _uart_included_h_ 3 | 4 | #include 5 | 6 | void uart_init(uint32_t baud); 7 | void uart_putchar(uint8_t c); 8 | uint8_t uart_getchar(void); 9 | uint8_t uart_available(void); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /kvmc_in/support/49-teensy.rules: -------------------------------------------------------------------------------- 1 | # UDEV Rules for Teensy boards, http://www.pjrc.com/teensy/ 2 | # 3 | # The latest version of this file may be found at: 4 | # http://www.pjrc.com/teensy/49-teensy.rules 5 | # 6 | # This file must be placed at: 7 | # 8 | # /etc/udev/rules.d/49-teensy.rules (preferred location) 9 | # or 10 | # /lib/udev/rules.d/49-teensy.rules (req'd on some broken systems) 11 | # 12 | # To install, type this command in a terminal: 13 | # sudo cp 49-teensy.rules /etc/udev/rules.d/49-teensy.rules 14 | # 15 | # Or use the alternate way (from this forum message) to download and install: 16 | # https://forum.pjrc.com/threads/45595?p=150445&viewfull=1#post150445 17 | # 18 | # After this file is installed, physically unplug and reconnect Teensy. 19 | # 20 | ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1" 21 | ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1" 22 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", MODE:="0666" 23 | KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", MODE:="0666" 24 | # 25 | # If you share your linux system with other users, or just don't like the 26 | # idea of write permission for everybody, you can replace MODE:="0666" with 27 | # OWNER:="yourusername" to create the device owned by you, or with 28 | # GROUP:="somegroupname" and mange access using standard unix groups. 29 | # 30 | # 31 | # If using USB Serial you get a new device each time (Ubuntu 9.10) 32 | # eg: /dev/ttyACM0, ttyACM1, ttyACM2, ttyACM3, ttyACM4, etc 33 | # apt-get remove --purge modemmanager (reboot may be necessary) 34 | # 35 | # Older modem proding (eg, Ubuntu 9.04) caused very slow serial device detection. 36 | # To fix, add this near top of /lib/udev/rules.d/77-nm-probe-modem-capabilities.rules 37 | # SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789]?", GOTO="nm_modem_probe_end" 38 | # 39 | -------------------------------------------------------------------------------- /kvmc_in/kvmc_in.c: -------------------------------------------------------------------------------- 1 | /* kvmc_in 2 | * Copyright (c) 2018 LinuxDojo 3 | * http//linuxdojo.com 4 | * 5 | * kvmc_in is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, 8 | * or (at your option) any later version. 9 | * 10 | * kvmc_in is distributed in the hope that it will be useful, but 11 | * WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You can obtain a copy of the GNU General Public License along at: 16 | * http://www.gnu.org/licenses/. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "usb_serial.h" 24 | #include "uart.h" 25 | 26 | #define LED_CONFIG (DDRD |= (1<<6)) 27 | #define LED_ON (PORTD |= (1<<6)) 28 | #define LED_OFF (PORTD &= ~(1<<6)) 29 | #define CPU_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n)) 30 | 31 | int main(void){ 32 | 33 | int bytes_available; 34 | int n; 35 | 36 | CPU_PRESCALE(0); 37 | LED_CONFIG; 38 | usb_init(); 39 | uart_init(115200); 40 | while (1){ 41 | LED_OFF; 42 | while (!usb_configured()); 43 | _delay_ms(1000); 44 | usb_serial_flush_input(); 45 | LED_ON; 46 | while (usb_serial_get_control() & USB_SERIAL_DTR){ 47 | bytes_available = usb_serial_available(); 48 | for (int i = 0; i < bytes_available; i++){ 49 | n = usb_serial_getchar(); 50 | if (n >= 0) uart_putchar(n); 51 | } 52 | bytes_available = uart_available(); 53 | for (int i = 0; i < bytes_available; i++){ 54 | n = uart_getchar(); 55 | if (n >= 0) usb_serial_putchar(n); 56 | } 57 | } 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /kvmc_in/uart.c: -------------------------------------------------------------------------------- 1 | /* UART Example for Teensy USB Development Board 2 | * http://www.pjrc.com/teensy/ 3 | * Copyright (c) 2009 PJRC.COM, LLC 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | */ 23 | 24 | // Version 1.0: Initial Release 25 | // Version 1.1: Add support for Teensy 2.0, minor optimizations 26 | 27 | 28 | #include 29 | #include 30 | 31 | #include "uart.h" 32 | 33 | // These buffers may be any size from 2 to 256 bytes. 34 | #define RX_BUFFER_SIZE 64 35 | #define TX_BUFFER_SIZE 40 36 | 37 | static volatile uint8_t tx_buffer[TX_BUFFER_SIZE]; 38 | static volatile uint8_t tx_buffer_head; 39 | static volatile uint8_t tx_buffer_tail; 40 | static volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; 41 | static volatile uint8_t rx_buffer_head; 42 | static volatile uint8_t rx_buffer_tail; 43 | 44 | // Initialize the UART 45 | void uart_init(uint32_t baud) 46 | { 47 | cli(); 48 | UBRR1 = (F_CPU / 4 / baud - 1) / 2; 49 | UCSR1A = (1<= TX_BUFFER_SIZE) i = 0; 64 | while (tx_buffer_tail == i) ; // wait until space in buffer 65 | //cli(); 66 | tx_buffer[i] = c; 67 | tx_buffer_head = i; 68 | UCSR1B = (1<= RX_BUFFER_SIZE) i = 0; 80 | c = rx_buffer[i]; 81 | rx_buffer_tail = i; 82 | return c; 83 | } 84 | 85 | // Return the number of bytes waiting in the receive buffer. 86 | // Call this before uart_getchar() to check if it will need 87 | // to wait for a byte to arrive. 88 | uint8_t uart_available(void) 89 | { 90 | uint8_t head, tail; 91 | 92 | head = rx_buffer_head; 93 | tail = rx_buffer_tail; 94 | if (head >= tail) return head - tail; 95 | return RX_BUFFER_SIZE + head - tail; 96 | } 97 | 98 | // Transmit Interrupt 99 | ISR(USART1_UDRE_vect) 100 | { 101 | uint8_t i; 102 | 103 | if (tx_buffer_head == tx_buffer_tail) { 104 | // buffer is empty, disable transmit interrupt 105 | UCSR1B = (1<= TX_BUFFER_SIZE) i = 0; 109 | UDR1 = tx_buffer[i]; 110 | tx_buffer_tail = i; 111 | } 112 | } 113 | 114 | // Receive Interrupt 115 | ISR(USART1_RX_vect) 116 | { 117 | uint8_t c, i; 118 | 119 | c = UDR1; 120 | i = rx_buffer_head + 1; 121 | if (i >= RX_BUFFER_SIZE) i = 0; 122 | if (i != rx_buffer_tail) { 123 | rx_buffer[i] = c; 124 | rx_buffer_head = i; 125 | } 126 | } 127 | 128 | -------------------------------------------------------------------------------- /kvmc_in/usb_serial.h: -------------------------------------------------------------------------------- 1 | #ifndef usb_serial_h__ 2 | #define usb_serial_h__ 3 | 4 | #include 5 | 6 | // setup 7 | void usb_init(void); // initialize everything 8 | uint8_t usb_configured(void); // is the USB port configured 9 | 10 | // receiving data 11 | int16_t usb_serial_getchar(void); // receive a character (-1 if timeout/error) 12 | uint8_t usb_serial_available(void); // number of bytes in receive buffer 13 | void usb_serial_flush_input(void); // discard any buffered input 14 | 15 | // transmitting data 16 | int8_t usb_serial_putchar(uint8_t c); // transmit a character 17 | int8_t usb_serial_putchar_nowait(uint8_t c); // transmit a character, do not wait 18 | int8_t usb_serial_write(const uint8_t *buffer, uint16_t size); // transmit a buffer 19 | void usb_serial_flush_output(void); // immediately transmit any buffered output 20 | 21 | // serial parameters 22 | uint32_t usb_serial_get_baud(void); // get the baud rate 23 | uint8_t usb_serial_get_stopbits(void); // get the number of stop bits 24 | uint8_t usb_serial_get_paritytype(void);// get the parity type 25 | uint8_t usb_serial_get_numbits(void); // get the number of data bits 26 | uint8_t usb_serial_get_control(void); // get the RTS and DTR signal state 27 | int8_t usb_serial_set_control(uint8_t signals); // set DSR, DCD, RI, etc 28 | 29 | // constants corresponding to the various serial parameters 30 | #define USB_SERIAL_DTR 0x01 31 | #define USB_SERIAL_RTS 0x02 32 | #define USB_SERIAL_1_STOP 0 33 | #define USB_SERIAL_1_5_STOP 1 34 | #define USB_SERIAL_2_STOP 2 35 | #define USB_SERIAL_PARITY_NONE 0 36 | #define USB_SERIAL_PARITY_ODD 1 37 | #define USB_SERIAL_PARITY_EVEN 2 38 | #define USB_SERIAL_PARITY_MARK 3 39 | #define USB_SERIAL_PARITY_SPACE 4 40 | #define USB_SERIAL_DCD 0x01 41 | #define USB_SERIAL_DSR 0x02 42 | #define USB_SERIAL_BREAK 0x04 43 | #define USB_SERIAL_RI 0x08 44 | #define USB_SERIAL_FRAME_ERR 0x10 45 | #define USB_SERIAL_PARITY_ERR 0x20 46 | #define USB_SERIAL_OVERRUN_ERR 0x40 47 | 48 | // This file does not include the HID debug functions, so these empty 49 | // macros replace them with nothing, so users can compile code that 50 | // has calls to these functions. 51 | #define usb_debug_putchar(c) 52 | #define usb_debug_flush_output() 53 | 54 | 55 | 56 | // Everything below this point is only intended for usb_serial.c 57 | #ifdef USB_SERIAL_PRIVATE_INCLUDE 58 | #include 59 | #include 60 | #include 61 | 62 | #define EP_TYPE_CONTROL 0x00 63 | #define EP_TYPE_BULK_IN 0x81 64 | #define EP_TYPE_BULK_OUT 0x80 65 | #define EP_TYPE_INTERRUPT_IN 0xC1 66 | #define EP_TYPE_INTERRUPT_OUT 0xC0 67 | #define EP_TYPE_ISOCHRONOUS_IN 0x41 68 | #define EP_TYPE_ISOCHRONOUS_OUT 0x40 69 | #define EP_SINGLE_BUFFER 0x02 70 | #define EP_DOUBLE_BUFFER 0x06 71 | #define EP_SIZE(s) ((s) == 64 ? 0x30 : \ 72 | ((s) == 32 ? 0x20 : \ 73 | ((s) == 16 ? 0x10 : \ 74 | 0x00))) 75 | 76 | #define MAX_ENDPOINT 4 77 | 78 | #define LSB(n) (n & 255) 79 | #define MSB(n) ((n >> 8) & 255) 80 | 81 | #if defined(__AVR_AT90USB162__) 82 | #define HW_CONFIG() 83 | #define PLL_CONFIG() (PLLCSR = ((1< Board: Teensy 2.0 61 | - Tools -> USB Type: Serial + Keyboard + Mouse + Joystck 62 | 63 | Compile and write to the Teensy: 64 | 65 | - Sketch -> Verify/Compile (follow prompts) 66 | 67 | 68 | ### USB Storage Device Software 69 | 70 | TODO 71 | 72 | 73 | 74 | 75 | # USAGE 76 | 77 | ## To start KVMC: 78 | 79 | 1. Connect the KVMC Control port to a USB3.0 compliant port via USB and boot from USB 80 | 2. Start the KVMC app from the desktop 81 | 3. Connect server VGA port, then connect server USB port. 82 | 83 | NOTE: Shutdown Lubuntu before unplugging the "laptop" port else you will risk 84 | corrupting the ext2 filesystem in the file "casper-rw" on the root of the USB 85 | key (located at /cdrom/casper-rw in Lubuntu). If corruption occurs, you may not 86 | be able to boot Lubuntu. If this happens, fix it by mounting the USB key's fist 87 | partition on a separate Linux system and enter as root: 88 | 89 | e2fsck -y /casper-rw 90 | 91 | ## Session recording and playback 92 | 93 | Record to a file every keystroke and mouse event (and serial message, see 94 | below) that you perform when controlling the remote computer for later 95 | playback. Select File -> Record/Replay Session Input. Good for repeating 96 | arduous, repetitive, manual user input tasks. 97 | 98 | ## Paste as keystrokes 99 | 100 | Paste whatever typable text is in your clipboard as keystrokes on the remote 101 | machine. 102 | 103 | ## Transferring files between local and remote computers 104 | 105 | The KVMC acts as a null-modem serial cable. Just fire up a terminal emulator on 106 | both machines to transfer files with a binary serial protocol like Zmodem. Use 107 | 115200bps 8N1 for both sides of the serial connection. 108 | 109 | Lubuntu comes with minicom which can be started from a shell. Open LXTerminal 110 | and type "sudo minicom". The serial port you need to specify in minicom is 111 | displayed on the bottom left of the KVMC App window in green. To get help in 112 | minicom, press Ctrl-A then press Z. 113 | 114 | The KVMC box presents a USB serial device on the remote machine. If the remote 115 | computer runs Windows, this will appear as something like COM13, but you need 116 | to install a serial driver before it will see this com port. To do so, open 117 | notepad on the remote, choose Edit -> Paste as Keystrokes -> Paste Windows 118 | Serial Driver -> click Paste. Then save the file as serial.inf in Windows, 119 | right click it and install. Note on Mac and Linux machines there is no need to 120 | install a driver for serial comms to work. Now you can fire up Hyperterm or 121 | your favorite terminal emulator and you're away (give puttyZ for Windows a go, 122 | there's a copy in KVMC's Lubuntu at /home/lubuntu/KVMC/puttyz.zip). 123 | 124 | If you are recording during a serial session, only outgoing serial packets 125 | sent by the controlling computer will be saved and replayed. 126 | 127 | ## Troubleshooting 128 | 129 | - Keyboard/mouse events not working or behaving weirdly? Try reconnecting 130 | the Server USB cable (can take a few tries). 131 | 132 | - App not starting or crashing? Try running it from the CLI to see any errors. 133 | Start LXTermial and type: 134 | 135 | sudo /home/lubuntu/KVMC/kvmc-1.1/kvmc.py" 136 | 137 | - Things are terrible? Disconnect everything and try reboot. If you have to do 138 | this too often to get out of a pickle, remove the USB key from inside the 139 | KVMC (3 screws) and boot from it. Once booted, attach the KVMC box and fire 140 | up the KVMC app. You can then detach and re-attach the KVMC box without 141 | rebooting Lubuntu, making sure to close and open the KVMC GUI app each time. 142 | 143 | - Cant close the KVMC App? Open a shell and type "sudo pkill -9 -f kvmc.py" to 144 | kill it. 145 | -------------------------------------------------------------------------------- /kvmc_out/kvmc_out.ino: -------------------------------------------------------------------------------- 1 | /* kvmc_out 2 | Copyright (c) 2018 LinuxDojo Pty Ltd 3 | http://linuxdojo.com.au 4 | 5 | kvmc is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published 7 | by the Free Software Foundation, either version 3 of the License, 8 | or (at your option) any later version. 9 | 10 | kvmc_in is distributed in the hope that it will be useful, but 11 | WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You can obtain a copy of the GNU General Public License along at: 16 | http://www.gnu.org/licenses/. 17 | */ 18 | 19 | // Message Types 20 | #define KEYBOARD 0 21 | #define KEYBOARD_MODKEY 1 22 | #define MOUSE_BUTTON 2 23 | #define MOUSE_MOVE 3 24 | #define MOUSE_WHEEL 4 25 | #define SERIAL_DATA 5 26 | #define REINIT 6 27 | #define SC 7 28 | 29 | // Digital Pin Assignments 30 | #define LED_PIN 11 31 | #define SC_UP 12 32 | #define SC_DOWN 13 33 | #define SC_LEFT 14 34 | #define SC_RIGHT 15 35 | #define SC_MENU 16 36 | #define SC_ZOOM 17 37 | 38 | HardwareSerial Uart = HardwareSerial(); 39 | int byte_counter = 0; 40 | char incoming_byte; 41 | char* packet; 42 | boolean sc_menu_state = false; 43 | boolean menu_button_pressed_last = false; 44 | 45 | void toggle_sc_menu_state() { 46 | if (sc_menu_state) 47 | sc_menu_state = false; 48 | else 49 | sc_menu_state = true; 50 | } 51 | 52 | void hide_sc_menu() { 53 | if (menu_button_pressed_last) { 54 | toggle_sc_menu_state(); 55 | menu_button_pressed_last = false; 56 | } 57 | if (sc_menu_state) { 58 | digitalWrite(SC_MENU, LOW); 59 | delay(50); 60 | toggle_sc_menu_state(); 61 | } 62 | } 63 | 64 | void reinit() { 65 | Keyboard.set_key1(0); 66 | Keyboard.set_key2(0); 67 | Keyboard.set_key3(0); 68 | Keyboard.set_key4(0); 69 | Keyboard.set_key5(0); 70 | Keyboard.set_key6(0); 71 | Keyboard.set_modifier(0); 72 | Keyboard.send_now(); 73 | Mouse.set_buttons(0, 0, 0); 74 | Mouse.move(0, 0); 75 | hide_sc_menu(); 76 | } 77 | 78 | int get_msg_type(char msg_byte) { 79 | //return(msg_byte >> 5); 80 | return (msg_byte & 7); 81 | } 82 | 83 | int get_msg_payload(char msg_byte) { 84 | // returns first 5 bits of byte 85 | return (msg_byte >> 3); 86 | } 87 | 88 | void setup() { 89 | Uart.begin(115200); 90 | Serial.begin(9600); 91 | pinMode(LED_PIN, OUTPUT); 92 | pinMode(SC_UP, OUTPUT); 93 | pinMode(SC_DOWN, OUTPUT); 94 | pinMode(SC_LEFT, OUTPUT); 95 | pinMode(SC_RIGHT, OUTPUT); 96 | pinMode(SC_MENU, OUTPUT); 97 | pinMode(SC_ZOOM, OUTPUT); 98 | } 99 | 100 | void loop() { 101 | digitalWrite(LED_PIN, LOW); 102 | digitalWrite(SC_UP, HIGH); 103 | digitalWrite(SC_DOWN, HIGH); 104 | digitalWrite(SC_LEFT, HIGH); 105 | digitalWrite(SC_RIGHT, HIGH); 106 | digitalWrite(SC_MENU, HIGH); 107 | digitalWrite(SC_ZOOM, HIGH); 108 | 109 | // Incoming serial data from the system being controlled. Send to the upstream controlling Teensy. 110 | if (Serial.available()) { 111 | Uart.write(Serial.read()); 112 | } 113 | 114 | // Incoming message from controller teensy. Process and react accordingly. 115 | if (Uart.available()) { 116 | digitalWrite(LED_PIN, HIGH); 117 | incoming_byte = Uart.read(); 118 | 119 | // Split message into type and payload 120 | int msg_type = get_msg_type(incoming_byte); 121 | int msg_payload = get_msg_payload(incoming_byte); 122 | 123 | if (msg_type == KEYBOARD) { 124 | while (true) { 125 | if (Uart.available()) { 126 | packet[byte_counter] = Uart.read(); 127 | byte_counter++; 128 | if (byte_counter == 6) { 129 | byte_counter = 0; 130 | Keyboard.set_key1(packet[0]); 131 | Keyboard.set_key2(packet[1]); 132 | Keyboard.set_key3(packet[2]); 133 | Keyboard.set_key4(packet[3]); 134 | Keyboard.set_key5(packet[4]); 135 | Keyboard.set_key6(packet[5]); 136 | Keyboard.send_now(); 137 | packet = ""; 138 | break; 139 | } 140 | } 141 | } 142 | hide_sc_menu(); 143 | } 144 | 145 | else if (msg_type == KEYBOARD_MODKEY) { 146 | Keyboard.set_modifier(msg_payload); 147 | Keyboard.send_now(); 148 | hide_sc_menu(); 149 | } 150 | 151 | else if (msg_type == MOUSE_BUTTON) { 152 | Mouse.set_buttons((msg_payload & 0x01) != 0, 153 | (msg_payload & 0x02) != 0, 154 | (msg_payload & 0x04) != 0); 155 | hide_sc_menu(); 156 | } 157 | 158 | else if (msg_type == MOUSE_MOVE) { 159 | while (true) { 160 | if (Uart.available()) { 161 | packet[byte_counter] = Uart.read(); 162 | byte_counter++; 163 | if (byte_counter == 2) { 164 | byte_counter = 0; 165 | Mouse.move(packet[0], packet[1]); 166 | packet = ""; 167 | break; 168 | } 169 | } 170 | } 171 | hide_sc_menu(); 172 | } 173 | 174 | else if (msg_type == MOUSE_WHEEL) { 175 | if (msg_payload == 0) { 176 | Mouse.scroll(1); 177 | } 178 | else { 179 | Mouse.scroll(-1); 180 | } 181 | hide_sc_menu(); 182 | } 183 | 184 | else if (msg_type == SERIAL_DATA) { 185 | // Loop until the UART is available 186 | while (!Uart.available()); 187 | // Send the data to the serial port of the system being controlled 188 | Serial.write(Uart.read()); 189 | } 190 | else if (msg_type == REINIT) { 191 | reinit(); 192 | } 193 | else if (msg_type == SC) { 194 | menu_button_pressed_last = false; 195 | while (!Uart.available()); 196 | incoming_byte = Uart.read(); 197 | if (incoming_byte & 1) 198 | digitalWrite(SC_UP, LOW); 199 | if (incoming_byte & 2) 200 | digitalWrite(SC_DOWN, LOW); 201 | if (incoming_byte & 4) 202 | digitalWrite(SC_LEFT, LOW); 203 | if (incoming_byte & 8) 204 | digitalWrite(SC_RIGHT, LOW); 205 | if (incoming_byte & 16) { 206 | digitalWrite(SC_MENU, LOW); 207 | toggle_sc_menu_state(); 208 | menu_button_pressed_last = true; 209 | } 210 | if (incoming_byte & 32) 211 | digitalWrite(SC_ZOOM, LOW); 212 | delay(50); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /kvmc_in/usb_serial.c: -------------------------------------------------------------------------------- 1 | /* USB Serial Example for Teensy USB Development Board 2 | * http://www.pjrc.com/teensy/usb_serial.html 3 | * Copyright (c) 2008,2010,2011 PJRC.COM, LLC 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | */ 23 | 24 | // Version 1.0: Initial Release 25 | // Version 1.1: support Teensy++ 26 | // Version 1.2: fixed usb_serial_available 27 | // Version 1.3: added transmit bandwidth test 28 | // Version 1.4: added usb_serial_write 29 | // Version 1.5: add support for Teensy 2.0 30 | // Version 1.6: fix zero length packet bug 31 | // Version 1.7: fix usb_serial_set_control 32 | 33 | #define USB_SERIAL_PRIVATE_INCLUDE 34 | #include "usb_serial.h" 35 | 36 | 37 | /************************************************************************** 38 | * 39 | * Configurable Options 40 | * 41 | **************************************************************************/ 42 | 43 | // You can change these to give your code its own name. On Windows, 44 | // these are only used before an INF file (driver install) is loaded. 45 | #define STR_MANUFACTURER L"linuxdojo.com" 46 | #define STR_PRODUCT L"kvmc" 47 | 48 | // All USB serial devices are supposed to have a serial number 49 | // (according to Microsoft). On windows, a new COM port is created 50 | // for every unique serial/vendor/product number combination. If 51 | // you program 2 identical boards with 2 different serial numbers 52 | // and they are assigned COM7 and COM8, each will always get the 53 | // same COM port number because Windows remembers serial numbers. 54 | // 55 | // On Mac OS-X, a device file is created automatically which 56 | // incorperates the serial number, eg, /dev/cu-usbmodem12341 57 | // 58 | // Linux by default ignores the serial number, and creates device 59 | // files named /dev/ttyACM0, /dev/ttyACM1... in the order connected. 60 | // Udev rules (in /etc/udev/rules.d) can define persistent device 61 | // names linked to this serial number, as well as permissions, owner 62 | // and group settings. 63 | #define STR_SERIAL_NUMBER L"00002" 64 | 65 | // Mac OS-X and Linux automatically load the correct drivers. On 66 | // Windows, even though the driver is supplied by Microsoft, an 67 | // INF file is needed to load the driver. These numbers need to 68 | // match the INF file. 69 | #define VENDOR_ID 0x16C0 70 | #define PRODUCT_ID 0x047A 71 | 72 | // When you write data, it goes into a USB endpoint buffer, which 73 | // is transmitted to the PC when it becomes full, or after a timeout 74 | // with no more writes. Even if you write in exactly packet-size 75 | // increments, this timeout is used to send a "zero length packet" 76 | // that tells the PC no more data is expected and it should pass 77 | // any buffered data to the application that may be waiting. If 78 | // you want data sent immediately, call usb_serial_flush_output(). 79 | #define TRANSMIT_FLUSH_TIMEOUT 5 /* in milliseconds */ 80 | 81 | // If the PC is connected but not "listening", this is the length 82 | // of time before usb_serial_getchar() returns with an error. This 83 | // is roughly equivilant to a real UART simply transmitting the 84 | // bits on a wire where nobody is listening, except you get an error 85 | // code which you can ignore for serial-like discard of data, or 86 | // use to know your data wasn't sent. 87 | #define TRANSMIT_TIMEOUT 25 /* in milliseconds */ 88 | 89 | // USB devices are supposed to implment a halt feature, which is 90 | // rarely (if ever) used. If you comment this line out, the halt 91 | // code will be removed, saving 116 bytes of space (gcc 4.3.0). 92 | // This is not strictly USB compliant, but works with all major 93 | // operating systems. 94 | #define SUPPORT_ENDPOINT_HALT 95 | 96 | 97 | 98 | /************************************************************************** 99 | * 100 | * Endpoint Buffer Configuration 101 | * 102 | **************************************************************************/ 103 | 104 | // These buffer sizes are best for most applications, but perhaps if you 105 | // want more buffering on some endpoint at the expense of others, this 106 | // is where you can make such changes. The AT90USB162 has only 176 bytes 107 | // of DPRAM (USB buffers) and only endpoints 3 & 4 can double buffer. 108 | 109 | #define ENDPOINT0_SIZE 16 110 | #define CDC_ACM_ENDPOINT 2 111 | #define CDC_RX_ENDPOINT 3 112 | #define CDC_TX_ENDPOINT 4 113 | #if defined(__AVR_AT90USB162__) 114 | #define CDC_ACM_SIZE 16 115 | #define CDC_ACM_BUFFER EP_SINGLE_BUFFER 116 | #define CDC_RX_SIZE 32 117 | #define CDC_RX_BUFFER EP_DOUBLE_BUFFER 118 | #define CDC_TX_SIZE 32 119 | #define CDC_TX_BUFFER EP_DOUBLE_BUFFER 120 | #else 121 | #define CDC_ACM_SIZE 16 122 | #define CDC_ACM_BUFFER EP_SINGLE_BUFFER 123 | #define CDC_RX_SIZE 64 124 | #define CDC_RX_BUFFER EP_DOUBLE_BUFFER 125 | #define CDC_TX_SIZE 64 126 | #define CDC_TX_BUFFER EP_DOUBLE_BUFFER 127 | #endif 128 | 129 | static const uint8_t PROGMEM endpoint_config_table[] = { 130 | 0, 131 | 1, EP_TYPE_INTERRUPT_IN, EP_SIZE(CDC_ACM_SIZE) | CDC_ACM_BUFFER, 132 | 1, EP_TYPE_BULK_OUT, EP_SIZE(CDC_RX_SIZE) | CDC_RX_BUFFER, 133 | 1, EP_TYPE_BULK_IN, EP_SIZE(CDC_TX_SIZE) | CDC_TX_BUFFER 134 | }; 135 | 136 | 137 | /************************************************************************** 138 | * 139 | * Descriptor Data 140 | * 141 | **************************************************************************/ 142 | 143 | // Descriptors are the data that your computer reads when it auto-detects 144 | // this USB device (called "enumeration" in USB lingo). The most commonly 145 | // changed items are editable at the top of this file. Changing things 146 | // in here should only be done by those who've read chapter 9 of the USB 147 | // spec and relevant portions of any USB class specifications! 148 | 149 | static const uint8_t PROGMEM device_descriptor[] = { 150 | 18, // bLength 151 | 1, // bDescriptorType 152 | 0x00, 0x02, // bcdUSB 153 | 2, // bDeviceClass 154 | 0, // bDeviceSubClass 155 | 0, // bDeviceProtocol 156 | ENDPOINT0_SIZE, // bMaxPacketSize0 157 | LSB(VENDOR_ID), MSB(VENDOR_ID), // idVendor 158 | LSB(PRODUCT_ID), MSB(PRODUCT_ID), // idProduct 159 | 0x00, 0x01, // bcdDevice 160 | 1, // iManufacturer 161 | 2, // iProduct 162 | 3, // iSerialNumber 163 | 1 // bNumConfigurations 164 | }; 165 | 166 | #define CONFIG1_DESC_SIZE (9+9+5+5+4+5+7+9+7+7) 167 | static const uint8_t PROGMEM config1_descriptor[CONFIG1_DESC_SIZE] = { 168 | // configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10 169 | 9, // bLength; 170 | 2, // bDescriptorType; 171 | LSB(CONFIG1_DESC_SIZE), // wTotalLength 172 | MSB(CONFIG1_DESC_SIZE), 173 | 2, // bNumInterfaces 174 | 1, // bConfigurationValue 175 | 0, // iConfiguration 176 | 0xC0, // bmAttributes 177 | 50, // bMaxPower 178 | // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 179 | 9, // bLength 180 | 4, // bDescriptorType 181 | 0, // bInterfaceNumber 182 | 0, // bAlternateSetting 183 | 1, // bNumEndpoints 184 | 0x02, // bInterfaceClass 185 | 0x02, // bInterfaceSubClass 186 | 0x01, // bInterfaceProtocol 187 | 0, // iInterface 188 | // CDC Header Functional Descriptor, CDC Spec 5.2.3.1, Table 26 189 | 5, // bFunctionLength 190 | 0x24, // bDescriptorType 191 | 0x00, // bDescriptorSubtype 192 | 0x10, 0x01, // bcdCDC 193 | // Call Management Functional Descriptor, CDC Spec 5.2.3.2, Table 27 194 | 5, // bFunctionLength 195 | 0x24, // bDescriptorType 196 | 0x01, // bDescriptorSubtype 197 | 0x01, // bmCapabilities 198 | 1, // bDataInterface 199 | // Abstract Control Management Functional Descriptor, CDC Spec 5.2.3.3, Table 28 200 | 4, // bFunctionLength 201 | 0x24, // bDescriptorType 202 | 0x02, // bDescriptorSubtype 203 | 0x06, // bmCapabilities 204 | // Union Functional Descriptor, CDC Spec 5.2.3.8, Table 33 205 | 5, // bFunctionLength 206 | 0x24, // bDescriptorType 207 | 0x06, // bDescriptorSubtype 208 | 0, // bMasterInterface 209 | 1, // bSlaveInterface0 210 | // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 211 | 7, // bLength 212 | 5, // bDescriptorType 213 | CDC_ACM_ENDPOINT | 0x80, // bEndpointAddress 214 | 0x03, // bmAttributes (0x03=intr) 215 | CDC_ACM_SIZE, 0, // wMaxPacketSize 216 | 64, // bInterval 217 | // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12 218 | 9, // bLength 219 | 4, // bDescriptorType 220 | 1, // bInterfaceNumber 221 | 0, // bAlternateSetting 222 | 2, // bNumEndpoints 223 | 0x0A, // bInterfaceClass 224 | 0x00, // bInterfaceSubClass 225 | 0x00, // bInterfaceProtocol 226 | 0, // iInterface 227 | // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 228 | 7, // bLength 229 | 5, // bDescriptorType 230 | CDC_RX_ENDPOINT, // bEndpointAddress 231 | 0x02, // bmAttributes (0x02=bulk) 232 | CDC_RX_SIZE, 0, // wMaxPacketSize 233 | 0, // bInterval 234 | // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13 235 | 7, // bLength 236 | 5, // bDescriptorType 237 | CDC_TX_ENDPOINT | 0x80, // bEndpointAddress 238 | 0x02, // bmAttributes (0x02=bulk) 239 | CDC_TX_SIZE, 0, // wMaxPacketSize 240 | 0 // bInterval 241 | }; 242 | 243 | // If you're desperate for a little extra code memory, these strings 244 | // can be completely removed if iManufacturer, iProduct, iSerialNumber 245 | // in the device desciptor are changed to zeros. 246 | struct usb_string_descriptor_struct { 247 | uint8_t bLength; 248 | uint8_t bDescriptorType; 249 | int16_t wString[]; 250 | }; 251 | static const struct usb_string_descriptor_struct PROGMEM string0 = { 252 | 4, 253 | 3, 254 | {0x0409} 255 | }; 256 | static const struct usb_string_descriptor_struct PROGMEM string1 = { 257 | sizeof(STR_MANUFACTURER), 258 | 3, 259 | STR_MANUFACTURER 260 | }; 261 | static const struct usb_string_descriptor_struct PROGMEM string2 = { 262 | sizeof(STR_PRODUCT), 263 | 3, 264 | STR_PRODUCT 265 | }; 266 | static const struct usb_string_descriptor_struct PROGMEM string3 = { 267 | sizeof(STR_SERIAL_NUMBER), 268 | 3, 269 | STR_SERIAL_NUMBER 270 | }; 271 | 272 | // This table defines which descriptor data is sent for each specific 273 | // request from the host (in wValue and wIndex). 274 | static const struct descriptor_list_struct { 275 | uint16_t wValue; 276 | uint16_t wIndex; 277 | const uint8_t *addr; 278 | uint8_t length; 279 | } PROGMEM descriptor_list[] = { 280 | {0x0100, 0x0000, device_descriptor, sizeof(device_descriptor)}, 281 | {0x0200, 0x0000, config1_descriptor, sizeof(config1_descriptor)}, 282 | {0x0300, 0x0000, (const uint8_t *)&string0, 4}, 283 | {0x0301, 0x0409, (const uint8_t *)&string1, sizeof(STR_MANUFACTURER)}, 284 | {0x0302, 0x0409, (const uint8_t *)&string2, sizeof(STR_PRODUCT)}, 285 | {0x0303, 0x0409, (const uint8_t *)&string3, sizeof(STR_SERIAL_NUMBER)} 286 | }; 287 | #define NUM_DESC_LIST (sizeof(descriptor_list)/sizeof(struct descriptor_list_struct)) 288 | 289 | 290 | /************************************************************************** 291 | * 292 | * Variables - these are the only non-stack RAM usage 293 | * 294 | **************************************************************************/ 295 | 296 | // zero when we are not configured, non-zero when enumerated 297 | static volatile uint8_t usb_configuration=0; 298 | 299 | // the time remaining before we transmit any partially full 300 | // packet, or send a zero length packet. 301 | static volatile uint8_t transmit_flush_timer=0; 302 | static uint8_t transmit_previous_timeout=0; 303 | 304 | // serial port settings (baud rate, control signals, etc) set 305 | // by the PC. These are ignored, but kept in RAM. 306 | static uint8_t cdc_line_coding[7]={0x00, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x08}; 307 | static uint8_t cdc_line_rtsdtr=0; 308 | 309 | 310 | /************************************************************************** 311 | * 312 | * Public Functions - these are the API intended for the user 313 | * 314 | **************************************************************************/ 315 | 316 | // initialize USB serial 317 | void usb_init(void) 318 | { 319 | HW_CONFIG(); 320 | USB_FREEZE(); // enable USB 321 | PLL_CONFIG(); // config PLL, 16 MHz xtal 322 | while (!(PLLCSR & (1< size) write_size = size; 538 | size -= write_size; 539 | 540 | // write the packet 541 | switch (write_size) { 542 | #if (CDC_TX_SIZE == 64) 543 | case 64: UEDATX = *buffer++; 544 | case 63: UEDATX = *buffer++; 545 | case 62: UEDATX = *buffer++; 546 | case 61: UEDATX = *buffer++; 547 | case 60: UEDATX = *buffer++; 548 | case 59: UEDATX = *buffer++; 549 | case 58: UEDATX = *buffer++; 550 | case 57: UEDATX = *buffer++; 551 | case 56: UEDATX = *buffer++; 552 | case 55: UEDATX = *buffer++; 553 | case 54: UEDATX = *buffer++; 554 | case 53: UEDATX = *buffer++; 555 | case 52: UEDATX = *buffer++; 556 | case 51: UEDATX = *buffer++; 557 | case 50: UEDATX = *buffer++; 558 | case 49: UEDATX = *buffer++; 559 | case 48: UEDATX = *buffer++; 560 | case 47: UEDATX = *buffer++; 561 | case 46: UEDATX = *buffer++; 562 | case 45: UEDATX = *buffer++; 563 | case 44: UEDATX = *buffer++; 564 | case 43: UEDATX = *buffer++; 565 | case 42: UEDATX = *buffer++; 566 | case 41: UEDATX = *buffer++; 567 | case 40: UEDATX = *buffer++; 568 | case 39: UEDATX = *buffer++; 569 | case 38: UEDATX = *buffer++; 570 | case 37: UEDATX = *buffer++; 571 | case 36: UEDATX = *buffer++; 572 | case 35: UEDATX = *buffer++; 573 | case 34: UEDATX = *buffer++; 574 | case 33: UEDATX = *buffer++; 575 | #endif 576 | #if (CDC_TX_SIZE >= 32) 577 | case 32: UEDATX = *buffer++; 578 | case 31: UEDATX = *buffer++; 579 | case 30: UEDATX = *buffer++; 580 | case 29: UEDATX = *buffer++; 581 | case 28: UEDATX = *buffer++; 582 | case 27: UEDATX = *buffer++; 583 | case 26: UEDATX = *buffer++; 584 | case 25: UEDATX = *buffer++; 585 | case 24: UEDATX = *buffer++; 586 | case 23: UEDATX = *buffer++; 587 | case 22: UEDATX = *buffer++; 588 | case 21: UEDATX = *buffer++; 589 | case 20: UEDATX = *buffer++; 590 | case 19: UEDATX = *buffer++; 591 | case 18: UEDATX = *buffer++; 592 | case 17: UEDATX = *buffer++; 593 | #endif 594 | #if (CDC_TX_SIZE >= 16) 595 | case 16: UEDATX = *buffer++; 596 | case 15: UEDATX = *buffer++; 597 | case 14: UEDATX = *buffer++; 598 | case 13: UEDATX = *buffer++; 599 | case 12: UEDATX = *buffer++; 600 | case 11: UEDATX = *buffer++; 601 | case 10: UEDATX = *buffer++; 602 | case 9: UEDATX = *buffer++; 603 | #endif 604 | case 8: UEDATX = *buffer++; 605 | case 7: UEDATX = *buffer++; 606 | case 6: UEDATX = *buffer++; 607 | case 5: UEDATX = *buffer++; 608 | case 4: UEDATX = *buffer++; 609 | case 3: UEDATX = *buffer++; 610 | case 2: UEDATX = *buffer++; 611 | default: 612 | case 1: UEDATX = *buffer++; 613 | case 0: break; 614 | } 615 | // if this completed a packet, transmit it now! 616 | if (!(UEINTX & (1<= NUM_DESC_LIST) { 805 | UECONX = (1< desc_length) len = desc_length; 827 | do { 828 | // wait for host ready for IN packet 829 | do { 830 | i = UEINTX; 831 | } while (!(i & ((1<= 1 && i <= MAX_ENDPOINT) { 919 | usb_send_in(); 920 | UENUM = i; 921 | if (bRequest == SET_FEATURE) { 922 | UECONX = (1<': chr(55), 160 | 'slash': chr(56), '/': chr(56), 'question': chr(56), '?': chr(56), 161 | 'caps_lock': chr(57), 'f1': chr(58), 'f2': chr(59), 'f3': chr(60), 162 | 'f4': chr(61), 'f5': chr(62), 'f6': chr(63), 'f7': chr(64), 'f8': chr(65), 163 | 'f9': chr(66), 'f10': chr(67), 'f11': chr(68), 'f12': chr(69), 164 | 'print': chr(70), 'sys_req': chr(70), 'scroll_lock': chr(71), 165 | 'pause': chr(72), 'break': chr(72), 'insert': chr(73), 'home': chr(74), 166 | 'page_up': chr(75), 'delete': chr(76), 'end': chr(77), 'page_down': chr(78), 167 | 'right': chr(79), 'left': chr(80), 'down': chr(81), 'up': chr(82), 168 | 'num_lock': chr(83), 'kp_divide': chr(84), 'kp_multiply': chr(85), 169 | 'kp_subtract': chr(86), 'kp_add': chr(87), 'kp_enter': chr(88), 170 | 'kp_1': chr(89), 'kp_end': chr(89), 'kp_2': chr(90), 'kp_down': chr(90), 171 | 'kp_3': chr(91), 'kp_page_down': chr(91), 'kp_4': chr(92), 172 | 'kp_left': chr(92), 'kp_5': chr(93), 'kp_begin': chr(93), 'kp_6': chr(94), 173 | 'kp_right': chr(94), 'kp_7': chr(95), 'kp_home': chr(95), 'kp_8': chr(96), 174 | 'kp_up': chr(96), 'kp_9': chr(97), 'kp_page_up': chr(97), 'kp_0': chr(98), 175 | 'kp_insert': chr(98), 'kp_decimal': chr(99), 'kp_delete': chr(99)} 176 | 177 | teensy_modkey_map = { 178 | 'none': 0x00, 179 | 'control_l': 0x01, 180 | #'control_r': 0x01, # used for ungrabbing mouse/keyboard 181 | 'shift_l': 0x02, 182 | 'shift_r': 0x02, 183 | 'alt_l': 0x04, 184 | 'alt_r': 0x04, 185 | 'super_l': 0x08, 186 | 'super_r': 0x08} 187 | 188 | teensy_mouse_button_map = { 189 | 0: 0x00, # none 190 | 1: 0x01, # left 191 | 2: 0x02, # middle 192 | 3: 0x04, # right 193 | 4: 0x08, # wheel up 194 | 5: 0x10} # wheel down 195 | 196 | # buttons on the VGA-to-TV scan line coverter 197 | sc_button_map = { 198 | "left": 0x01, 199 | "right": 0x02, 200 | "down": 0x04, 201 | "up": 0x08, 202 | "menu": 0x10, 203 | "zoom": 0x20} 204 | 205 | msg_type = {'keyboard': 0, 206 | 'keyboard_modkey': 1, 207 | 'mouse_button': 2, 208 | 'mouse_move': 3, 209 | 'mouse_wheel': 4, 210 | 'serial': 5, 211 | 'reset': 6, 212 | 'sc': 7} 213 | 214 | # setup default config attributes 215 | conf_file = os.path.join(os.getenv("HOME") ,".kvmc", "config") 216 | kvmc_device = "/dev/ttyACM0" 217 | capture_device = "/dev/video0" 218 | capture_device_input = "0" 219 | replay_no_delay = False 220 | record_init_mouse_pointer = True 221 | # setup runtime attributes 222 | serial_lock = threading.Lock() 223 | recording_file = None 224 | replay_file = None 225 | rec_file_header = "kvmc%s" % VERSION 226 | stop_replay = False 227 | 228 | def __init__(self): 229 | self._init_states() 230 | try: 231 | self.connect() 232 | except Exception, e: 233 | print "Error opening kvmc device: %s\n%s\nCheck config at %s" %\ 234 | (self.kvmc_device, e, self.conf_file) 235 | sys.exit(-1) 236 | 237 | def _init_states(self): 238 | # store the state of the 6 teensy keyboard values 239 | self.key_state = ["none", "none", "none", "none", "none", "none"] 240 | # store the values of the 4 teensy mod keys (ctrl, shift, alt, super) 241 | self.mod_key_state = ["none", "none", "none", "none"] 242 | # store the values of the 3 mouse buttons and scroll wheel 243 | self.mouse_button_state = [0, 0, 0, 0, 0] 244 | # store the values of mouse x and y 245 | self.mouse_position_state = [0, 0] 246 | # store the mouse wheel direction state 0, 1 -> up, down 247 | self.mouse_wheel_direction = 0 248 | 249 | def get_config(self, conf_file): 250 | if not os.path.exists(conf_file): 251 | self.set_config(conf_file) 252 | f = open(conf_file) 253 | conf_line = f.readlines() 254 | f.close() 255 | for line in conf_line: 256 | key, value = [i.strip() for i in line.split("=")] 257 | if key == "kvmc_device": 258 | self.kvmc_device = value 259 | elif key == "capture_device": 260 | self.capture_device = value 261 | elif key == "capture_device_input": 262 | self.capture_device_input = value 263 | elif key == "replay_no_delay": 264 | self.replay_no_delay = int(value) 265 | elif key == "record_init_mouse_pointer": 266 | self.record_init_mouse_pointer = int(value) 267 | 268 | def set_config(self, conf_file): 269 | conf_dir = os.path.dirname(conf_file) 270 | if not os.path.exists(conf_dir): 271 | os.mkdir(conf_dir) 272 | f = open(conf_file, "w") 273 | f.write("kvmc_device=%s\ncapture_device=%s\ncapture_device_input=%s\nreplay_no_delay=%s\nrecord_init_mouse_pointer=%s\n" % \ 274 | (self.kvmc_device, self.capture_device, self.capture_device_input, int(self.replay_no_delay), int(self.record_init_mouse_pointer))) 275 | f.close() 276 | 277 | def connect(self): 278 | self.get_config(self.conf_file) 279 | conn = serial.Serial(self.kvmc_device, 280 | baudrate=9600, 281 | bytesize=serial.EIGHTBITS, 282 | parity=serial.PARITY_NONE, 283 | stopbits=serial.STOPBITS_ONE, 284 | timeout=0, 285 | xonxoff=0, 286 | rtscts=0 287 | ) 288 | conn.setDTR(True) 289 | self.conn = conn 290 | self.send_reset() 291 | 292 | def _send(self, data): 293 | self.serial_lock.acquire() 294 | try: 295 | self.conn.write(data) 296 | if self.recording_file and not self.recording_file.closed: 297 | try: 298 | delay = time.time() - self.recording_timer 299 | self.recording_file.write(struct.pack('>f', delay)) 300 | self.recording_file.write(data) 301 | self.recording_timer = time.time() 302 | except IOError, e: 303 | print "Error writing to recording file '%s': %s" % (self.recording_file.name, e) 304 | sys.exit(-1) 305 | except serial.serialutil.SerialException, e: 306 | print "Error sending data to kvmc device: %s\n%s\nCheck config at %s" %\ 307 | (self.kvmc_device, e, self.conf_file) 308 | sys.exit(-1) 309 | self.serial_lock.release() 310 | 311 | def _send_keyboard_state(self): 312 | self._send(chr(self.msg_type["keyboard"])) 313 | # send the current keyboard state to the teensy 314 | for key_name in self.key_state: 315 | self._send(self.teensy_key_map[key_name]) 316 | 317 | def _send_keyboard_modkey_state(self): 318 | # create the mod_keys 319 | mod_keys = self.teensy_modkey_map[self.mod_key_state[0]] |\ 320 | self.teensy_modkey_map[self.mod_key_state[1]] |\ 321 | self.teensy_modkey_map[self.mod_key_state[2]] |\ 322 | self.teensy_modkey_map[self.mod_key_state[3]] 323 | self._send(chr((mod_keys << 3) | self.msg_type["keyboard_modkey"])) 324 | 325 | def _send_mouse_button_state(self): 326 | mouse_buttons = self.teensy_mouse_button_map[self.mouse_button_state[0]] |\ 327 | self.teensy_mouse_button_map[self.mouse_button_state[1]] |\ 328 | self.teensy_mouse_button_map[self.mouse_button_state[2]] |\ 329 | self.teensy_mouse_button_map[self.mouse_button_state[3]] |\ 330 | self.teensy_mouse_button_map[self.mouse_button_state[4]] 331 | self._send(chr( (mouse_buttons << 3) | self.msg_type["mouse_button"])) 332 | 333 | def _send_mouse_wheel_state(self): 334 | self._send(chr((self.mouse_wheel_direction << 3) | self.msg_type["mouse_wheel"])) 335 | 336 | def _send_mouse_move_state(self): 337 | self._send(chr(self.msg_type["mouse_move"])) 338 | self._send(struct.pack(">b",self.mouse_position_state[0])) 339 | self._send(struct.pack(">b",self.mouse_position_state[1])) 340 | 341 | def send_serial(self, byte): 342 | self._send(chr(self.msg_type["serial"])) 343 | self._send(byte) 344 | 345 | def send_reset(self): 346 | # send 6 reset packets incase we are in the middle of a multi-byte message, the 347 | # longest of which is 6 bytes (ie. the _send_keyboard_state message). If this is 348 | # the case (possible if a replay was stopped mid-stream) then this may cause 349 | # extraneous outout on the remote machine such as extra key presses. 350 | for i in range(7): 351 | self._send(chr(self.msg_type["reset"])) 352 | self._init_states() 353 | 354 | def press_sc_button(self, button): 355 | if button not in self.sc_button_map: 356 | print "Erorr: Unsupported scan line converter button: %s" % button 357 | return 358 | self._send(chr(self.msg_type["sc"])) 359 | self._send(chr(self.sc_button_map[button])) 360 | 361 | def press_key(self, key_name): 362 | key_name = key_name.lower() 363 | # bail if a non-supported key was pressed 364 | if key_name not in self.teensy_key_map and key_name not in self.teensy_modkey_map: 365 | print "Error Key '%s' not supported" % key_name 366 | return 367 | #if key currently pressed, reset teensy as we probably missed a gtk release event for some reason. 368 | if key_name in self.key_state or key_name in self.mod_key_state: 369 | self.send_reset() 370 | if key_name in self.teensy_key_map: 371 | if "none" in self.key_state: 372 | index = self.key_state.index("none") 373 | self.key_state[index] = key_name 374 | self._send_keyboard_state() 375 | else: 376 | print "Error: Max 6 simultaneous key presses exceeded, ignoring." 377 | elif key_name in self.teensy_modkey_map: 378 | index = self.mod_key_state.index("none") 379 | self.mod_key_state[index] = key_name 380 | self._send_keyboard_modkey_state() 381 | 382 | def release_key(self, key_name): 383 | key_name = key_name.lower() 384 | if key_name in self.key_state: 385 | index = self.key_state.index(key_name) 386 | self.key_state[index] = "none" 387 | self._send_keyboard_state() 388 | elif key_name in self.mod_key_state: 389 | index = self.mod_key_state.index(key_name) 390 | self.mod_key_state[index] = "none" 391 | self._send_keyboard_modkey_state() 392 | 393 | def press_mouse_button(self, mouse_button): 394 | # bail if a non-supported mouse button was pressed 395 | if mouse_button not in self.teensy_mouse_button_map: 396 | return 397 | # return if button is currently pressed 398 | if mouse_button in self.mouse_button_state: 399 | return 400 | index = self.mouse_button_state.index(0) 401 | self.mouse_button_state[index] = mouse_button 402 | self._send_mouse_button_state() 403 | 404 | def release_mouse_button(self, mouse_button): 405 | if mouse_button in self.mouse_button_state: 406 | index = self.mouse_button_state.index(mouse_button) 407 | self.mouse_button_state[index] = 0 408 | self._send_mouse_button_state() 409 | 410 | def move_mouse_wheel(self, direction): 411 | self.mouse_wheel_direction = direction 412 | self._send_mouse_wheel_state() 413 | 414 | def move_mouse(self, x, y): 415 | if -128 <= x <= 127 and -128 <= y <= 127: 416 | self.mouse_position_state = [x, y] 417 | self._send_mouse_move_state() 418 | self.mouse_position_state = [0, 0] 419 | 420 | def depress_key(self, char): 421 | self.press_key(char) 422 | time.sleep(.001) 423 | self.release_key(char) 424 | time.sleep(.001) 425 | 426 | def shift_depress_key(self, char): 427 | self.press_key("shift_l") 428 | time.sleep(.001) 429 | self.depress_key(char) 430 | self.release_key("shift_l") 431 | time.sleep(.001) 432 | 433 | def type_key(self, char): 434 | if char in [chr(i) for i in range(97, 123)]: # lowercase letter 435 | self.depress_key(char) 436 | elif char in [chr(i) for i in range(65, 91)]: # uppercase letter 437 | self.shift_depress_key(char) 438 | elif char in [chr(i) for i in range(48, 58)]: # number 439 | self.depress_key(char) 440 | elif char in ['`', '-', '=', '[', ']', '\\', ';', "'", ',', '.', '/', '\t', ' ', '\n']: 441 | self.depress_key(char) 442 | elif char in ['~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '{', '}', '|', ':', '"', '<', '>', '?']: 443 | self.shift_depress_key(char) 444 | else: 445 | print "Error: no supported keyboard key for character '%s' (Unicode code point/ASCII: %s)" % (char, ord(char)) 446 | 447 | def start_recording(self, filename): 448 | recording_file = open(filename, 'w') 449 | recording_file.write(self.rec_file_header) 450 | self.recording_timer = time.time() 451 | self.recording_file = recording_file 452 | if self.record_init_mouse_pointer: 453 | for i in range(10): 454 | self.move_mouse(-127, 127) 455 | 456 | def stop_recording(self): 457 | self.recording_file.close() 458 | return self.recording_file.name 459 | 460 | def is_not_rec_file(self, filename): 461 | try: 462 | f = open(filename, 'r') 463 | header = f.read(len(self.rec_file_header)) 464 | f.close() 465 | except Exception, e: 466 | return "Error reading file: %s" % e 467 | if not header in self.get_supported_rec_file_headers(): 468 | return "File is not a kvmc recording." 469 | 470 | def get_supported_rec_file_headers(self): 471 | return [self.rec_file_header] 472 | 473 | def replay(self, filename): 474 | self.replay_file = filename 475 | f = open(filename) 476 | f.read(len(self.rec_file_header)) 477 | while not self.stop_replay: 478 | try: 479 | delay = f.read(4) 480 | byte = f.read(1) 481 | except: 482 | print "Error: incorrectly formated kvmc recording file." 483 | sys.exit(-1) 484 | if not delay or not byte: 485 | break 486 | if not self.replay_no_delay: 487 | delay = struct.unpack('>f', delay)[0] 488 | time.sleep(delay) 489 | else: 490 | time.sleep(0.1) 491 | self._send(byte) 492 | if self.stop_replay: 493 | self.send_reset() 494 | self.stop_replay = False 495 | 496 | def close(self): 497 | self.conn.setDTR(False) 498 | self.conn.close() 499 | 500 | 501 | class KVMC_GUI(): 502 | 503 | grabbed_window = False 504 | mouse_x = None 505 | mouse_y = None 506 | mouse_update_timer = time.time() 507 | 508 | def __init__(self): 509 | # build the window 510 | self.display = gtk.gdk.Display(None) 511 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 512 | self.window.set_title("kvmc v%s" % VERSION) 513 | self.window.set_default_size(640, 480) 514 | self.window.set_position(gtk.WIN_POS_CENTER) 515 | agr = gtk.AccelGroup() 516 | self.window.add_accel_group(agr) 517 | mb = gtk.MenuBar() 518 | filemenu = gtk.Menu() 519 | editmenu = gtk.Menu() 520 | helpmenu = gtk.Menu() 521 | filem = gtk.MenuItem("_File") 522 | filem.set_submenu(filemenu) 523 | self.editm = gtk.MenuItem("_Edit") 524 | self.editm.set_submenu(editmenu) 525 | helpm = gtk.MenuItem("Help") 526 | helpm.set_submenu(helpmenu) 527 | self.record = gtk.ImageMenuItem(gtk.STOCK_MEDIA_RECORD, agr) 528 | self.record.set_label("Record Session Input...") 529 | key, mod = gtk.accelerator_parse("R") 530 | self.record.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) 531 | self.record.connect("activate", self.do_record) 532 | filemenu.append(self.record) 533 | self.replay = gtk.ImageMenuItem(gtk.STOCK_MEDIA_PLAY, agr) 534 | self.replay.set_label("Replay Session Input...") 535 | key, mod = gtk.accelerator_parse("P") 536 | self.replay.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) 537 | self.replay.connect("activate", self.do_replay) 538 | filemenu.append(self.replay) 539 | filemenu.append(gtk.SeparatorMenuItem()) 540 | exit = gtk.ImageMenuItem(gtk.STOCK_QUIT, agr) 541 | key, mod = gtk.accelerator_parse("Q") 542 | exit.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) 543 | exit.connect("activate", self.quit) 544 | filemenu.append(exit) 545 | paste = gtk.ImageMenuItem(gtk.STOCK_PASTE, agr) 546 | paste.set_label("Paste As Keystrokes") 547 | key, mod = gtk.accelerator_parse("V") 548 | paste.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) 549 | paste.connect("activate", self.paste) 550 | editmenu.append(paste) 551 | editmenu.append(gtk.SeparatorMenuItem()) 552 | control = gtk.ImageMenuItem(gtk.STOCK_FULLSCREEN, agr) 553 | control.set_label("Display Control") 554 | key, mod = gtk.accelerator_parse("O") 555 | control.add_accelerator("activate", agr, key, mod, gtk.ACCEL_VISIBLE) 556 | control.connect("activate", self.display_control) 557 | editmenu.append(control) 558 | self.setup = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES, agr) 559 | self.setup.connect("activate", self.preferences) 560 | editmenu.append(self.setup) 561 | about = gtk.ImageMenuItem(gtk.STOCK_ABOUT, agr) 562 | about.connect("activate", self.about) 563 | helpmenu.append(about) 564 | mb.append(filem) 565 | mb.append(self.editm) 566 | mb.append(helpm) 567 | vbox = gtk.VBox(False, 2) 568 | self.vbox = gtk.VBox() 569 | self.window.add(self.vbox) 570 | self.vbox.pack_start(mb, False, False, 0) 571 | self.movie_window = gtk.DrawingArea() 572 | self.vbox.add(self.movie_window) 573 | hbox = gtk.HBox() 574 | hbox.set_border_width(2) 575 | self.vbox.pack_start(hbox, False) 576 | hbox_left = gtk.HBox() 577 | serial_port_label = gtk.Label(" Serial Port: ") 578 | hbox_left.pack_start(serial_port_label, False) 579 | serial_event_box = gtk.EventBox() 580 | style = serial_event_box.get_style().copy() 581 | style.bg[gtk.STATE_NORMAL] = serial_event_box.get_colormap().alloc(0, 0, 0) 582 | serial_event_box.set_style(style) 583 | serial_event_box.show() 584 | self.serial_port_text = gtk.Label() 585 | style = self.serial_port_text.get_style().copy() 586 | style.fg[gtk.STATE_NORMAL] = self.serial_port_text.get_colormap().alloc(10000, 30000, 10000) 587 | self.serial_port_text.set_style(style) 588 | serial_event_box.add(self.serial_port_text) 589 | hbox_left.pack_start(serial_event_box, False) 590 | recorder_status_label = gtk.Label(" Recorder Status: ") 591 | hbox_left.pack_start(recorder_status_label, False) 592 | recorder_event_box = gtk.EventBox() 593 | style = recorder_event_box.get_style().copy() 594 | style.bg[gtk.STATE_NORMAL] = recorder_event_box.get_colormap().alloc(0, 0, 0) 595 | recorder_event_box.set_style(style) 596 | recorder_event_box.show() 597 | self.recorder_status_text = gtk.Label() 598 | style = self.recorder_status_text.get_style().copy() 599 | style.fg[gtk.STATE_NORMAL] = self.recorder_status_text.get_colormap().alloc(40000, 10000, 10000) 600 | self.recorder_status_text.set_style(style) 601 | recorder_event_box.add(self.recorder_status_text) 602 | hbox_left.pack_start(recorder_event_box, False) 603 | gtk.stock_add([(gtk.STOCK_MEDIA_RECORD, "_Stop", 0, 0, "")]) 604 | self.stop_button = gtk.Button(stock=gtk.STOCK_MEDIA_STOP) 605 | self.stop_button.set_sensitive(False) 606 | hbox_left.pack_start(self.stop_button, False) 607 | hbox_right = gtk.HBox() 608 | self.grab_text_label = gtk.Label("Click to control...") 609 | hbox_right.pack_end(self.grab_text_label, False) 610 | hbox.pack_start(hbox_left, True) 611 | hbox.pack_start(hbox_right, True) 612 | self.window.connect("key_press_event", self.key_press_event_handler) 613 | self.window.connect("key_release_event", self.key_release_event_handler) 614 | self.window.connect("button_press_event", self.button_press_event_handler) 615 | self.window.connect("button_release_event", self.button_release_event_handler) 616 | self.window.connect("scroll-event", self.button_press_event_handler) 617 | self.window.connect("delete_event", self.quit) 618 | self.window.connect_after("motion_notify_event", self.motion_notify_event_handler) 619 | self.movie_window.connect("expose_event", self.expose_event_handler) 620 | self.all_events = gtk.gdk.KEY_PRESS_MASK \ 621 | | gtk.gdk.KEY_RELEASE_MASK \ 622 | | gtk.gdk.BUTTON_PRESS_MASK \ 623 | | gtk.gdk.BUTTON_RELEASE_MASK \ 624 | | gtk.gdk.SCROLL_MASK \ 625 | | gtk.gdk.POINTER_MOTION_MASK \ 626 | | gtk.gdk.POINTER_MOTION_HINT_MASK \ 627 | | gtk.gdk.EXPOSURE_MASK 628 | self.window.set_events(self.all_events) 629 | self.window.show_all() 630 | # create a teensy connection object 631 | self.teensy = Teensy_Connection() 632 | self.serial_thread = Serial_Worker(self.teensy, self.serial_port_text) 633 | self.serial_thread.start() 634 | self.replay_thread = Replay_Worker(self.teensy, self) 635 | self.replay_thread.start() 636 | # Set up video 637 | self.mplayer_fd = None 638 | self.mplayer_pid = None 639 | self.init_video() 640 | self.stop_button_handler = None 641 | # create an invisible mouse cursor for passing to pointer_grab 642 | pix_data = """/* XPM */ 643 | static char * invisible_xpm[] = { "1 1 1 1", "c None", " "};""" 644 | color = gtk.gdk.Color() 645 | sys.stderr = open("/dev/null") # suppress gtk error 646 | pix = gtk.gdk.pixmap_create_from_data(None, pix_data, 1, 1, 1, color, color) 647 | sys.stderr = sys.__stderr__ 648 | self.invisible = gtk.gdk.Cursor(pix, pix, color, color, 0, 0) 649 | 650 | def init_video(self): 651 | if self.mplayer_pid: 652 | os.close(self.mplayer_fd) 653 | self.mplayer_fd = None 654 | os.kill(self.mplayer_pid, signal.SIGTERM) 655 | os.waitpid(self.mplayer_pid, 0) 656 | xid = self.movie_window.window.xid 657 | command = "mplayer -tv device=%s:input=%s:driver=v4l2 -wid %i -quiet tv://" % \ 658 | (self.teensy.capture_device, self.teensy.capture_device_input, xid) 659 | command = shlex.split(command) 660 | pid, self.mplayer_fd = pty.fork() 661 | if pid == 0: 662 | # Child/Slave 663 | try: 664 | os.execlp(command[0], *command) 665 | except OSError, e: 666 | raise Exception("Could not start 'mplayer': %s" % e) 667 | sys.exit(-1) 668 | # Parent/Master 669 | self.mplayer_pid = pid 670 | 671 | def about(self, *args): 672 | about = gtk.AboutDialog() 673 | about.set_program_name("kvmc") 674 | about.set_version("%s" % VERSION) 675 | about.set_copyright("Copyright (c) 2012 George Murdocca") 676 | about.set_comments("A USB-to-KVM application that provides direct KVM control of a headless computer using a kvmc device, including paste-as-keystrokes, serial communications and session recording.") 677 | about.set_license(LICENSE) 678 | about.set_website("https://github.com/gmurdocca/kvmc") 679 | about.run() 680 | about.destroy() 681 | 682 | def paste(self, *args): 683 | wrap_checkbox_text = "Wrap Text" 684 | serial_checkbox_text = "Paste Windows Serial Driver" 685 | cb = gtk.Clipboard() 686 | cb_text = cb.wait_for_text() or "" 687 | if type(args[0]) == type(gtk.CheckButton()): 688 | if args[0].get_label() == wrap_checkbox_text: 689 | if args[0].get_active(): 690 | self.texteditor.set_wrap_mode(gtk.WRAP_WORD) 691 | else: 692 | self.texteditor.set_wrap_mode(gtk.WRAP_NONE) 693 | elif args[0].get_label() == serial_checkbox_text: 694 | if args[0].get_active(): 695 | self.textbuffer.set_text(WINDOWS_USB_SERIAL_DRIVER_INF) 696 | else: 697 | self.textbuffer.set_text(cb_text) 698 | else: 699 | dialog = gtk.Dialog("Paste Clipboard as Keystrokes", 700 | self.window, 701 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 702 | (gtk.STOCK_PASTE, 1, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) 703 | dialog.set_default_size(480, 360) 704 | 705 | paste_text_label = gtk.Label("Paste text:") 706 | paste_text_label.set_alignment(0, 0) 707 | dialog.vbox.pack_start(paste_text_label, False) 708 | 709 | table = gtk.Table(rows=3, columns=1, homogeneous=False) 710 | table.set_row_spacing(0, 2) 711 | table.set_col_spacing(0, 2) 712 | dialog.vbox.pack_start(table, gtk.TRUE, gtk.TRUE, 0) 713 | text_vbox = gtk.VBox() 714 | texteditorsw = gtk.ScrolledWindow() 715 | texteditorsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 716 | self.texteditor = gtk.TextView(buffer=None) 717 | self.textbuffer = self.texteditor.get_buffer() 718 | self.textbuffer.set_text(cb_text) 719 | self.texteditor.set_editable(False) 720 | self.texteditor.set_justification(gtk.JUSTIFY_LEFT) 721 | texteditorsw.add(self.texteditor) 722 | texteditorsw.show() 723 | self.texteditor.show() 724 | text_vbox.pack_start(texteditorsw) 725 | table.attach(text_vbox, 0, 1, 0, 1, 726 | gtk.EXPAND | gtk.SHRINK | gtk.FILL, 727 | gtk.EXPAND | gtk.SHRINK | gtk.FILL, 0, 0) 728 | unsupported_char_label = gtk.Label("Note: Unsupported characters will be ignored.") 729 | style = unsupported_char_label.get_style().copy() 730 | style.fg[gtk.STATE_NORMAL] = unsupported_char_label.get_colormap().alloc(40000, 0, 0) 731 | unsupported_char_label.set_style(style) 732 | table.attach(unsupported_char_label, 0, 1, 1, 2, gtk.FILL, gtk.FILL, 0, 0) 733 | hbox = gtk.HBox() 734 | serial_checkbox = gtk.CheckButton(label=serial_checkbox_text) 735 | serial_checkbox.connect("toggled", self.paste, dialog) 736 | wrap_checkbox = gtk.CheckButton(label=wrap_checkbox_text) 737 | wrap_checkbox.connect("toggled", self.paste, dialog) 738 | wrap_checkbox.set_active(True) 739 | hbox.pack_start(wrap_checkbox, False) 740 | hbox.pack_end(serial_checkbox, False) 741 | table.attach(hbox, 0, 1, 2, 3, gtk.FILL, gtk.FILL, 0, 0) 742 | dialog.show_all() 743 | while True: 744 | result = dialog.run() 745 | if result == 1: 746 | text_buffer = self.textbuffer.get_text(self.textbuffer.get_start_iter(), self.textbuffer.get_end_iter()) 747 | for char in text_buffer: 748 | self.teensy.type_key(char) 749 | break 750 | else: 751 | break 752 | dialog.destroy() 753 | 754 | def do_record(self, *args): 755 | chooser = gtk.FileChooserDialog(title="Record Session Input...", action=gtk.FILE_CHOOSER_ACTION_SAVE, 756 | buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 757 | gtk.STOCK_SAVE, gtk.RESPONSE_OK)) 758 | cwd = "." 759 | if self.teensy.recording_file: 760 | cwd = os.path.dirname(self.teensy.recording_file.name) 761 | chooser.set_current_folder(cwd) 762 | chooser.set_current_name("recording.kvmc") 763 | chooser.set_default_response(gtk.RESPONSE_OK) 764 | chooser.set_do_overwrite_confirmation(True) 765 | response = chooser.run() 766 | if response == gtk.RESPONSE_OK: 767 | self.record.set_sensitive(False) 768 | self.replay.set_sensitive(False) 769 | self.setup.set_sensitive(False) 770 | if self.stop_button_handler: 771 | self.stop_button.disconnect(self.stop_button_handler) 772 | self.stop_button_handler = self.stop_button.connect("clicked", self.do_stop, "record") 773 | self.stop_button.set_sensitive(True) 774 | selected_file = chooser.get_filename() 775 | self.rec_playback_path = os.path.dirname(selected_file) 776 | self.teensy.start_recording(chooser.get_filename()) 777 | self.recorder_status_text.set_text(" Recording ") 778 | chooser.destroy() 779 | 780 | def do_stop(self, *args): 781 | target = args[1] 782 | self.stop_button.set_sensitive(False) 783 | self.record.set_sensitive(True) 784 | self.replay.set_sensitive(True) 785 | self.setup.set_sensitive(True) 786 | if target == "record": 787 | self.recorder_status_text.set_text(" Idle ") 788 | filename = self.teensy.stop_recording() 789 | md = gtk.MessageDialog(self.window, 790 | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, 791 | gtk.BUTTONS_CLOSE, "Recording saved to file:\n%s" % filename) 792 | md.run() 793 | md.destroy() 794 | elif target == "replay": 795 | self.teensy.stop_replay = True 796 | 797 | 798 | def do_replay(self, *args): 799 | chooser = gtk.FileChooserDialog(title="Replay Session Input...", action=gtk.FILE_CHOOSER_ACTION_OPEN, 800 | buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 801 | gtk.STOCK_OPEN, gtk.RESPONSE_OK)) 802 | chooser.set_default_response(gtk.RESPONSE_OK) 803 | cwd = "." 804 | if self.teensy.replay_file: 805 | cwd = os.path.dirname(self.teensy.replay_file) 806 | chooser.set_current_folder(cwd) 807 | filter = gtk.FileFilter() 808 | filter.set_name("All files") 809 | filter.add_pattern("*") 810 | chooser.add_filter(filter) 811 | filter = gtk.FileFilter() 812 | filter.set_name("kvmc recordings") 813 | filter.add_pattern("*.kvmc") 814 | chooser.add_filter(filter) 815 | response = chooser.run() 816 | replay_file = None 817 | if response == gtk.RESPONSE_OK: 818 | replay_file = chooser.get_filename() 819 | chooser.destroy() 820 | if not replay_file: 821 | return 822 | error_msg = self.teensy.is_not_rec_file(replay_file) 823 | if error_msg: 824 | md = gtk.MessageDialog(self.window, 825 | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, 826 | gtk.BUTTONS_CLOSE, error_msg) 827 | md.run() 828 | md.destroy() 829 | return 830 | md = gtk.Dialog("Confirm Replay", self.window, 831 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 832 | (gtk.STOCK_YES, gtk.RESPONSE_OK, gtk.STOCK_NO, gtk.RESPONSE_CLOSE)) 833 | md.set_resizable(False) 834 | msg = "Are you sure you want to replay session input from file:\n'%s'?" % os.path.basename(replay_file) 835 | md.vbox.pack_start(gtk.Label(msg), False) 836 | md.show_all() 837 | response = md.run() 838 | if response == gtk.RESPONSE_OK: 839 | self.replay_thread.replay_file = replay_file 840 | md.destroy() 841 | 842 | def preferences(self, *args): 843 | if type(args[0]) != type(gtk.ImageMenuItem()): 844 | args[1].response(args[2]) 845 | else: 846 | dialog = gtk.Dialog("Preferences", 847 | self.window, 848 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 849 | (gtk.STOCK_SAVE, 1, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) 850 | dialog.set_resizable(False) 851 | table = gtk.Table(rows=5, columns=2, homogeneous=False) 852 | kvmc_in_label = gtk.Label("kvmc_in device:") 853 | kvmc_in_label.set_alignment(1, .5) 854 | table.attach(kvmc_in_label, 0, 1, 0, 1) 855 | kvmc_device_entry = gtk.Entry() 856 | kvmc_device_entry.connect("activate", self.preferences, dialog, 1) 857 | kvmc_device_entry.set_text(self.teensy.kvmc_device) 858 | table.attach(kvmc_device_entry, 1, 2, 0, 1) 859 | capture_label = gtk.Label("capture device:") 860 | capture_label.set_alignment(1, .5) 861 | table.attach(capture_label, 0, 1, 1, 2) 862 | capture_device_entry = gtk.Entry() 863 | capture_device_entry.connect("activate", self.preferences, dialog, 1) 864 | capture_device_entry.set_text(self.teensy.capture_device) 865 | table.attach(capture_device_entry, 1, 2, 1, 2) 866 | capture_input_label = gtk.Label("capture device input:") 867 | capture_input_label.set_alignment(1, .5) 868 | table.attach(capture_input_label, 0, 1, 2, 3) 869 | capture_device_input_entry = gtk.Entry() 870 | capture_device_input_entry.connect("activate", self.preferences, dialog, 1) 871 | capture_device_input_entry.set_text(self.teensy.capture_device_input) 872 | table.attach(capture_device_input_entry, 1, 2, 2, 3) 873 | replay_speed_label = gtk.Label("Replay sessions at high speed (experimental):") 874 | replay_speed_label.set_alignment(1, .5) 875 | table.attach(replay_speed_label, 0, 1, 3, 4) 876 | replay_no_delay_checkbox = gtk.CheckButton(label=None) 877 | replay_no_delay_checkbox.set_active(self.teensy.replay_no_delay) 878 | table.attach(replay_no_delay_checkbox, 1,2,3,4) 879 | init_mouse_label = gtk.Label("Move mouse to bottom left on recording start:") 880 | init_mouse_label.set_alignment(1, .5) 881 | table.attach(init_mouse_label, 0, 1, 4, 5) 882 | record_init_mouse_pointer_checkbox = gtk.CheckButton(label=None) 883 | record_init_mouse_pointer_checkbox.set_active(self.teensy.record_init_mouse_pointer) 884 | table.attach(record_init_mouse_pointer_checkbox, 1,2,4,5) 885 | dialog.vbox.add(table) 886 | dialog.show_all() 887 | while True: 888 | result = dialog.run() 889 | if result == 1: 890 | kvmc_device_text = kvmc_device_entry.get_text() 891 | capture_device_text = capture_device_entry.get_text() 892 | capture_device_input_text = capture_device_input_entry.get_text() 893 | setup_error = None 894 | if not os.path.exists(kvmc_device_text): 895 | setup_error = "Specified kvmc device doesn't exist" 896 | elif not os.path.exists(capture_device_text): 897 | setup_error = "Specified capture device doesn't exist" 898 | try: 899 | capture_device_input_text = int(capture_device_input_text) 900 | except ValueError: 901 | setup_error = "Capture device input must be an integer" 902 | if setup_error: 903 | md = gtk.MessageDialog(self.window, 904 | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, 905 | gtk.BUTTONS_CLOSE, setup_error) 906 | md.run() 907 | md.destroy() 908 | else: 909 | self.teensy.kvmc_device = kvmc_device_text 910 | self.teensy.capture_device = capture_device_text 911 | self.teensy.capture_device_input = capture_device_input_text 912 | self.teensy.replay_no_delay = replay_no_delay_checkbox.get_active() 913 | self.teensy.record_init_mouse_pointer = record_init_mouse_pointer_checkbox.get_active() 914 | self.teensy.set_config(self.teensy.conf_file) 915 | self.serial_thread.pause = True 916 | self.teensy.close() 917 | self.teensy.__init__() 918 | self.init_video() 919 | self.serial_thread.pause = False 920 | break 921 | else: 922 | break 923 | dialog.destroy() 924 | 925 | def display_control(self, *args): 926 | dialog = gtk.Dialog("Display Control", 927 | self.window, 928 | gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 929 | (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) 930 | dialog.set_resizable(False) 931 | sc_table = gtk.Table(rows=2, columns=3, homogeneous=True) 932 | sc_up = gtk.Button(stock=gtk.STOCK_GO_UP) 933 | sc_down = gtk.Button(stock=gtk.STOCK_GO_DOWN) 934 | sc_left = gtk.Button(stock=gtk.STOCK_GO_BACK) 935 | sc_right = gtk.Button(stock=gtk.STOCK_GO_FORWARD) 936 | sc_menu = gtk.Button(label="menu") 937 | sc_zoom = gtk.Button(label="zoom") 938 | sc_up.connect("clicked", self.press_sc_button, "up") 939 | sc_down.connect("clicked", self.press_sc_button, "down") 940 | sc_left.connect("clicked", self.press_sc_button, "left") 941 | sc_right.connect("clicked", self.press_sc_button, "right") 942 | sc_menu.connect("clicked", self.press_sc_button, "menu") 943 | sc_zoom.connect("clicked", self.press_sc_button, "zoom") 944 | sc_table.attach(sc_up, 1, 2, 0, 1) 945 | sc_table.attach(sc_down, 1, 2, 1, 2) 946 | sc_table.attach(sc_left, 0, 1, 1, 2) 947 | sc_table.attach(sc_right, 2, 3, 1, 2) 948 | sc_table.attach(sc_menu, 0, 1, 0, 1) 949 | sc_table.attach(sc_zoom, 2, 3, 0, 1) 950 | dialog.vbox.add(sc_table) 951 | dialog.show_all() 952 | dialog.run() 953 | dialog.destroy() 954 | 955 | def press_sc_button(self, *args): 956 | button = args[1] 957 | self.teensy.press_sc_button(button) 958 | 959 | def quit(self, *args): 960 | self.serial_thread.quit() 961 | self.replay_thread.quit() 962 | gtk.main_quit() 963 | 964 | def main(self): 965 | signal.signal(signal.SIGCHLD, self.handle_sigchld) 966 | gobject.threads_init() 967 | gtk.main() 968 | 969 | def handle_sigchld(self, signal_number, stack_frame): 970 | try: 971 | os.waitpid(-1, os.WNOHANG) 972 | except OSError: 973 | pass 974 | if self.mplayer_fd: 975 | mplayer_output = os.read(self.mplayer_fd, 1000) 976 | print mplayer_output 977 | print "Error: 'mplayer' unexpectedly closed. See above output for details and/or check config at %s" \ 978 | % self.teensy.conf_file 979 | self.quit() 980 | 981 | def centre_mouse_in_widget(self, widget): 982 | top_left_x, top_left_y = self.window.get_position() 983 | centre_x = top_left_x + (widget.get_allocation().width / 2) 984 | centre_y = top_left_y + (widget.get_allocation().height / 2) 985 | gtk.gdk.Display.warp_pointer(self.display, self.display.get_default_screen(), centre_x, centre_y) 986 | 987 | def key_press_event_handler(self, widget, event): 988 | if self.grabbed_window: 989 | key_name = gtk.gdk.keyval_name(event.keyval) 990 | if key_name.lower() == "control_r": 991 | gtk.gdk.pointer_ungrab() 992 | gtk.gdk.keyboard_ungrab() 993 | self.grab_text_label.set_text("Click to control...") 994 | self.teensy.send_reset() 995 | self.grabbed_window = False 996 | else: 997 | self.teensy.press_key(key_name) 998 | return True 999 | return False 1000 | 1001 | def key_release_event_handler(self, widget, event): 1002 | if self.grabbed_window: 1003 | key_name = gtk.gdk.keyval_name(event.keyval) 1004 | self.teensy.release_key(key_name) 1005 | return True 1006 | 1007 | def button_press_event_handler(self, widget, event): 1008 | event_attrs = dir(event) 1009 | if not self.grabbed_window and not self.replay_thread.replay_file: 1010 | if "button" in event_attrs and event.button == 1: 1011 | self.teensy.send_reset() 1012 | # lock mouse cursor to our window and hide it 1013 | gtk.gdk.pointer_grab(widget.window, 1014 | owner_events=True, 1015 | event_mask=0, 1016 | confine_to=widget.window, 1017 | cursor=self.invisible) 1018 | # lock keybord events to our window 1019 | gtk.gdk.keyboard_grab(widget.window, owner_events=False) 1020 | # lock mouse location to centre of window and store x, y 1021 | self.centre_mouse_in_widget(widget) 1022 | # update the toolbar 1023 | self.grab_text_label.set_text("Right CTRL to release mouse...") 1024 | self.grabbed_window = True 1025 | else: 1026 | if "button" in event_attrs: 1027 | self.teensy.press_mouse_button(event.button) 1028 | elif "direction" in event_attrs: 1029 | if event.direction == gtk.gdk.SCROLL_UP: 1030 | self.teensy.move_mouse_wheel(0) 1031 | elif event.direction == gtk.gdk.SCROLL_DOWN: 1032 | self.teensy.move_mouse_wheel(1) 1033 | return True 1034 | 1035 | def button_release_event_handler(self, widget, event): 1036 | if self.grabbed_window: 1037 | self.teensy.release_mouse_button(event.button) 1038 | return True 1039 | 1040 | def motion_notify_event_handler(self, widget, event): 1041 | if self.grabbed_window: 1042 | if not self.mouse_x or not self.mouse_y: 1043 | # initialise mouse x,y on first event 1044 | self.mouse_x = event.x 1045 | self.mouse_y = event.y 1046 | elif (time.time() - self.mouse_update_timer) > .01: 1047 | self.mouse_update_timer = time.time() 1048 | dist_x = (self.mouse_x - event.x) * -1 1049 | dist_y = (self.mouse_y - event.y) * -1 1050 | if dist_x + dist_y != 0: 1051 | self.centre_mouse_in_widget(widget) 1052 | if dist_x > 127: dist_x = 127 1053 | if dist_x < -127: dist_x = -127 1054 | if dist_y > 127: dist_y = 127 1055 | if dist_y < -127: dist_y = -127 1056 | self.teensy.move_mouse(dist_x, dist_y) 1057 | return True 1058 | 1059 | def expose_event_handler(self, widget, event): 1060 | self.mouse_x = None 1061 | self.mouse_y = None 1062 | 1063 | 1064 | WINDOWS_USB_SERIAL_DRIVER_INF = \ 1065 | """; Windows INF to load usbser driver for all CDC-ACM USB Serial Ports 1066 | ; Copyright (C) 2008 PJRC.COM, LLC. 1067 | ; Save as filename: cdc_acm_class.inf 1068 | 1069 | [Version] 1070 | Signature="$Windows NT$" 1071 | Class=Ports 1072 | ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318} 1073 | Provider=%MFGNAME% 1074 | DriverVer=01/01/2008,1.0.0.0 1075 | [Manufacturer] 1076 | %MFGNAME%=DeviceList, NTamd64 1077 | [DeviceList] 1078 | %DEVNAME%=DriverInstall,USB\Class_02&SubClass_02&Prot_01 1079 | [DeviceList.NTamd64] 1080 | %DEVNAME%=DriverInstall,USB\Class_02&SubClass_02&Prot_01 1081 | [DeviceList.NTia64] 1082 | %DEVNAME%=DriverInstall,USB\Class_02&SubClass_02&Prot_01 1083 | [SourceDisksNames] 1084 | 1=%CD1NAME% 1085 | [SourceDisksFiles] 1086 | [DestinationDirs] 1087 | DefaultDestDir=12 1088 | [DriverInstall] 1089 | Include=mdmcpq.inf 1090 | CopyFiles=FakeModemCopyFileSection 1091 | AddReg=DriverAddReg 1092 | [DriverAddReg] 1093 | HKR,,EnumPropPages32,,"msports.dll,SerialPortPropPageProvider" 1094 | [DriverInstall.Services] 1095 | Include=mdmcpq.inf 1096 | AddService=usbser,0x00000002,LowerFilter_Service_Inst 1097 | [Strings] 1098 | MFGNAME="PJRC.COM, LLC." 1099 | DEVNAME="USB Serial (Communication Class, Abstract Control Model)" 1100 | CD1NAME="No CDROM required, usbser.sys is provided by Windows" 1101 | """ 1102 | 1103 | if __name__ == "__main__": 1104 | app = KVMC_GUI() 1105 | app.main() 1106 | 1107 | --------------------------------------------------------------------------------