当前位置: 代码网 > it编程>前端脚本>Golang > 基于Golang+Vue编写一个手机远程控制电脑的懒人工具

基于Golang+Vue编写一个手机远程控制电脑的懒人工具

2024年11月25日 Golang 我要评论
前言躺在床上投屏到电脑的时候, 调节音量和一些简单的操作还需要起身操作, 觉得麻烦, 就开发了这么一个小工具。思路go语言负责后端,负责模拟键盘输入和鼠标移动vue负责页面编写,调用后端接口,使用pe

前言

躺在床上投屏到电脑的时候, 调节音量和一些简单的操作还需要起身操作, 觉得麻烦, 就开发了这么一个小工具。

思路

  • 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手机远程控制电脑的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com