前言
躺在床上投屏到电脑的时候, 调节音量和一些简单的操作还需要起身操作, 觉得麻烦, 就开发了这么一个小工具。
思路
- go语言负责后端,负责模拟键盘输入和鼠标移动
- vue负责页面编写,调用后端接口,使用petite-vue单个页面开发, 够轻量
- go直接调用user32.dll完成键盘和鼠标的操作, 不依赖三方框架
- 前端完全基于浏览器, 有微信有扫一扫就能直接打开控制页
- 按键传输采用
http
请求, 鼠标移动采用websocket
使用技术
- 前端:petite-vue、qrcode
- 后端:go1.20、systray、websocket
开始操作
前端
封装fetch请求后端api
function request(options, temp) { let opts = temp if (typeof options != 'string') { opts = options } let { url, method = 'get', params = {}, data = null } = opts || {}; if (typeof options == 'string') url = options // 将查询参数转换为url编码字符串 const querystring = object.keys(params).map(key => `${encodeuricomponent(key)}=${encodeuricomponent(params[key])}`).join('&'); // 构建完整的请求url let finalurl = url + (url.includes('?') ? '&' : '?') + querystring; finalurl = finalurl.includes('http') ? finalurl : `${baseapi}${finalurl}` // 设置请求头部 const headers = {}; if (data) headers['content-type'] = 'application/json' // 发起fetch请求 return new promise((resolve, reject) => { fetch(finalurl, { method, headers, body: data ? json.stringify(data) : null}).then(res => { if (!res.ok) throw new error(`http error! status: ${response.status}`); return res.json(); }).then(r => resolve(r)).catch(e => reject(e)); }); }
websocket初始化
websocket = new websocket(`ws://${location.host}/ws`); websocket.onmessage = function(evt) { if(evt.data=="reload"){ window.location.pathname = "/"; window.location.reload(true); } }; websocket.onopen = function() { console.log('socket open....'); document.getelementbyid('touch').style.display = 'block'; }; websocket.onclose = function() { console.log('socket close....'); document.getelementbyid('touch').style.display = 'none'; }; let starttime = 0; function senddata(data, force) { const curr = new date().gettime(); if (curr - starttime > 60 || force) { console.log('socket send....', data); websocket.send(data); starttime = curr; } };
按键布局
<div id="keyboard"> <div class="f2"> <i k="ctrl,a">全选</i><i k="ctrl,c">复制</i><i k="ctrl,v">粘贴</i><i k="ctrl,x">剪切</i><i k="ctrl,z">撤销</i><i k="ctrl,s">保存</i> </div> <div class="f2"> <i k="ctrl,shift">输入法</i><i k="alt,f4">关闭</i><i k="win,d">桌面</i><i k="media_prev_track">上曲</i><i k="media_next_track">下曲</i> <i k="media_play_pause">播放</i><i k="volume_down">音量-</i><i k="volume_up">音量+</i><i k="volume_mute">静音</i> </div> <div class="f1"><i>esc</i><i>f1</i><i>f2</i><i>f3</i><i>f4</i><i>f5</i><i>f6</i><i>f7</i><i>f8</i><i>f9</i><i>f10</i><i>f11</i><i>f12</i></div> <div><i k="oem_3">`</i><i>1</i><i>2</i><i>3</i><i>4</i><i>5</i><i>6</i><i>7</i><i>8</i><i>9</i><i>0</i><i>back</i></div> <div><i>tab</i><i>q</i><i>w</i><i>e</i><i>r</i><i>t</i><i>y</i><i>u</i><i>i</i><i>o</i><i>p</i></div> <div><i k="capital">caps</i><i>a</i><i>s</i><i>d</i><i>f</i><i>g</i><i>h</i><i>j</i><i>k</i><i>l</i><i k="enter">回车</i></div> <div><i>shft</i><i>z</i><i>x</i><i>c</i><i>v</i><i>b</i><i>n</i><i>m</i><i k="home">hm</i><i k="up">↑</i><i k="end">ed</i></div> <div> <i k="oem_comma">,</i><i k="oem_period">.</i><i k="oem_2">/ </i><i k="oem_4"> { </i><i k="oem_6"> } </i><i k="semicolon"> ; </i> <i k="oem_7"> ' </i><i k="oem_minu"> - </i><i k="oem_plus"> + </i><i k="left">←</i><i k="down">↓</i><i k="right">→</i> </div> <div><span onclick="toggle('#keyboard')">隐藏</span><i style="flex: 1; line-height: 46px; margin-left: 10px;">space</i></div> </div>
其他说明
鼠标移动使用websocket实时通信后端, 做了防抖处理, 避免请求太多, 灵敏度高的时候鼠标会有卡顿, 还待优化
后端
初始化项目
go mod init dcontrol
下载依赖
go get github.com/getlantern/systray go get github.com/spf13/viper go get github.com/gorilla/websocket ...
编写代码
1.主函数
- 通过
go:embed
指定静态资源目录, 可以直接将前端打包的资源封装入exe中 http.handlefunc
将api前缀交给函数处理, 在函数里面具体处理子路由- 配置文件通过
config.yml
加载, 可以指定快捷应用和启动端口
//go:embed webapp var f embed.fs func main() { port := flag.int("p", 0, "server port") base.runport = *port filepath := flag.string("f", "./config.yml", "server config file") // dir := flag.string("d", "./webapp", "server static dir") flag.parse() //1.加载配置 setting.init(*filepath) base.runport = setting.conf.port if *port != 0 { base.runport = *port } addr := fmt.sprintf(":%d", base.runport) http.handlefunc("/control-api/monitor/", monitor.handleapi) http.handlefunc("/ws", ws.servews) // 注册静态资源 st, _ := fs.sub(f, "webapp") http.handle("/", http.stripprefix("/", http.fileserver(http.fs(st)))) err := http.listenandserve(addr, nil) if err != nil { fmt.println("start http error: ", err) } fmt.println("start http success ", base.runport) }
2.handleapi 处理子路由
func handleapi(w http.responsewriter, r *http.request) { w.header().set("access-control-allow-origin", "*") //允许访问所有域 w.header().add("access-control-allow-headers", "content-type") //header的类型 w.header().set("content-type", "application/json") // 获取请求路径 strings.hassuffix path := r.url.path fmt.println("redis handleapi path:", path) switch { case strings.contains(path, "/getkeymap"): getkeymap(w, r) case strings.contains(path, "/getip"): getip(w, r) case strings.contains(path, "/getapps"): getapps(w, r) case strings.contains(path, "/sendkey"): sendkey(w, r) case strings.contains(path, "/open"): open(w, r) default: http.notfound(w, r) } }
3.windows任务栏添加应用小图标和菜单
func gentaskbaricon() { if runtime.goos == "windows" { systray.run(onready, onexit) } } func onready() { systray.seticon(icondata) systray.settitle("d-control") systray.settooltip("d-control 右键点击打开菜单!") menuopen := systray.addmenuitem("打开网页", "打开系统网页") systray.addseparator() menuquit := systray.addmenuitem("退出", "退出程序") go func() { for { select { case <-menuopen.clickedch: openbrowser(fmt.sprintf("http://localhost:%d/", base.runport)) case <-menuquit.clickedch: systray.quit() os.exit(0) } } }() } func onexit() {}
4.调用user32.dll
, 实现模拟键盘输入和鼠标移动
var dll = syscall.newlazydll("user32.dll") var prockeybd = dll.newproc("keybd_event") var procsetcursorpos = dll.newproc("setcursorpos") var procgetcursorpos = dll.newproc("getcursorpos") var procmouseevent = dll.newproc("mouse_event") func setmouse(x int, y int, isdiff bool) { if isdiff { procgetcursorpos.call(uintptr(unsafe.pointer(&cursorpos))) fmt.println("cursorpos: ", cursorpos.x, cursorpos.y) procsetcursorpos.call(uintptr(cursorpos.x+int32(x)), uintptr(cursorpos.y+int32(y))) } else { procsetcursorpos.call(uintptr(int32(x)), uintptr(int32(y))) } } func clickmouse(str string) { if str == "l" { procmouseevent.call(mouseeventf_leftdown, 0, 0, 0, 0) time.sleep(50 * time.millisecond) // 短暂延迟 procmouseevent.call(mouseeventf_leftup, 0, 0, 0, 0) } else if str == "r" { procmouseevent.call(mouseeventf_rightdown, 0, 0, 0, 0) time.sleep(50 * time.millisecond) // 短暂延迟 procmouseevent.call(mouseeventf_rightup, 0, 0, 0, 0) } else if str == "m" { procmouseevent.call(mouseeventf_middledown, 0, 0, 0, 0) time.sleep(50 * time.millisecond) // 短暂延迟 procmouseevent.call(mouseeventf_middleup, 0, 0, 0, 0) } } func downkey(key int) { flag := 0 if key < 0xfff { // detect if the key code is virtual or no flag |= _keyeventf_scancode } else { key -= 0xfff } vkey := key + 0x80 prockeybd.call(uintptr(key), uintptr(vkey), uintptr(flag), 0) } func upkey(key int) { flag := _keyeventf_keyup if key < 0xfff { flag |= _keyeventf_scancode } else { key -= 0xfff } vkey := key + 0x80 prockeybd.call(uintptr(key), uintptr(vkey), uintptr(flag), 0) } // 按键映射map var keymap = map[string]int{ "shift": 0x10 + 0xfff, "ctrl": 0x11 + 0xfff, "alt": 0x12 + 0xfff, "lshift": 0xa0 + 0xfff, "rshift": 0xa1 + 0xfff, "lcontrol": 0xa2 + 0xfff, "rcontrol": 0xa3 + 0xfff, "win": 0x5b + 0xfff, ... }
5.websocket服务监听
func servews(w http.responsewriter, r *http.request) { ws, err := upgrader.upgrade(w, r, nil) if err != nil { fmt.println("upgrade:", err) return } fmt.println("servews connected......") defer ws.close() for { // 读取消息 messagetype, msg, err := ws.readmessage() if err != nil { fmt.println("error while reading message:", err) break } // 打印接收到的消息 fmt.printf("ws received: %s\n", msg) wsdata := string(msg) if wsdata == "pos,click" { // go keys.runkeys(keys.keymap["lbutton"]) keys.clickmouse("l") } else if wsdata == "pos,longclick" { keys.clickmouse("r") } else if strings.hasprefix(wsdata, "pos,start") { parts := strings.split(wsdata, ",") if len(parts) == 4 { fx, _ := strconv.parsefloat(parts[2], 64) fy, _ := strconv.parsefloat(parts[3], 64) keys.setmouse(int(fx), int(fy), true) } } // 可以选择回送消息给客户端 err = ws.writemessage(messagetype, msg) if err != nil { fmt.println("error while writing message:", err) break } } }
后言
搭配macast
开源投屏神器, 躺在床上手机随时投屏视频到电脑上, 手机再遥控电脑音量和简易操作, 美滋滋了
源码地址
源码和程序截图详见github.com/dhjz/dcontrol
页面效果图见appimg
目录
以上就是基于golang+vue编写一个手机远程控制电脑的懒人工具的详细内容,更多关于go vue手机远程控制电脑的资料请关注代码网其它相关文章!
发表评论