原本以為node.js已經夠強大了
結果golang更強大...
省掉一堆有的沒的東西...
call系統API只比喝水難上一點點而已
(對...就是VX、XS系列....天殺的肥啊orz
拉回來...
雖然不清楚是不是叫"簡報筆軟體"這個名子
不過~~ 管他的~~
大家知道意思就好~
先來分析一下需求:
1. 是手機控制電腦
>> 手機要能連到電腦 + 傳資料
>>>> 直接套http server, 核心內建不用白不用XD
2. 手機要有一個介面操作
>> 專用app之類的比較好刻介面
>>>> 因為懶的寫手機app, UI部份直接用HTML5解決掉(老本行XD
3. 至少要有幾個鍵盤按鍵模擬(已實作)
>> 要call系統的鍵盤API
>>>> 這邊用 keybd_event , SendInput 不知道為啥一直沒作用
4. 可能需要控制滑鼠(已實作)
>> 要call系統的滑鼠API
>>>> 用 mouse_event , 理由同鍵盤API
5. 可能要螢幕同步(尚未實作)
>> 要call系統的截圖API, 並將圖片傳到手機上顯示
>>>> 有空再弄...可能會用MJPEG做比較快@@
使用方法:
1. 讓手機跟電腦在同一個網段 or 手機可以連到電腦的網路環境
2. 執行編譯好的server(預編好的keyemu.exe、keyemu-x64.exe)
(2.1)建議先把server視窗最小化
3. 手機用瀏覽器連上電腦的ip + port(預設9999)
4. UI部份左半邊是滑鼠, 右半邊是各種按鍵
注意!!
這只是個簡單demo
如有不合用的地方請自行改code
(像是連線加密啦 要有密碼認證啥的
直接用在正式環境出事別找我喔@@
詳細實作請看code(總共不到500行)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"><</button> | |
<button id="right">></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 | |
) | |
沒有留言:
張貼留言