RGFW 底层:原始鼠标输入和鼠标锁定

2024-08-14 0 875

RGFW 底层:原始鼠标输入和鼠标锁定

解释如何锁定光标并为 x11、winAPI、cocoa 和 emscrIPten 启用原始鼠标输入的教程

介绍

rgfw 是一个轻量级单头窗口库,其源代码可以在这里找到。
教程基于其源代码

当您创建锁定光标的应用程序时,例如带有第一人称相机的游戏,能够禁用光标非常重要。
这意味着将光标锁定在屏幕中间并获取原始输入。

此方法的唯一替代方法是在鼠标移动时将鼠标拉回到窗口的中心。然而,这是一个 hack,所以它可能有错误
并且不适用于所有操作系统。因此,使用原始输入正确锁定鼠标非常重要。

本教程解释了 rgfw 如何处理原始鼠标输入,以便您可以了解如何自己实现它。

概述

所需步骤的快速概述

  1. 锁定光标
  2. 将光标居中
  3. 启用原始输入
  4. 处理原始输入
  5. 禁用原始输入
  6. 解锁光标

当用户要求 rgfw 保持光标时,rgfw 启用一个表示光标已保持的位标志。

1

win->_winargs |= rgfw_hold_mouse;

第 1 步(锁定光标)

在 x11 上,可以通过 xgrabpointer 抓取光标来锁定光标

1

xgrabpointer(display, window, true, pointermotionmask, grabmodeasync, grabmodeasync, none, none, currenttime);

这使窗口可以完全控制指针。

windows 上,clipcursor 将光标锁定到屏幕上的特定矩形。
这意味着我们必须在屏幕上找到窗口矩形,然后将鼠标夹到该矩形上。

还使用:geTCLientrect) 和 clienttoscreen

1

2

3

4

5

6

7

8

9

10

//first get the window size (the rgfw_window struct also includes this infORMation, but using this ensures it's correct)

rect cliprect;

getclientrect(window, &cliprect);

// clipcursor needs screen coordinates, not coordinates relative to the window

clienttoscreen(window, (point*) &cliprect.left);

clienttoscreen(window, (point*) &cliprect.right);

// now we can lock the cursor

clipcursor(&cliprect);

在 macos 和 emscripten 上,启用原始输入的功能也会锁定光标。所以我将在步骤 4 中了解它的功能。

步骤2(将光标置于中心)

光标锁定后,应居中于屏幕中间。
这可确保光标锁定在正确的位置,不会干扰其他任何内容。

rgfw 使用名为 rgfw_window_movemouse 的 rgfw 函数将鼠标移动到窗口中间。

在x11上,xwarppointer可用于将光标移动到窗口中心

1

xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);

windows上,使用setcursorpos

1

setcursorpos(window_x + (window_width / 2), window_y + (window_height / 2));

在 macos 上,使用 cgwarpmousecursorposition

1

cgwarpmousecursorposition(window_x + (window_width / 2), window_y + (window_height / 2));

在 emscripten 上,rgfw 不移动鼠标。

步骤 3(启用原始输入)

对于 x11,xi 用于启用原始输入

1

2

3

4

5

6

7

8

9

10

11

12

// mask for xi and set mouse for raw mouse input ("rawmotion")

unsigned char mask[ximasklen(xi_rawmotion)] = { 0 };

xisetmask(mask, xi_rawmotion);

// set up x1 struct

xieventmask em;

em.deviceid = xiallmasterdevices;

em.mask_len = sizeof(mask);

em.mask = mask;

//enable raw input using the structure

xiselectevents(display, xdefaultrootwindow(display), &em, 1);

在 windows 上,您需要设置 rawinputdevice 结构并使用 registerrawinputdevices 启用它

1

2

const rawinputdevice id = { 0x01, 0x02, 0, window };

registerrawinputdevices(&id, 1, sizeof(id));

在 macos 上你只需要运行 cgassociatemouseandmousecursorposition
这还通过解除鼠标光标和鼠标移动的关联来锁定光标

1

cgassociatemouseandmousecursorposition(0);

在 emscripten 上你只需要请求用户锁定指针

1

emscripten_request_pointerlock("#canvas", 1);

步骤 4(处理原始输入事件)

这些都发生在事件循环期间。

对于x11,您必须处理普通的motionnotify,手动将输入转换为原始输入。
要检查原始鼠标输入事件,您需要使用 genericevent。

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

