Skip to content

Commit 488008e

Browse files
committed
Add cursor mode GLFW_CURSOR_CAPTURED
This adds a cursor mode that provides a visible cursor confined to the content area of the window. Fixes glfw#58
1 parent a46f829 commit 488008e

11 files changed

+119
-8
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ information on what to include when reporting a bug.
139139
- Added `GLFW_POINTING_HAND_CURSOR` alias for `GLFW_HAND_CURSOR` (#427)
140140
- Added `GLFW_MOUSE_PASSTHROUGH` window hint for letting mouse input pass
141141
through the window (#1236,#1568)
142+
- Added `GLFW_CURSOR_CAPTURED` cursor mode to confine the cursor to the window
143+
content area (#58)
142144
- Added `GLFW_PLATFORM_UNAVAILABLE` error for platform detection failures (#1958)
143145
- Added `GLFW_FEATURE_UNAVAILABLE` error for platform limitations (#1692)
144146
- Added `GLFW_FEATURE_UNIMPLEMENTED` error for incomplete backends (#1692)

docs/input.dox

+12
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,25 @@ glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
312312

313313
This mode puts no limit on the motion of the cursor.
314314

315+
If you wish the cursor to be visible but confined to the content area of the
316+
window, set the cursor mode to `GLFW_CURSOR_CAPTURED`.
317+
318+
@code
319+
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_CAPTURED);
320+
@endcode
321+
322+
The cursor will behave normally inside the content area but will not be able to
323+
leave unless the window loses focus.
324+
315325
To exit out of either of these special modes, restore the `GLFW_CURSOR_NORMAL`
316326
cursor mode.
317327

318328
@code
319329
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
320330
@endcode
321331

332+
If the cursor was disabled, this will move it back to its last visible position.
333+
322334

323335
@anchor GLFW_RAW_MOUSE_MOTION
324336
@subsection raw_mouse_motion Raw mouse motion

docs/news.dox

+9
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ requesting a specific rendering backend when using
5858
contexts.
5959

6060

61+
@subsubsection captured_cursor_34 Captured cursor mode
62+
63+
GLFW now supports confining the cursor to the window content area with the @ref
64+
GLFW_CURSOR_CAPTURED cursor mode.
65+
66+
For more information see @ref cursor_mode.
67+
68+
6169
@subsubsection features_34_init_allocator Support for custom memory allocator
6270

6371
GLFW now supports plugging a custom memory allocator at initialization with @ref
@@ -234,6 +242,7 @@ then GLFW will fail to initialize.
234242
- @ref GLFW_ANGLE_PLATFORM_TYPE_VULKAN
235243
- @ref GLFW_ANGLE_PLATFORM_TYPE_METAL
236244
- @ref GLFW_X11_XCB_VULKAN_SURFACE
245+
- @ref GLFW_CURSOR_CAPTURED
237246

238247

239248
@section news_archive Release notes for earlier versions

include/GLFW/glfw3.h

+3
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,7 @@ extern "C" {
11341134
#define GLFW_CURSOR_NORMAL 0x00034001
11351135
#define GLFW_CURSOR_HIDDEN 0x00034002
11361136
#define GLFW_CURSOR_DISABLED 0x00034003
1137+
#define GLFW_CURSOR_CAPTURED 0x00034004
11371138

11381139
#define GLFW_ANY_RELEASE_BEHAVIOR 0
11391140
#define GLFW_RELEASE_BEHAVIOR_FLUSH 0x00035001
@@ -4566,6 +4567,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
45664567
* - `GLFW_CURSOR_DISABLED` hides and grabs the cursor, providing virtual
45674568
* and unlimited cursor movement. This is useful for implementing for
45684569
* example 3D camera controls.
4570+
* - `GLFW_CURSOR_CAPTURED` makes the cursor visible and confines it to the
4571+
* content area of the window.
45694572
*
45704573
* If the mode is `GLFW_STICKY_KEYS`, the value must be either `GLFW_TRUE` to
45714574
* enable sticky keys, or `GLFW_FALSE` to disable it. If sticky keys are

src/cocoa_window.m

+8
Original file line numberDiff line numberDiff line change
@@ -1619,8 +1619,16 @@ void _glfwSetCursorPosCocoa(_GLFWwindow* window, double x, double y)
16191619
void _glfwSetCursorModeCocoa(_GLFWwindow* window, int mode)
16201620
{
16211621
@autoreleasepool {
1622+
1623+
if (mode == GLFW_CURSOR_CAPTURED)
1624+
{
1625+
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
1626+
"Cocoa: Captured cursor mode not yet implemented");
1627+
}
1628+
16221629
if (_glfwWindowFocusedCocoa(window))
16231630
updateCursorMode(window);
1631+
16241632
} // autoreleasepool
16251633
}
16261634

src/input.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,8 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
596596
{
597597
if (value != GLFW_CURSOR_NORMAL &&
598598
value != GLFW_CURSOR_HIDDEN &&
599-
value != GLFW_CURSOR_DISABLED)
599+
value != GLFW_CURSOR_DISABLED &&
600+
value != GLFW_CURSOR_CAPTURED)
600601
{
601602
_glfwInputError(GLFW_INVALID_ENUM,
602603
"Invalid cursor mode 0x%08X",

src/win32_window.c

+13-2
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area)
238238
//
239239
static void updateCursorImage(_GLFWwindow* window)
240240
{
241-
if (window->cursorMode == GLFW_CURSOR_NORMAL)
241+
if (window->cursorMode == GLFW_CURSOR_NORMAL ||
242+
window->cursorMode == GLFW_CURSOR_CAPTURED)
242243
{
243244
if (window->cursor)
244245
SetCursor(window->cursor->win32.handle);
@@ -586,6 +587,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
586587
{
587588
if (window->cursorMode == GLFW_CURSOR_DISABLED)
588589
disableCursor(window);
590+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
591+
captureCursor(window);
589592

590593
window->win32.frameAction = GLFW_FALSE;
591594
}
@@ -604,6 +607,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
604607

605608
if (window->cursorMode == GLFW_CURSOR_DISABLED)
606609
disableCursor(window);
610+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
611+
captureCursor(window);
607612

608613
return 0;
609614
}
@@ -612,6 +617,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
612617
{
613618
if (window->cursorMode == GLFW_CURSOR_DISABLED)
614619
enableCursor(window);
620+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
621+
releaseCursor();
615622

616623
if (window->monitor && window->autoIconify)
617624
_glfwIconifyWindowWin32(window);
@@ -981,6 +988,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
981988
// resizing the window or using the window menu
982989
if (window->cursorMode == GLFW_CURSOR_DISABLED)
983990
enableCursor(window);
991+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
992+
releaseCursor();
984993

985994
break;
986995
}
@@ -995,6 +1004,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
9951004
// resizing the window or using the menu
9961005
if (window->cursorMode == GLFW_CURSOR_DISABLED)
9971006
disableCursor(window);
1007+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
1008+
captureCursor(window);
9981009

9991010
break;
10001011
}
@@ -2166,7 +2177,7 @@ void _glfwSetCursorModeWin32(_GLFWwindow* window, int mode)
21662177
disableRawMouseMotion(window);
21672178
}
21682179

2169-
if (mode == GLFW_CURSOR_DISABLED)
2180+
if (mode == GLFW_CURSOR_DISABLED || mode == GLFW_CURSOR_CAPTURED)
21702181
captureCursor(window);
21712182
else
21722183
releaseCursor();

src/wl_platform.h

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ typedef struct _GLFWwindowWayland
269269

270270
struct zwp_relative_pointer_v1* relativePointer;
271271
struct zwp_locked_pointer_v1* lockedPointer;
272+
struct zwp_confined_pointer_v1* confinedPointer;
272273

273274
struct zwp_idle_inhibitor_v1* idleInhibitor;
274275

src/wl_window.c

+53-1
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,9 @@ void _glfwDestroyWindowWayland(_GLFWwindow* window)
18601860
if (window->wl.lockedPointer)
18611861
zwp_locked_pointer_v1_destroy(window->wl.lockedPointer);
18621862

1863+
if (window->wl.confinedPointer)
1864+
zwp_confined_pointer_v1_destroy(window->wl.confinedPointer);
1865+
18631866
if (window->context.destroy)
18641867
window->context.destroy(window);
18651868

@@ -2538,6 +2541,43 @@ static void lockPointer(_GLFWwindow* window)
25382541
window);
25392542
}
25402543

2544+
static void confinedPointerHandleConfined(void* userData,
2545+
struct zwp_confined_pointer_v1* confinedPointer)
2546+
{
2547+
}
2548+
2549+
static void confinedPointerHandleUnconfined(void* userData,
2550+
struct zwp_confined_pointer_v1* confinedPointer)
2551+
{
2552+
}
2553+
2554+
static const struct zwp_confined_pointer_v1_listener confinedPointerListener =
2555+
{
2556+
confinedPointerHandleConfined,
2557+
confinedPointerHandleUnconfined
2558+
};
2559+
2560+
static void confinePointer(_GLFWwindow* window)
2561+
{
2562+
window->wl.confinedPointer =
2563+
zwp_pointer_constraints_v1_confine_pointer(
2564+
_glfw.wl.pointerConstraints,
2565+
window->wl.surface,
2566+
_glfw.wl.pointer,
2567+
NULL,
2568+
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
2569+
2570+
zwp_confined_pointer_v1_add_listener(window->wl.confinedPointer,
2571+
&confinedPointerListener,
2572+
window);
2573+
}
2574+
2575+
static void unconfinePointer(_GLFWwindow* window)
2576+
{
2577+
zwp_confined_pointer_v1_destroy(window->wl.confinedPointer);
2578+
window->wl.confinedPointer = NULL;
2579+
}
2580+
25412581
void _glfwSetCursorWayland(_GLFWwindow* window, _GLFWcursor* cursor)
25422582
{
25432583
if (!_glfw.wl.pointer)
@@ -2553,17 +2593,29 @@ void _glfwSetCursorWayland(_GLFWwindow* window, _GLFWcursor* cursor)
25532593
// Update pointer lock to match cursor mode
25542594
if (window->cursorMode == GLFW_CURSOR_DISABLED)
25552595
{
2596+
if (window->wl.confinedPointer)
2597+
unconfinePointer(window);
25562598
if (!window->wl.lockedPointer)
25572599
lockPointer(window);
25582600
}
2601+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
2602+
{
2603+
if (window->wl.lockedPointer)
2604+
unlockPointer(window);
2605+
if (!window->wl.confinedPointer)
2606+
confinePointer(window);
2607+
}
25592608
else if (window->cursorMode == GLFW_CURSOR_NORMAL ||
25602609
window->cursorMode == GLFW_CURSOR_HIDDEN)
25612610
{
25622611
if (window->wl.lockedPointer)
25632612
unlockPointer(window);
2613+
else if (window->wl.confinedPointer)
2614+
unconfinePointer(window);
25642615
}
25652616

2566-
if (window->cursorMode == GLFW_CURSOR_NORMAL)
2617+
if (window->cursorMode == GLFW_CURSOR_NORMAL ||
2618+
window->cursorMode == GLFW_CURSOR_CAPTURED)
25672619
{
25682620
if (cursor)
25692621
setCursorImage(window, &cursor->wl);

src/x11_window.c

+9-3
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,8 @@ static char* convertLatin1toUTF8(const char* source)
453453
//
454454
static void updateCursorImage(_GLFWwindow* window)
455455
{
456-
if (window->cursorMode == GLFW_CURSOR_NORMAL)
456+
if (window->cursorMode == GLFW_CURSOR_NORMAL ||
457+
window->cursorMode == GLFW_CURSOR_CAPTURED)
457458
{
458459
if (window->cursor)
459460
{
@@ -1705,6 +1706,8 @@ static void processEvent(XEvent *event)
17051706

17061707
if (window->cursorMode == GLFW_CURSOR_DISABLED)
17071708
disableCursor(window);
1709+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
1710+
captureCursor(window);
17081711

17091712
if (window->x11.ic)
17101713
XSetICFocus(window->x11.ic);
@@ -1725,6 +1728,8 @@ static void processEvent(XEvent *event)
17251728

17261729
if (window->cursorMode == GLFW_CURSOR_DISABLED)
17271730
enableCursor(window);
1731+
else if (window->cursorMode == GLFW_CURSOR_CAPTURED)
1732+
releaseCursor();
17281733

17291734
if (window->x11.ic)
17301735
XUnsetICFocus(window->x11.ic);
@@ -2831,7 +2836,7 @@ void _glfwSetCursorModeX11(_GLFWwindow* window, int mode)
28312836
disableRawMouseMotion(window);
28322837
}
28332838

2834-
if (mode == GLFW_CURSOR_DISABLED)
2839+
if (mode == GLFW_CURSOR_DISABLED || mode == GLFW_CURSOR_CAPTURED)
28352840
captureCursor(window);
28362841
else
28372842
releaseCursor();
@@ -3003,7 +3008,8 @@ void _glfwDestroyCursorX11(_GLFWcursor* cursor)
30033008

30043009
void _glfwSetCursorX11(_GLFWwindow* window, _GLFWcursor* cursor)
30053010
{
3006-
if (window->cursorMode == GLFW_CURSOR_NORMAL)
3011+
if (window->cursorMode == GLFW_CURSOR_NORMAL ||
3012+
window->cursorMode == GLFW_CURSOR_CAPTURED)
30073013
{
30083014
updateCursorImage(window);
30093015
XFlush(_glfw.x11.display);

tests/cursor.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action,
172172

173173
case GLFW_KEY_ESCAPE:
174174
{
175-
if (glfwGetInputMode(window, GLFW_CURSOR) != GLFW_CURSOR_DISABLED)
175+
const int mode = glfwGetInputMode(window, GLFW_CURSOR);
176+
if (mode != GLFW_CURSOR_DISABLED && mode != GLFW_CURSOR_CAPTURED)
176177
{
177178
glfwSetWindowShouldClose(window, GLFW_TRUE);
178179
break;
@@ -197,6 +198,11 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action,
197198
printf("(( cursor is hidden ))\n");
198199
break;
199200

201+
case GLFW_KEY_C:
202+
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_CAPTURED);
203+
printf("(( cursor is captured ))\n");
204+
break;
205+
200206
case GLFW_KEY_R:
201207
if (!glfwRawMouseMotionSupported())
202208
break;

0 commit comments

Comments
 (0)