解释如何锁定光标并为 x11、winapi、cocoa 和 emscripten 启用原始鼠标输入的教程。
介绍
rgfw 是一个轻量级单头窗口库,其源代码可以在这里找到。
本教程基于其源代码。
当您创建锁定光标的应用程序时,例如带有第一人称相机的游戏,能够禁用光标非常重要。
这意味着将光标锁定在屏幕中间并获取原始输入。
此方法的唯一替代方法是在鼠标移动时将鼠标拉回到窗口的中心。然而,这是一个 hack,所以它可能有错误
并且不适用于所有操作系统。因此,使用原始输入正确锁定鼠标非常重要。
本教程解释了 rgfw 如何处理原始鼠标输入,以便您可以了解如何自己实现它。
概述
所需步骤的快速概述
- 锁定光标
- 将光标居中
- 启用原始输入
- 处理原始输入
- 禁用原始输入
- 解锁光标
当用户要求 rgfw 保持光标时,rgfw 启用一个表示光标已保持的位标志。
win->_winargs |= rgfw_hold_mouse;
第 1 步(锁定光标)
在 x11 上,可以通过 xgrabpointer 抓取光标来锁定光标
xgrabpointer(display, window, true, pointermotionmask, grabmodeasync, grabmodeasync, none, none, currenttime);
这使窗口可以完全控制指针。
在 windows 上,clipcursor 将光标锁定到屏幕上的特定矩形。
这意味着我们必须在屏幕上找到窗口矩形,然后将鼠标夹到该矩形上。
还使用:getclientrect) 和 clienttoscreen
//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);
在 和 emscripten 上,启用原始输入的功能也会锁定光标。所以我将在步骤 4 中了解它的功能。
步骤2(将光标置于中心)
光标锁定后,应居中于屏幕中间。
这可确保光标锁定在正确的位置,不会干扰其他任何内容。
rgfw 使用名为 rgfw_window_movemouse 的 rgfw 函数将鼠标移动到窗口中间。
在x11上,xwarppointer可用于将光标移动到窗口中心
xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2);
在windows上,使用setcursorpos
setcursorpos(window_x + (window_width / 2), window_y + (window_height / 2));
在 macos 上,使用 cgwarpmousecursorposition
cgwarpmousecursorposition(window_x + (window_width / 2), window_y + (window_height / 2));
在 emscripten 上,rgfw 不移动鼠标。
步骤 3(启用原始输入)
对于 x11,xi 用于启用原始输入
// 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 启用它
const rawinputdevice id = { 0x01, 0x02, 0, window }; registerrawinputdevices(&id, 1, sizeof(id));
在 macos 上你只需要运行 cgassociatemouseandmousecursorposition
这还通过解除鼠标光标和鼠标移动的关联来锁定光标
cgassociatemouseandmousecursorposition(0);
在 emscripten 上你只需要请求用户锁定指针
emscripten_request_pointerlock("#canvas", 1);
步骤 4(处理原始输入事件)
这些都发生在事件循环期间。
对于x11,您必须处理普通的motionnotify,手动将输入转换为原始输入。
要检查原始鼠标输入事件,您需要使用 genericevent。
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 事件并检查原始运动输入
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 获取和翻转鼠标点
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
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 禁用位标志。
win->_winargs ^= rgfw_hold_mouse;
在x11中,首先,你必须创建一个带有空白掩码的结构。
这将禁用原始输入。
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 传递原始输入设备结构来禁用原始输入。
const rawinputdevice id = { 0x01, 0x02, ridev_remove, null }; registerrawinputdevices(&id, 1, sizeof(id));
在 macos 和 emscripten 上,解锁光标也会禁用原始输入。
第6步(解锁光标)
在x11上,xungrabpoint用于解锁光标。
xungrabpointer(display, currenttime);
在 windows 上,将 null 矩形指针传递给 clipcursor 以指向光标。
clipcursor(null);
在 macos 上,关联鼠标光标和鼠标移动将禁用原始输入并解锁光标
cgassociatemouseandmousecursorposition(1);
在 emscripten 上,退出指针锁定将解锁光标并禁用原始输入。
emscripten_exit_pointerlock();
完整代码示例
x11
// 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), &em, 1); bool rawinput = true; xpoint point; xpoint _lastmousepoint; xevent event; for (;;) { xnextevent(display, &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 %in", 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, &event.xcookie); break; } xgeteventdata(display, &event.xcookie); if (event.xcookie.evtype == xi_rawmotion) { xirawevent *raw = (xirawevent *)event.xcookie.data; if (raw->valuators.mask_len == 0) { xfreeeventdata(display, &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->valuators.mask, 0) != 0) deltax += raw->raw_values[0]; if (ximaskisset(raw->valuators.mask, 1) != 0) deltay += raw->raw_values[1]; point = (xpoint){-deltax, -deltay}; xwarppointer(display, none, window, 0, 0, 0, 0, window_width / 2, window_height / 2); printf("rawinput %i %in", point.x, point.y); } xfreeeventdata(display, &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), &em, 1); xungrabpointer(display, currenttime); printf("raw input disabledn"); break; default: break; } } xclosedisplay(display); } </x11></string.h></stdlib.h></stdio.h></x11>
维纳皮
// 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(&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, &clipRect); // ClipCursor needs screen coords, not coords relative to the window ClientToScreen(hwnd, (POINT*) &clipRect.left); ClientToScreen(hwnd, (POINT*) &clipRect.right); // now we can lock the cursor ClipCursor(&clipRect); SetCursorPos(window_x + (window_width / 2), window_y + (window_height / 2)); const RAWINPUTDEVICE id = { 0x01, 0x02, 0, hwnd }; RegisterRawInputDevices(&id, 1, sizeof(id)); MSG msg; BOOL holdMouse = TRUE; BOOL running = TRUE; POINT point; while (running) { if (PeekMessageA(&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, &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 point.x = -raw->data.mouse.lLastX; point.y = -raw->data.mouse.lLastY; printf("raw input: %i %in", point.x, point.y); break; } case WM_KEYDOWN: if (holdMouse == FALSE) break; const RAWINPUTDEVICE id = { 0x01, 0x02, RIDEV_REMOVE, NULL }; RegisterRawInputDevices(&id, 1, sizeof(id)); ClipCursor(NULL); printf("rawinput disabledn"); holdMouse = FALSE; break; default: break; } TranslateMessage(&msg); DispatchMessage(&msg); } running = IsWindow(hwnd); } DestroyWindow(hwnd); return 0; } </assert.h></stdint.h></stdio.h></windows.h>
以上就是RGFW 底层:原始鼠标输入和鼠标锁定的详细内容,更多请关注php中文网其它相关文章!