switch (e.type) {

    (...)

    case motionnotify:

        /* check if mouse hold is enabled */

        if ((win->_winargs & rgfw_hold_mouse)) {

            /* convert e.xmotion to raw input by subtracting the previous point */

            win->event.point.x = win->_lastmousepoint.x - e.xmotion.x;

            win->event.point.y = win->_lastmousepoint.y - e.xmotion.y;

            //the mouse must be moved back to the center when it moves

            xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);

        }

        break;

    case genericevent: {

        /* motionnotify is used for mouse events if the mouse isn't held */

        if (!(win->_winargs & rgfw_hold_mouse)) {

            xfreeeventdata(display, &e.xcookie);

            break;

        }

        xgeteventdata(display, &e.xcookie);

        if (e.xcookie.evtype == xi_rawmotion) {

            xirawevent *raw = (xirawevent *)e.xcookie.data;

            if (raw->valuators.mask_len == 0) {

                xfreeeventdata(display, &e.xcookie);

                break;

            }

            double deltax = 0.0f;

            double deltay = 0.0f;

            /* check if relative motion data exists Where we think it does */

            if (ximaskisset(raw->valuators.mask, 0) != 0)

                deltax += raw->raw_values[0];

            if (ximaskisset(raw->valuators.mask, 1) != 0)

                deltay += raw->raw_values[1];

            //the mouse must be moved back to the center when it moves

            xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);

            win->event.point = rgfw_point((u32)-deltax, (u32)-deltay);

        }

        xfreeeventdata(display, &e.xcookie);

        break;

    }

在 windows 上,您只需要处理 wm_input 事件并检查原始运动输入

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

