Skip to content

Commit 9d61ff8

Browse files
gh-152334: Add curses key-management functions
Add define_key(), key_defined() and keyok(), the ncurses extensions for managing how control strings are recognized as key codes, beyond the predefined terminfo keys and the all-or-nothing window.keypad(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bbf7786 commit 9d61ff8

6 files changed

Lines changed: 295 additions & 1 deletion

File tree

Doc/library/curses.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,37 @@ The module :mod:`!curses` defines the following functions:
345345
a key with that value.
346346

347347

348+
.. function:: define_key(definition, keycode)
349+
350+
Define an escape sequence *definition*, a string, as a key that generates
351+
the key code *keycode*, so that :mod:`curses` interprets it like one of the
352+
keys predefined in the terminal database.
353+
354+
If *definition* is ``None``, any existing binding for *keycode* is removed.
355+
If *keycode* is zero or negative, any existing binding for *definition* is
356+
removed.
357+
358+
.. versionadded:: next
359+
360+
361+
.. function:: key_defined(definition)
362+
363+
Return the key code bound to the escape sequence *definition*, a string,
364+
``0`` if no key code is bound to it, or ``-1`` if *definition* is a prefix
365+
of a longer bound sequence (and so is ambiguous).
366+
367+
.. versionadded:: next
368+
369+
370+
.. function:: keyok(keycode, enable)
371+
372+
Enable (if *enable* is true) or disable (otherwise) interpretation of the
373+
key code *keycode*. Unlike :meth:`window.keypad`, this affects a single
374+
key code rather than all of them.
375+
376+
.. versionadded:: next
377+
378+
348379
.. function:: halfdelay(tenths)
349380

350381
Used for half-delay mode, which is similar to cbreak mode in that characters

Doc/whatsnew/3.16.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ curses
165165
:func:`~curses.scr_set`, which dump the whole screen to a file and restore it.
166166
(Contributed by Serhiy Storchaka in :gh:`152260`.)
167167

168+
* Add the :mod:`curses` key-management functions :func:`~curses.define_key`,
169+
:func:`~curses.key_defined` and :func:`~curses.keyok`, available when built
170+
against an ncurses with ``NCURSES_EXT_FUNCS``.
171+
(Contributed by Serhiy Storchaka in :gh:`152334`.)
172+
168173
gzip
169174
----
170175

Lib/test/test_curses.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,21 @@ def test_env_queries(self):
13151315
self.assertIsInstance(c, str)
13161316
self.assertEqual(len(c), 1)
13171317

1318+
@requires_curses_func('define_key')
1319+
def test_key_management(self):
1320+
# Bind a custom escape sequence to a free key code and read it back.
1321+
seq = '\x1bspam'
1322+
keycode = 0o600
1323+
curses.define_key(seq, keycode)
1324+
self.assertEqual(curses.key_defined(seq), keycode)
1325+
# keyok enables or disables interpretation of a single key code.
1326+
# Use the key code just defined, which is guaranteed to be known.
1327+
self.assertIsNone(curses.keyok(keycode, False))
1328+
self.assertIsNone(curses.keyok(keycode, True))
1329+
# Passing None removes the binding for the key code.
1330+
curses.define_key(None, keycode)
1331+
self.assertEqual(curses.key_defined(seq), 0)
1332+
13181333
def test_output_options(self):
13191334
stdscr = self.stdscr
13201335

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add the :func:`curses.define_key`, :func:`curses.key_defined` and
2+
:func:`curses.keyok` key-management functions.

Modules/_cursesmodule.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5776,6 +5776,77 @@ _curses_has_key_impl(PyObject *module, int key)
57765776
}
57775777
#endif
57785778

5779+
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS
5780+
/*[clinic input]
5781+
_curses.define_key
5782+
5783+
definition: str(accept={str, NoneType})
5784+
Escape sequence to bind, or None to remove a binding.
5785+
keycode: int
5786+
Key code to generate.
5787+
/
5788+
5789+
Define an escape sequence for a key code.
5790+
5791+
If definition is None, any existing binding for keycode is removed.
5792+
If keycode is zero or negative, the binding for definition is removed.
5793+
[clinic start generated code]*/
5794+
5795+
static PyObject *
5796+
_curses_define_key_impl(PyObject *module, const char *definition,
5797+
int keycode)
5798+
/*[clinic end generated code: output=9dc655653bb09062 input=8db9e0d8802c709f]*/
5799+
{
5800+
PyCursesStatefulInitialised(module);
5801+
5802+
return curses_check_err(module, define_key(definition, keycode),
5803+
"define_key", NULL);
5804+
}
5805+
5806+
/*[clinic input]
5807+
_curses.key_defined
5808+
5809+
definition: str
5810+
Escape sequence.
5811+
/
5812+
5813+
Return the key code bound to an escape sequence.
5814+
5815+
Return 0 if no key code is bound to the escape sequence, or -1 if the
5816+
escape sequence is a prefix of another bound sequence (so ambiguous).
5817+
[clinic start generated code]*/
5818+
5819+
static PyObject *
5820+
_curses_key_defined_impl(PyObject *module, const char *definition)
5821+
/*[clinic end generated code: output=2d357e01fe277c88 input=03749d7bd79d8d2c]*/
5822+
{
5823+
PyCursesStatefulInitialised(module);
5824+
5825+
return PyLong_FromLong(key_defined(definition));
5826+
}
5827+
5828+
/*[clinic input]
5829+
_curses.keyok
5830+
5831+
keycode: int
5832+
Key code.
5833+
enable: bool
5834+
Whether the key code is interpreted.
5835+
/
5836+
5837+
Enable or disable interpretation of an individual key code.
5838+
[clinic start generated code]*/
5839+
5840+
static PyObject *
5841+
_curses_keyok_impl(PyObject *module, int keycode, int enable)
5842+
/*[clinic end generated code: output=43eab0b4d9973e44 input=5bee51d850f481b9]*/
5843+
{
5844+
PyCursesStatefulInitialised(module);
5845+
5846+
return curses_check_err(module, keyok(keycode, enable), "keyok", NULL);
5847+
}
5848+
#endif
5849+
57795850
/*[clinic input]
57805851
_curses.init_color
57815852
@@ -7759,6 +7830,9 @@ static PyMethodDef cursesmodule_methods[] = {
77597830
_CURSES_HAS_IC_METHODDEF
77607831
_CURSES_HAS_IL_METHODDEF
77617832
_CURSES_HAS_KEY_METHODDEF
7833+
_CURSES_DEFINE_KEY_METHODDEF
7834+
_CURSES_KEY_DEFINED_METHODDEF
7835+
_CURSES_KEYOK_METHODDEF
77627836
_CURSES_HALFDELAY_METHODDEF
77637837
_CURSES_INIT_COLOR_METHODDEF
77647838
_CURSES_INIT_PAIR_METHODDEF

Modules/clinic/_cursesmodule.c.h

Lines changed: 168 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)