Skip to content

Instantly share code, notes, and snippets.

@cs8425
Last active February 1, 2024 15:26
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save cs8425/d166d4dd640129581f4e27bab8cc0706 to your computer and use it in GitHub Desktop.
Save cs8425/d166d4dd640129581f4e27bab8cc0706 to your computer and use it in GitHub Desktop.
simple keyboard & mouse binding over web in golang
// build:
// GOOS=windows GOARCH=386 go build -o keyemu.exe keyemu.go
// GOOS=windows GOARCH=amd64 go build -o keyemu-x64.exe keyemu.go
// pre-build: https://mega.nz/#F!c9M0VaqK!sjUh5jwAU1tYkVv8z9YqaQ
// ref: https://github.com/micmonay/keybd_event
package main
import (
"io"
"io/ioutil"
"net/http"
"syscall"
// "time"
"fmt"
"flag"
"strconv"
"strings"
)
var localAddr = flag.String("l", ":9999", "")
var (
moduser32 = syscall.NewLazyDLL("user32.dll")
procKeyBd = moduser32.NewProc("keybd_event")
procMouse = moduser32.NewProc("mouse_event")
)
var html = `<!doctype html>
<head>
<title>XD</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
<style>
html, body {
height: 100%;
margin: 0px;
}
body > div {
float: left;
width: 50%;
height: 100%;
}
#mouse {
border: 2px solid #000;
height: calc(100% - 4px);
}
#btns {
margin: 8px;
height: calc(100% - 16px);
}
button {
border: 1px solid gray;
padding: 5px 10px;
border-radius: 5px;
background-color: #efefef;
}
</style>
</head>
<body>
<div>
<div id="mouse"></div>
</div>
<div>
<div id="btns">
<button id="mouseL">mouseL</button>
<button id="mouseR">mouseR</button>
<button id="up">^</button>
<button id="down">V</button>
<button id="left">&lt;</button>
<button id="right">&gt;</button>
<button id="esc">esc</button>
<button id="ent">enter</button>
<button id="tab">Tab</button>
<button id="altf4">Alt-F4</button>
<button id="ctrlc">Ctrl-C</button>
<button id="alt-tab">Alt-Tab</button>
</div>
</div>
</body>
<script type="text/javascript">
var isdrag = false;
var t = null;
var pos = {};
var queue = [];
var delaypost = null;
var mousemove = function(){
delaypost = setTimeout(mousemove, 50);
if(queue.length == 0) return;
var out = '';
for(var i=0; i<queue.length; i++){
var dx = queue[i][0];
var dy = queue[i][1];
var part = queue.length / 4;
for(var j=0; j<part && i<queue.length-1; j++){
dx += queue[++i][0];
dy += queue[i][1];
}
out += Math.round(dx) + ',' + Math.round(dy) + '\n';
}
// console.log('mousemove', out);
$.post('/mousemove', out, null, 'text');
queue = [];
}
$('#mouse').bind('mousedown touchstart', function(e){
isdrag = true;
t = new Date();
if(typeof e.touches != 'undefined'){
pos.x = e.touches[0].clientX;
pos.y = e.touches[0].clientY;
}else{
pos.x = e.clientX;
pos.y = e.clientY;
}
}).bind('mouseup touchend', function(e){
isdrag = false;
}).bind('mousemove touchmove', function(e){
if(!isdrag) return;
e.preventDefault();
var x,y;
if(typeof e.touches != 'undefined'){
x = e.touches[0].clientX;
y = e.touches[0].clientY;
}else{
x = e.clientX;
y = e.clientY;
}
var dx = x - pos.x;
var dy = y - pos.y;
pos.x = x;
pos.y = y;
queue.push([dx, dy]);
if(!delaypost) delaypost = setTimeout(mousemove, 50);
}).bind('click', function(e){
if(((new Date()) - t) > 120) return;
$.get('/mouseL');
});
var bindlist = ['up', 'down', 'left', 'right', 'esc', 'ent', 'tab', 'altf4', 'ctrlc', 'alt-tab', 'mouseL', 'mouseR'];
for(idx in bindlist){
var ele = bindlist[idx];
(function(ele){
$('#' + ele).bind('click', function(e){$.get('/' + ele)});
})(ele);
}
</script>
</html>`
var keymap map[string]int = map[string]int{
"/up": VK_UP,
"/down": VK_DOWN,
"/left": VK_LEFT,
"/right": VK_RIGHT,
"/esc": VK_ESCAPE,
"/ent": VK_RETURN,
"/tab": VK_TAB,
}
func mousemvs(data io.ReadCloser) {
defer data.Close()
body, err := ioutil.ReadAll(data)
// fmt.Printf("%v:%v\n", err, body)
if err != nil {
return
}
mvs := strings.Split(string(body), "\n")
for _, line := range mvs {
d := strings.Split(line, ",")
dx, err := strconv.ParseInt(d[0], 10, 32)
if err != nil {
return
}
dy, err := strconv.ParseInt(d[1], 10, 32)
if err != nil {
return
}
mouse_move(false, int32(dx), int32(dy))
}
}
func keys(w http.ResponseWriter, r *http.Request) {
key, ok := keymap[r.URL.Path]
if ok {
fmt.Printf("got key: %v\n", r.URL.Path)
sendkey(key)
io.WriteString(w, r.URL.Path + ", ok")
} else {
switch r.URL.Path {
case "/altf4":
fmt.Printf("got alt-F4\n")
downKey(_VK_ALT)
sendkey(VK_F4)
upKey(_VK_ALT)
case "/ctrlc":
fmt.Printf("got ctrl-C\n")
downKey(_VK_CTRL)
sendkeys("C")
upKey(_VK_CTRL)
case "/alt-tab":
fmt.Printf("got alt-tab\n")
downKey(_VK_ALT)
sendkey(VK_TAB)
upKey(_VK_ALT)
case "/mousemove":
// fmt.Printf("got mousemove event\n")
mousemvs(r.Body)
case "/mouseL":
fmt.Printf("got mouseL event\n")
mouse_click(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP)
case "/mouseR":
fmt.Printf("got mouseR event\n")
mouse_click(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP)
default:
io.WriteString(w, html)
}
}
}
func main() {
flag.Parse()
fmt.Printf("server Listen @ %v\n", *localAddr)
http.HandleFunc("/", keys)
http.ListenAndServe(*localAddr, nil)
}
func mouse_click(intype uint32) {
procMouse.Call(uintptr(intype), 0, 0, 0, 0)
}
func mouse_move(abs bool, x int32, y int32) {
var intype uint32 = MOUSEEVENTF_MOVE
if abs {
intype |= MOUSEEVENTF_ABSOLUTE
}
procMouse.Call(uintptr(intype), uintptr(x), uintptr(y), 0, 0)
}
func mouse_wheel(delta int32, vh bool) {
var intype uint32
if vh {
intype = MOUSEEVENTF_HWHEEL
} else {
intype = MOUSEEVENTF_WHEEL
}
procMouse.Call(uintptr(intype), 0, 0, uintptr(delta), 0)
}
const (
MOUSEEVENTF_ABSOLUTE = 0x8000;
MOUSEEVENTF_MOVE = 0x0001;
MOUSEEVENTF_LEFTDOWN = 0x0002;
MOUSEEVENTF_LEFTUP = 0x0004;
MOUSEEVENTF_MIDDLEDOWN = 0x0020;
MOUSEEVENTF_MIDDLEUP = 0x0040;
MOUSEEVENTF_RIGHTDOWN = 0x0008;
MOUSEEVENTF_RIGHTUP = 0x0010;
MOUSEEVENTF_WHEEL = 0x0800;
MOUSEEVENTF_HWHEEL = 0x01000;
MOUSEEVENTF_XDOWN = 0x0080;
MOUSEEVENTF_XUP = 0x0100;
)
func sendkey(vk int) {
downKey(vk)
upKey(vk)
}
func sendkeys(str string) {
for _, s := range str {
if s >= 'a' && s <= 'z' {
s -= 'a' - 'A'
}
fmt.Printf("sendkey(): %x\n", int(s))
downKey(int(s))
upKey(int(s))
}
}
func downKey(key int) {
vkey := key + 0x80
procKeyBd.Call(uintptr(key), uintptr(vkey), 0, 0)
}
func upKey(key int) {
vkey := key + 0x80
procKeyBd.Call(uintptr(key), uintptr(vkey), _KEYEVENTF_KEYUP, 0)
}
const (
_VK_SHIFT = 0x10
_VK_CTRL = 0x11
_VK_ALT = 0x12
_VK_LSHIFT = 0xA0
_VK_RSHIFT = 0xA1
_VK_LCONTROL = 0xA2
_VK_RCONTROL = 0xA3
_KEYEVENTF_KEYUP = 0x0002
)
// Virtual-Key Codes
const (
VK_LBUTTON = 0x01
VK_RBUTTON = 0x02
VK_CANCEL = 0x03
VK_MBUTTON = 0x04
VK_XBUTTON1 = 0x05
VK_XBUTTON2 = 0x06
VK_BACK = 0x08
VK_TAB = 0x09
VK_CLEAR = 0x0C
VK_RETURN = 0x0D
VK_SHIFT = 0x10
VK_CONTROL = 0x11
VK_MENU = 0x12
VK_PAUSE = 0x13
VK_CAPITAL = 0x14
VK_KANA = 0x15
VK_HANGEUL = 0x15
VK_HANGUL = 0x15
VK_JUNJA = 0x17
VK_FINAL = 0x18
VK_HANJA = 0x19
VK_KANJI = 0x19
VK_ESCAPE = 0x1B
VK_CONVERT = 0x1C
VK_NONCONVERT = 0x1D
VK_ACCEPT = 0x1E
VK_MODECHANGE = 0x1F
VK_SPACE = 0x20
VK_PRIOR = 0x21
VK_NEXT = 0x22
VK_END = 0x23
VK_HOME = 0x24
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
VK_SELECT = 0x29
VK_PRINT = 0x2A
VK_EXECUTE = 0x2B
VK_SNAPSHOT = 0x2C
VK_INSERT = 0x2D
VK_DELETE = 0x2E
VK_HELP = 0x2F
VK_LWIN = 0x5B
VK_RWIN = 0x5C
VK_APPS = 0x5D
VK_SLEEP = 0x5F
VK_NUMPAD0 = 0x60
VK_NUMPAD1 = 0x61
VK_NUMPAD2 = 0x62
VK_NUMPAD3 = 0x63
VK_NUMPAD4 = 0x64
VK_NUMPAD5 = 0x65
VK_NUMPAD6 = 0x66
VK_NUMPAD7 = 0x67
VK_NUMPAD8 = 0x68
VK_NUMPAD9 = 0x69
VK_MULTIPLY = 0x6A
VK_ADD = 0x6B
VK_SEPARATOR = 0x6C
VK_SUBTRACT = 0x6D
VK_DECIMAL = 0x6E
VK_DIVIDE = 0x6F
VK_F1 = 0x70
VK_F2 = 0x71
VK_F3 = 0x72
VK_F4 = 0x73
VK_F5 = 0x74
VK_F6 = 0x75
VK_F7 = 0x76
VK_F8 = 0x77
VK_F9 = 0x78
VK_F10 = 0x79
VK_F11 = 0x7A
VK_F12 = 0x7B
VK_F13 = 0x7C
VK_F14 = 0x7D
VK_F15 = 0x7E
VK_F16 = 0x7F
VK_F17 = 0x80
VK_F18 = 0x81
VK_F19 = 0x82
VK_F20 = 0x83
VK_F21 = 0x84
VK_F22 = 0x85
VK_F23 = 0x86
VK_F24 = 0x87
VK_NUMLOCK = 0x90
VK_SCROLL = 0x91
VK_OEM_NEC_EQUAL = 0x92
VK_OEM_FJ_JISHO = 0x92
VK_OEM_FJ_MASSHOU = 0x93
VK_OEM_FJ_TOUROKU = 0x94
VK_OEM_FJ_LOYA = 0x95
VK_OEM_FJ_ROYA = 0x96
VK_LSHIFT = 0xA0
VK_RSHIFT = 0xA1
VK_LCONTROL = 0xA2
VK_RCONTROL = 0xA3
VK_LMENU = 0xA4
VK_RMENU = 0xA5
VK_BROWSER_BACK = 0xA6
VK_BROWSER_FORWARD = 0xA7
VK_BROWSER_REFRESH = 0xA8
VK_BROWSER_STOP = 0xA9
VK_BROWSER_SEARCH = 0xAA
VK_BROWSER_FAVORITES = 0xAB
VK_BROWSER_HOME = 0xAC
VK_VOLUME_MUTE = 0xAD
VK_VOLUME_DOWN = 0xAE
VK_VOLUME_UP = 0xAF
VK_MEDIA_NEXT_TRACK = 0xB0
VK_MEDIA_PREV_TRACK = 0xB1
VK_MEDIA_STOP = 0xB2
VK_MEDIA_PLAY_PAUSE = 0xB3
VK_LAUNCH_MAIL = 0xB4
VK_LAUNCH_MEDIA_SELECT = 0xB5
VK_LAUNCH_APP1 = 0xB6
VK_LAUNCH_APP2 = 0xB7
VK_OEM_1 = 0xBA
VK_OEM_PLUS = 0xBB
VK_OEM_COMMA = 0xBC
VK_OEM_MINUS = 0xBD
VK_OEM_PERIOD = 0xBE
VK_OEM_2 = 0xBF
VK_OEM_3 = 0xC0
VK_OEM_4 = 0xDB
VK_OEM_5 = 0xDC
VK_OEM_6 = 0xDD
VK_OEM_7 = 0xDE
VK_OEM_8 = 0xDF
VK_OEM_AX = 0xE1
VK_OEM_102 = 0xE2
VK_ICO_HELP = 0xE3
VK_ICO_00 = 0xE4
VK_PROCESSKEY = 0xE5
VK_ICO_CLEAR = 0xE6
VK_OEM_RESET = 0xE9
VK_OEM_JUMP = 0xEA
VK_OEM_PA1 = 0xEB
VK_OEM_PA2 = 0xEC
VK_OEM_PA3 = 0xED
VK_OEM_WSCTRL = 0xEE
VK_OEM_CUSEL = 0xEF
VK_OEM_ATTN = 0xF0
VK_OEM_FINISH = 0xF1
VK_OEM_COPY = 0xF2
VK_OEM_AUTO = 0xF3
VK_OEM_ENLW = 0xF4
VK_OEM_BACKTAB = 0xF5
VK_ATTN = 0xF6
VK_CRSEL = 0xF7
VK_EXSEL = 0xF8
VK_EREOF = 0xF9
VK_PLAY = 0xFA
VK_ZOOM = 0xFB
VK_NONAME = 0xFC
VK_PA1 = 0xFD
VK_OEM_CLEAR = 0xFE
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment