Skip to content

Commit

Permalink
Window class input grab API rework (#2379)
Browse files Browse the repository at this point in the history
* Window grab API rework. Grab split to mouse & keyboard
  • Loading branch information
yunline authored Oct 15, 2023
1 parent a917d3f commit e3d34b4
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 28 deletions.
9 changes: 7 additions & 2 deletions buildconfig/stubs/pygame/_window.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,20 @@ class Window:
def minimize(self) -> None: ...
def set_modal_for(self, parent: Window) -> None: ...
def set_icon(self, icon: Surface) -> None: ...

grab: bool

grab_mouse: bool
grab_keyboard: bool
title: str
resizable: bool
borderless: bool
always_on_top: bool
relative_mouse: bool
opacity: float

@property
def mouse_grabbed(self) -> bool: ...
@property
def keyboard_grabbed(self) -> bool: ...
@property
def id(self) -> int: ...
@property
Expand Down
86 changes: 78 additions & 8 deletions docs/reST/ref/sdl2_video.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
:param bool resizable: Create a resizable window.
:param bool minimized: Create a mimized window.
:param bool maximized: Create a maximized window.
:param bool input_grabbed: Create a window with a grabbed input focus.
:param bool mouse_grabbed: Create a window with grabbed mouse input.
:param bool keyboard_grabbed: Create a window with grabbed keyboard input.
:param bool input_focus: Create a window with input focus.
:param bool mouse_focus: Create a window with mouse focus.
:param bool foreign: Marks a window not created by SDL.
Expand All @@ -99,15 +100,84 @@
(X11 only).


.. attribute:: grab
.. attribute:: grab_mouse

| :sl:`Get or set the window's input grab state`
| :sg:`grab -> bool`
| :sl:`Get or set the window's mouse grab mode`
| :sg:`grab_mouse -> bool`
Gets or sets the window's input grab state.
When input is grabbed, the mouse is confined to the window.
If the caller enables a grab while another window is currently grabbed,
the other window loses its grab in favor of the caller's window.
When this attribute is set to ``True``, the window will try to confine the mouse
cursor to itself.

Note this only set the "mode" of grab. The mouse may be confined to another window
depending on the window focus. To get if the mouse is currently restricted to this
window, please use :attr:`mouse_grabbed`.

.. seealso:: :attr:`mouse_grabbed`

.. versionadded:: 2.4.0

.. attribute:: grab_keyboard

| :sl:`Get or set the window's keyboard grab mode`
| :sg:`grab_keyboard -> bool`
When this attribute is set to ``True``, the window will try to capture system
keyboard shortcuts like ``Alt+Tab`` or the ``Meta/Super`` key.

This attribute only set the "mode" of grab. The keyboard may be captured by
another window depending on the window focus. To get if keyboard is currently
captured by this window, please use :attr:`keyboard_grabbed`.

Note that not all system keyboard shortcuts can be captured by applications
(one example is ``Ctrl+Alt+Del`` on Windows).

When keyboard grab is enabled, pygame will continue to handle ``Alt+Tab`` when
the window is full-screen to ensure the user is not trapped in your application.
If you have a custom keyboard shortcut to exit fullscreen mode, you may suppress
this behavior with an environment variable, e.g.
``os.environ["SDL_ALLOW_ALT_TAB_WHILE_GRABBED"] = "0"``.

This attribute requires SDL 2.0.16+.

.. seealso:: :attr:`keyboard_grabbed`

.. versionadded:: 2.4.0

.. attribute:: mouse_grabbed

| :sl:`Get if the mouse cursor is confined to the window (**read-only**)`
| :sg:`mouse_grabbed -> bool`
Get if the mouse cursor is currently grabbed and confined to the window.

Roughly equivalent to this expression:

::

win.grab_mouse and (win is get_grabbed_window())
.. seealso:: :attr:`grab_mouse`

.. versionadded:: 2.4.0

.. attribute:: keyboard_grabbed

| :sl:`Get if the keyboard shortcuts are captured by the window (**read-only**)`
| :sg:`keyboard_grabbed -> bool`
Get if the keyboard shortcuts are currently grabbed and captured by the window.

Roughly equivalent to this expression:

::

win.grab_keyboard and (win is get_grabbed_window())
This attribute requires SDL 2.0.16+.

.. seealso:: :attr:`grab_keyboard`

.. versionadded:: 2.4.0

.. attribute:: relative_mouse

Expand Down
5 changes: 4 additions & 1 deletion src_c/doc/sdl2_video_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
#define DOC_SDL2_VIDEO_GETDRIVERS "get_drivers() -> Iterator[RendererDriverInfo]\nYield info about the rendering drivers available for Renderer objects"
#define DOC_SDL2_VIDEO_GETGRABBEDWINDOW "get_grabbed_window() -> Window or None\nGet the window with input grab enabled"
#define DOC_SDL2_VIDEO_WINDOW "Window(title='pygame window', size=(640, 480), position=None, fullscreen=False, fullscreen_desktop=False, **kwargs) -> Window\npygame object that represents a window"
#define DOC_SDL2_VIDEO_WINDOW_GRAB "grab -> bool\nGet or set the window's input grab state"
#define DOC_SDL2_VIDEO_WINDOW_GRABMOUSE "grab_mouse -> bool\nGet or set the window's mouse grab mode"
#define DOC_SDL2_VIDEO_WINDOW_GRABKEYBOARD "grab_keyboard -> bool\nGet or set the window's keyboard grab mode"
#define DOC_SDL2_VIDEO_WINDOW_MOUSEGRABBED "mouse_grabbed -> bool\nGet if the mouse cursor is confined to the window (**read-only**)"
#define DOC_SDL2_VIDEO_WINDOW_KEYBOARDGRABBED "keyboard_grabbed -> bool\nGet if the keyboard shortcuts are captured by the window (**read-only**)"
#define DOC_SDL2_VIDEO_WINDOW_RELATIVEMOUSE "relative_mouse -> bool\nGet or set the window's relative mouse mode state"
#define DOC_SDL2_VIDEO_WINDOW_TITLE "title -> str\nGet or set the window title"
#define DOC_SDL2_VIDEO_WINDOW_RESIZABLE "resizable -> bool\nGet or set whether the window is resizable"
Expand Down
98 changes: 93 additions & 5 deletions src_c/window.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,21 +228,88 @@ window_set_icon(pgWindowObject *self, PyObject *arg)
}

static int
window_set_grab(pgWindowObject *self, PyObject *arg, void *v)
window_set_grab_mouse(pgWindowObject *self, PyObject *arg, void *v)
{
int enable = PyObject_IsTrue(arg);
if (enable == -1)
return -1;

#if SDL_VERSION_ATLEAST(2, 0, 16)
SDL_SetWindowMouseGrab(self->_win, enable);
#else
SDL_SetWindowGrab(self->_win, enable);
#endif

return 0;
}

static PyObject *
window_get_grab(pgWindowObject *self, void *v)
window_get_grab_mouse(pgWindowObject *self, void *v)
{
#if SDL_VERSION_ATLEAST(2, 0, 16)
return PyBool_FromLong(SDL_GetWindowFlags(self->_win) &
SDL_WINDOW_MOUSE_GRABBED);
#else
return PyBool_FromLong(SDL_GetWindowFlags(self->_win) &
SDL_WINDOW_INPUT_GRABBED);
#endif
}

static PyObject *
window_get_mouse_grabbed(pgWindowObject *self, void *v)
{
#if SDL_VERSION_ATLEAST(2, 0, 16)
return PyBool_FromLong(SDL_GetWindowMouseGrab(self->_win));
#else
return PyBool_FromLong(SDL_GetWindowGrab(self->_win));
#endif
}

static int
window_set_grab_keyboard(pgWindowObject *self, PyObject *arg, void *v)
{
#if SDL_VERSION_ATLEAST(2, 0, 16)
int enable = PyObject_IsTrue(arg);
if (enable == -1)
return -1;

SDL_SetWindowKeyboardGrab(self->_win, enable);
#else
if (PyErr_WarnEx(PyExc_Warning, "'grab_keyboard' requires SDL 2.0.16+",
1) == -1) {
return -1;
}
#endif
return 0;
}

static PyObject *
window_get_grab_keyboard(pgWindowObject *self, void *v)
{
#if SDL_VERSION_ATLEAST(2, 0, 16)
return PyBool_FromLong(SDL_GetWindowFlags(self->_win) &
SDL_WINDOW_KEYBOARD_GRABBED);
#else
if (PyErr_WarnEx(PyExc_Warning, "'grab_keyboard' requires SDL 2.0.16+",
1) == -1) {
return NULL;
}
return PyBool_FromLong(SDL_FALSE);
#endif
}

static PyObject *
window_get_keyboard_grabbed(pgWindowObject *self, void *v)
{
#if SDL_VERSION_ATLEAST(2, 0, 16)
return PyBool_FromLong(SDL_GetWindowKeyboardGrab(self->_win));
#else
if (PyErr_WarnEx(PyExc_Warning, "'keyboard_captured' requires SDL 2.0.16+",
1) == -1) {
return NULL;
}
return PyBool_FromLong(SDL_FALSE);
#endif
}

static int
Expand Down Expand Up @@ -626,14 +693,28 @@ window_init(pgWindowObject *self, PyObject *args, PyObject *kwargs)
if (_value_bool)
flags |= SDL_WINDOW_MAXIMIZED;
}
else if (!strcmp(_key_str, "input_grabbed")) {
else if (!strcmp(_key_str, "mouse_grabbed")) {
if (_value_bool)
#if SDL_VERSION_ATLEAST(2, 0, 16)
flags |= SDL_WINDOW_MOUSE_GRABBED;
#else
flags |= SDL_WINDOW_INPUT_GRABBED;
#endif
}
else if (!strcmp(_key_str, "keyboard_grabbed")) {
if (_value_bool) {
#if SDL_VERSION_ATLEAST(2, 0, 16)
flags |= SDL_WINDOW_KEYBOARD_GRABBED;
#else
if (PyErr_WarnEx(PyExc_Warning,
"Keyword 'keyboard_grabbed' requires "
"SDL 2.0.16+",
1) == -1) {
return -1;
}
#endif
}
}
else if (!strcmp(_key_str, "input_focus")) {
if (_value_bool) {
flags |= SDL_WINDOW_INPUT_FOCUS;
Expand Down Expand Up @@ -827,8 +908,15 @@ static PyMethodDef window_methods[] = {
{NULL, NULL, 0, NULL}};

static PyGetSetDef _window_getset[] = {
{"grab", (getter)window_get_grab, (setter)window_set_grab,
DOC_SDL2_VIDEO_WINDOW_GRAB, NULL},
{"grab_mouse", (getter)window_get_grab_mouse,
(setter)window_set_grab_mouse, DOC_SDL2_VIDEO_WINDOW_GRABMOUSE, NULL},
{"grab_keyboard", (getter)window_get_grab_keyboard,
(setter)window_set_grab_keyboard, DOC_SDL2_VIDEO_WINDOW_GRABKEYBOARD,
NULL},
{"mouse_grabbed", (getter)window_get_mouse_grabbed, NULL,
DOC_SDL2_VIDEO_WINDOW_MOUSEGRABBED, NULL},
{"keyboard_grabbed", (getter)window_get_keyboard_grabbed, NULL,
DOC_SDL2_VIDEO_WINDOW_KEYBOARDGRABBED, NULL},
{"title", (getter)window_get_title, (setter)window_set_title,
DOC_SDL2_VIDEO_WINDOW_TITLE, NULL},
{"resizable", (getter)window_get_resizable, (setter)window_set_resizable,
Expand Down
36 changes: 24 additions & 12 deletions test/window_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pygame._sdl2.video import Window
from pygame.version import SDL

# os.environ["SDL_VIDEODRIVER"] = "dummy"
os.environ["SDL_VIDEODRIVER"] = "dummy"

pygame.init()

Expand All @@ -27,18 +27,30 @@ def bool_attr_test(self, attr):
setattr(self.win, attr, 17)
self.assertIsInstance(getattr(self.win, attr), bool)

def test_grab(self):
self.bool_attr_test("grab")
def test_grab_mouse_keyboard(self):
self.bool_attr_test("grab_mouse")
self.bool_attr_test("grab_keyboard")

@unittest.skipIf(
os.environ.get("SDL_VIDEODRIVER") == "dummy",
"requires the SDL_VIDEODRIVER to be a non dummy value",
)
def test_grab_set(self):
self.win.grab = True
self.assertTrue(self.win.grab)
self.win.grab = False
self.assertFalse(self.win.grab)
self.win.grab_mouse = True
self.assertTrue(self.win.grab_mouse)
self.win.grab_mouse = False
self.assertFalse(self.win.grab_mouse)

if SDL >= (2, 0, 16):
self.win.grab_keyboard = True
self.assertTrue(self.win.grab_keyboard)
self.win.grab_keyboard = False
self.assertFalse(self.win.grab_keyboard)

def test_mouse_keyboard_grabbed(self):
self.assertIsInstance(getattr(self.win, "mouse_grabbed"), bool)
self.assertIsInstance(getattr(self.win, "keyboard_grabbed"), bool)
self.assertRaises(
AttributeError, lambda: setattr(self.win, "mouse_grabbed", False)
)
self.assertRaises(
AttributeError, lambda: setattr(self.win, "keyboard_grabbed", False)
)

def test_title(self):
self.assertEqual(self.win.title, self.DEFAULT_TITLE)
Expand Down

0 comments on commit e3d34b4

Please sign in to comment.