switch (msg.message) {

    (...)

    case wm_input: {

        /* check if the mouse is being held */

        if (!(win->_winargs & rgfw_hold_mouse))

            break;

        /* get raw data as an array */

        unsigned size = sizeof(rawinput);

        static rawinput raw[sizeof(rawinput)];

        getrawinputdata((hrawinput)msg.lparam, rid_input, raw, &size, sizeof(rawinputHEADer));

        //make sure raw data is valid

        if (raw->header.dwtype != rim_typemouse || (raw->data.mouse.llastx == 0 && raw->data.mouse.llasty == 0) )

            break;

        //the data is flipped 

        win->event.point.x = -raw->data.mouse.llastx;

        win->event.point.y = -raw->data.mouse.llasty;

        break;

    }

在 macos 上,您可以正常检查鼠标输入,同时使用 deltax 和 deltay 获取和翻转鼠标点

1

2

3

4

5

6

7

8

9

10

11

12

13

14

switch (objc_msgsend_uint(e, sel_registername("type"))) {

    case nseventtypeleftmousedragged:

    case nseventtypeothermousedragged:

    case nseventtyperightmousedragged:

    case nseventtypemousemoved:

        if ((win->_winargs & rgfw_hold_mouse) == 0) // if the mouse is not held

                    break;

                nspoint p;

        p.x = ((cgfloat(*)(id, sel))abi_objc_msgsend_fpret)(e, sel_registername("deltax"));

        p.y = ((cgfloat(*)(id, sel))abi_objc_msgsend_fpret)(e, sel_registername("deltay"));

                //the raw input must be flipped for macos as well, and cast for rgfw's event data

        win->event.point = rgfw_point((u32) -p.x, (u32) -p.y));

在 emscripten 上,可以像平常一样检查鼠标事件,除了我们要使用和翻转 e->movementx/y

1

2

3

4

5

6

7

em_bool emscripten_on_mousemove(int eventtype, const emscriptenmouseevent* e, void* userdata) {

    if ((rgfw_root->_winargs & rgfw_hold_mouse) == 0) // if the mouse is not held

            return

    //the raw input must be flipped for emscripten as well

        rgfw_point p = rgfw_point(-e->movementx, -e->movementy);

}

步骤 5(禁用原始输入)

最后,rgfw 允许禁用原始输入并解锁光标以恢复正常的鼠标输入。

首先,rgfw 禁用位标志。

1

win->_winargs ^= rgfw_hold_mouse;

在x11中,首先,你必须创建一个带有空白掩码的结构。
这将禁用原始输入。

1

2

3

4

5

6

7

unsigned char mask[] = { 0 };

xieventmask em;

em.deviceid = xiallmasterdevices;

em.mask_len = sizeof(mask);

em.mask = mask;

xiselectevents(display, xdefaultrootwindow(display), &em, 1);

对于 windows,您可以使用 ridev_remove 传递原始输入设备结构来禁用原始输入。

1

2

const rawinputdevice id = { 0x01, 0x02, ridev_remove, null };

registerrawinputdevices(&id, 1, sizeof(id));

在 macos 和 emscripten 上,解锁光标也会禁用原始输入。

第6步(解锁光标)

在x11上,xungrabpoint用于解锁光标。

1

xungrabpointer(display, currenttime);

在 windows 上,将 null 矩形指针传递给 clipcursor 以指向光标。

1

clipcursor(null);

在 macos 上,关联鼠标光标和鼠标移动将禁用原始输入并解锁光标

1

cgassociatemouseandmousecursorposition(1);

在 emscripten 上,退出指针锁定将解锁光标并禁用原始输入。

1

emscripten_exit_pointerlock();

完整代码示例

x11

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

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

// this can be compiled with

// gcc x11.c -lx11 -lxi

#include <x11>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <x11>

int main(void) {

    unsigned int window_width = 200;

    unsigned int window_height = 200;

    display* display = xopendisplay(null); 

    window window = xcreatesimplewindow(display, rootwindow(display, defaultscreen(display)), 400, 400, window_width, window_height, 1, blackpixel(display, defaultscreen(display)), whitepixel(display, defaultscreen(display)));

    xselectinput(display, window, exposuremask | keypressmask);

    xmapwindow(display, window);

    xgrabpointer(display, window, true, pointermotionmask, grabmodeasync, grabmodeasync, none, none, currenttime);

    xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);

    // mask for xi and set mouse for raw mouse input ("rawmotion")

    unsigned char mask[ximasklen(xi_rawmotion)] = { 0 };

    xisetmask(mask, xi_rawmotion);

    // set up x1 struct

    xieventmask em;

    em.deviceid = xiallmasterdevices;

    em.mask_len = sizeof(mask);

    em.mask = mask;

    // enable raw input using the structure

    xiselectevents(display, xdefaultrootwindow(display), &amp;em, 1);

    bool rawinput = true;

    xpoint point;

    xpoint _lastmousepoint;

    xevent event;

    for (;;) {

        xnextevent(display, &amp;event);

        switch (event.type) {

            case motionnotify:

                /* check if mouse hold is enabled */

                if (rawinput) {

                    /* convert e.xmotion to rawinput by substracting the previous point */

                    point.x = _lastmousepoint.x - event.xmotion.x;

                    point.y = _lastmousepoint.y - event.xmotion.y;

                    printf("rawinput %i %i\n", point.x, point.y);

                    xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);

                }

                break;

            case genericevent: {

                /* motionnotify is used for mouse events if the mouse isn't held */

                if (rawinput == false) {

                    xfreeeventdata(display, &amp;event.xcookie);

                    break;

                }

                xgeteventdata(display, &amp;event.xcookie);

                if (event.xcookie.evtype == xi_rawmotion) {

                    xirawevent *raw = (xirawevent *)event.xcookie.data;

                    if (raw-&gt;valuators.mask_len == 0) {

                        xfreeeventdata(display, &amp;event.xcookie);

                        break;

                    }

                    double deltax = 0.0f;

                    double deltay = 0.0f;

                    /* check if relative motion data exists where we think it does */

                    if (ximaskisset(raw-&gt;valuators.mask, 0) != 0)

                        deltax += raw-&gt;raw_values[0];

                    if (ximaskisset(raw-&gt;valuators.mask, 1) != 0)

                        deltay += raw-&gt;raw_values[1];

                    point = (xpoint){-deltax, -deltay};

                    xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);

                    printf("rawinput %i %i\n", point.x, point.y);

                }  

                xfreeeventdata(display, &amp;event.xcookie);

                break;

            }

            case keypress:

                if (rawinput == false)

                    break;

                unsigned char mask[] = { 0 };

                xieventmask em;

                em.deviceid = xiallmasterdevices;

                em.mask_len = sizeof(mask);

                em.mask = mask;

                xiselectevents(display, xdefaultrootwindow(display), &amp;em, 1);

                xungrabpointer(display, currenttime);

                printf("raw input disabled\n");

                break;

            default: break;

        }

    }

    xclosedisplay(display);

 }

</x11></string.h></stdlib.h></stdio.h></x11>

维纳皮

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

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

// compile with gcc winapi.c

#include <windows.h>

#include <stdio.h>

#include <stdint.h>

