广告位联系
返回顶部
分享到

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

Golang 来源:互联网 作者:佚名 发布时间:2024-11-18 08:27:57 人浏览
摘要

思路 Go语言负责后端,负责模拟键盘输入和鼠标移动 Vue负责页面编写,调用后端接口,使用petite-vue单个页面开发, 够轻量 go直接调用user32.dll完成键盘和鼠标的操作, 不依赖三方框架 前端完全基

思路

  • Go语言负责后端,负责模拟键盘输入和鼠标移动
  • Vue负责页面编写,调用后端接口,使用petite-vue单个页面开发, 够轻量
  • go直接调用user32.dll完成键盘和鼠标的操作, 不依赖三方框架
  • 前端完全基于浏览器, 有微信有扫一扫就能直接打开控制页
  • 按键传输采用http请求, 鼠标移动采用websocket

使用技术

  • 前端:petite-vue、qrcode
  • 后端:go1.20、systray、websocket

开始操作

前端

封装fetch请求后端api

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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初始化

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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;

        }

};

按键布局

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

<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实时通信后端, 做了防抖处理, 避免请求太多, 灵敏度高的时候鼠标会有卡顿, 还待优化

后端

初始化项目

1

go mod init dcontrol

下载依赖

1

2

3

4

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加载, 可以指定快捷应用和启动端口

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//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 处理子路由

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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任务栏添加应用小图标和菜单

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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, 实现模拟键盘输入和鼠标移动

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

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服务监听

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

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目录


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • Go集成swagger实现在线接口文档的教程

    Go集成swagger实现在线接口文档的教程
    安装swaggo 1 go install github.com/swaggo/swag/cmd/swag@latest 编写swag 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
  • 基于Golang+Vue编写一个手机远程控制电脑的懒人工

    基于Golang+Vue编写一个手机远程控制电脑的懒人工
    思路 Go语言负责后端,负责模拟键盘输入和鼠标移动 Vue负责页面编写,调用后端接口,使用petite-vue单个页面开发, 够轻量 go直接调用user32
  • Go语言怎么使用Viper来管理配置
    在现代软件开发中,良好的配置管理可以极大地提升应用的灵活性和可维护性。 在 Go 语言中,Viper 是一个功能强大且广泛使用的配置管理库
  • Golang的GC垃圾回收机制的介绍
    在现代编程语言中,垃圾回收(Garbage Collection, GC)机制是一个至关重要的特性。它帮助开发者自动管理内存,避免内存泄漏和悬挂指针等问
  • Golang中类型转换利器cast库的用法
    在Golang开发中,类型转换是一个常见且不可避免的过程。无论是将字符串转换为整数,还是将接口转换为布尔值,类型转换都贯穿在代码的
  • Python框架FastAPI详解介绍

    Python框架FastAPI详解介绍
    FastAPI是一种现代、快速(高性能)的Web框架,用于Python 3.6+,使用Python类型提示构建API。它的设计初衷是帮助开发者在短时间内开发出高性
  • 使用go实现创建WebSocket服务器
    使用Go语言创建WebSocket服务器可以利用现有的库来简化开发过程。gorilla/websocket是一个非常流行且功能强大的库,适用于Go语言的WebSocket应用
  • golang日志库ZAP[uber-go zap]示例介绍
    golang 日志库ZAP[uber-go zap] 1. 简要说明 zap 是 uber 开源的 Go 高性能日志库,支持不同的日志级别, 能够打印基本信息等,但不支持日志的分割
  • golang并发编程使用Select语句的实现介绍
    在 Go 语言中,select语句是一种控制结构,允许一个 Goroutine 同时等待多个通道操作。select语句会阻塞,直到其中的一个case可以继续执行,然
  • go语言time.After()的作用介绍
    time.After是一个非常实用的函数,它返回一个用于读取的单向通道(-chan time.Time),这个通道会在指定的时间间隔之后发送当前时间。这个机
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计