#include <assert.h>

int main() {

    WNDCLASS wc = {0};

    wc.lpfnWndProc   = DefWindowproc; // Default window procedure

    wc.hInstance     = GetModuleHandle(NULL);

    wc.lPSzClassName = "SampleWindowClass";

    RegisterClass(&amp;wc);

    int window_width = 300;

    int window_height = 300;

    int window_x = 400;

    int window_y = 400;

    HWND hwnd = CreateWindowA(wc.lpszClassName, "Sample Window", 0,

            window_x, window_y, window_width, window_height,

            NULL, NULL, wc.hInstance, NULL);

    ShowWindow(hwnd, SW_SHOW);

    UpdateWindow(hwnd);

    // first get the window size (the RGFW_window struct also includes this informaton, but using this ensures it's correct)

    RECT clipRect;

    GetClientRect(hwnd, &amp;clipRect);

    // ClipCursor needs screen coords, not coords relative to the window

    ClientToScreen(hwnd, (POINT*) &amp;clipRect.left);

    ClientToScreen(hwnd, (POINT*) &amp;clipRect.right);

    // now we can lock the cursor

    ClipCursor(&amp;clipRect);

    SetCursorPos(window_x + (window_width / 2), window_y + (window_height / 2));   

    const RAWINPUTDEVICE id = { 0x01, 0x02, 0, hwnd };

    RegisterRawInputDevices(&amp;id, 1, sizeof(id));

    MSG msg;

    BOOL holdMouse = TRUE;

    BOOL running = TRUE;

    POINT point;

    while (running) {

        if (PeekMessageA(&amp;msg, hwnd, 0u, 0u, PM_REMOVE)) {

            switch (msg.message) {

                case WM_CLOSE:

                case WM_QuiT:

                    running = FALSE;

                    break;

                case WM_INPUT: {

                    /* check if the mouse is being held */

                    if (holdMouse == FALSE)

                        break;

                    /* get raw data as an array */

                    unsigned size = sizeof(RAWINPUT);

                    static RAWINPUT raw[sizeof(RAWINPUT)];

                    GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, raw, &amp;size, sizeof(RAWINPUTHEADER));

                    // make sure raw data is valid

                    if (raw-&gt;header.dwType != RIM_TYPEMOUSE || (raw-&gt;data.mouse.lLastX == 0 &amp;&amp; raw-&gt;data.mouse.lLastY == 0) )

                        break;

                    // the data is flipped 

                    point.x = -raw-&gt;data.mouse.lLastX;

                    point.y = -raw-&gt;data.mouse.lLastY;

                    printf("raw input: %i %i\n", point.x, point.y);

                    break;

                }

                case WM_KEYDOWN:

                    if (holdMouse == FALSE)

                        break;

                    const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL };

                    RegisterRawInputDevices(&amp;id, 1, sizeof(id));

                    ClipCursor(NULL);

                    printf("rawinput disabled\n");

                    holdMouse = FALSE;

                    break;

                default: break;

            }

            TranslateMessage(&amp;msg);

            DispatchMessage(&amp;msg);

        }

        running = IsWindow(hwnd);

    }

    DestroyWindow(hwnd);

    return 0;

}

</assert.h></stdint.h></stdio.h></windows.h>

登录后复制

 

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

免责声明
1. 本站所有资源来源于用户上传和网络等,如有侵权请邮件联系本站整改team@lcwl.fun!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系本站工作人员处理!
6. 本站资源售价或VIP只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 因人力时间成本问题,部分源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
9.本站所有源码资源都是经过本站工作人员人工亲测可搭建的,保证每个源码都可以正常搭建,但不保证源码内功能都完全可用,源码属于可复制的产品,无任何理由退款!

网站搭建学习网 C RGFW 底层:原始鼠标输入和鼠标锁定 https://www.xuezuoweb.com/12928.html

常见问题
  • 本站所有的源码都是经过平台人工部署搭建测试过可用的
查看详情
  • 购买源码资源时购买了带主机的套餐是指可以享受源码和所选套餐型号的主机两个产品,在本站套餐里开通主机可享优惠,最高免费使用主机
查看详情

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务

Fa快捷助手
手机编程软件开发

在手机上用手点一点就能轻松做软件

去做软件
链未云主机
免备案香港云主机

开通主机就送域名的免备案香港云主机

去使用
链未云服务器
免备案香港云服务器

支持售后、超低价、稳定的免备案香港云服务器

去使用