Skip to content

Commit a335162

Browse files
gh-152502: Detect the curses mouse interface and is_* methods portably
The mouse interface (getmouse(), has_mouse(), the BUTTON* constants, window.mouse_trafo(), ...) and the window is_*() state-query methods were gated on ncurses-specific macros, so they were dropped on other curses implementations that provide them, such as NetBSD curses and PDCurses. Gate them instead on configure capability probes (for functions NetBSD curses provides, since it defines no identifying macro) or on NCURSES_EXT_FUNCS or the PDCURSES macro (for functions only ncurses and PDCurses provide). Probe for getmouse() with its X/Open getmouse(MEVENT *) signature, since PDCurses declares an incompatible getmouse(void) unless built for the ncurses mouse API, which PDC_NCMOUSE now always selects. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a3dc784 commit a335162

8 files changed

Lines changed: 341 additions & 55 deletions

File tree

Doc/library/curses.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ Mouse
465465

466466
Return ``True`` if the mouse driver has been successfully initialized.
467467

468-
Availability: ncurses 5.8 or later.
468+
Availability: if the underlying curses library provides ``has_mouse()``.
469469

470470
.. versionadded:: next
471471

Include/py_curses.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
#define NCURSES_OPAQUE 0
3737
#endif
3838

39+
/* PDCurses exposes its ncurses-compatible mouse API, the one this module uses,
40+
only when this is defined before the curses header is included below.
41+
Ignored by other curses implementations. */
42+
#ifndef PDC_NCMOUSE
43+
# define PDC_NCMOUSE
44+
#endif
45+
3946
#if defined(HAVE_NCURSESW_NCURSES_H)
4047
# include <ncursesw/ncurses.h>
4148
#elif defined(HAVE_NCURSESW_CURSES_H)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Detect the :mod:`curses` mouse interface (:func:`~curses.getmouse`,
2+
:func:`~curses.has_mouse`, the ``BUTTON*`` constants, and others) and the
3+
window ``is_*`` state-query methods with configure capability probes or library
4+
macros instead of gating them on ncurses-specific macros. They are now also
5+
available with other curses implementations that provide them, such as NetBSD
6+
curses and PDCurses (the latter underpins ``windows-curses``).

Modules/_cursesmodule.c

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,20 +1828,28 @@ Window_NoArgNoReturnFunction(wdeleteln)
18281828

18291829
Window_NoArgTrueFalseFunction(is_wintouched)
18301830

1831-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404
1831+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) || defined(PDCURSES)
18321832
Window_NoArgTrueFalseFunction(is_cleared)
18331833
Window_NoArgTrueFalseFunction(is_idcok)
18341834
Window_NoArgTrueFalseFunction(is_idlok)
18351835
Window_NoArgTrueFalseFunction(is_immedok)
1836-
Window_NoArgTrueFalseFunction(is_keypad)
1837-
Window_NoArgTrueFalseFunction(is_leaveok)
18381836
Window_NoArgTrueFalseFunction(is_nodelay)
18391837
Window_NoArgTrueFalseFunction(is_notimeout)
1840-
Window_NoArgTrueFalseFunction(is_pad)
18411838
Window_NoArgTrueFalseFunction(is_scrollok)
18421839
Window_NoArgTrueFalseFunction(is_subwin)
18431840
Window_NoArgTrueFalseFunction(is_syncok)
1841+
#endif
1842+
#if defined(HAVE_CURSES_IS_KEYPAD) || defined(PDCURSES)
1843+
Window_NoArgTrueFalseFunction(is_keypad)
1844+
#endif
1845+
#if defined(HAVE_CURSES_IS_LEAVEOK) || defined(PDCURSES)
1846+
Window_NoArgTrueFalseFunction(is_leaveok)
1847+
#endif
1848+
#if defined(HAVE_CURSES_IS_PAD) || defined(PDCURSES)
1849+
Window_NoArgTrueFalseFunction(is_pad)
1850+
#endif
18441851

1852+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) || defined(PDCURSES)
18451853
static PyObject *
18461854
PyCursesWindow_getdelay(PyObject *op, PyObject *Py_UNUSED(ignored))
18471855
{
@@ -1872,7 +1880,7 @@ PyCursesWindow_getparent(PyObject *op, PyObject *Py_UNUSED(ignored))
18721880
}
18731881
return Py_NewRef((PyObject *)self->orig);
18741882
}
1875-
#endif /* NCURSES_EXT_FUNCS */
1883+
#endif /* NCURSES_EXT_FUNCS >= 20110404 || PDCURSES */
18761884

18771885
Window_NoArgNoReturnVoidFunction(wsyncup)
18781886
Window_NoArgNoReturnVoidFunction(wsyncdown)
@@ -2985,7 +2993,7 @@ _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
29852993
return curses_window_check_err(self, rtn, funcname, "echochar");
29862994
}
29872995

2988-
#ifdef NCURSES_MOUSE_VERSION
2996+
#if defined(HAVE_CURSES_GETMOUSE) || defined(PDCURSES)
29892997
/*[clinic input]
29902998
@permit_long_summary
29912999
_curses.window.enclose
@@ -4881,7 +4889,7 @@ static PyMethodDef PyCursesWindow_methods[] = {
48814889
_CURSES_WINDOW_GETCH_METHODDEF
48824890
_CURSES_WINDOW_GETKEY_METHODDEF
48834891
_CURSES_WINDOW_GET_WCH_METHODDEF
4884-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404
4892+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) || defined(PDCURSES)
48854893
{"getdelay", PyCursesWindow_getdelay, METH_NOARGS,
48864894
"getdelay($self, /)\n--\n\n"
48874895
"Return the window's read timeout in milliseconds.\n\n"
@@ -4890,15 +4898,15 @@ static PyMethodDef PyCursesWindow_methods[] = {
48904898
{"getmaxyx", PyCursesWindow_getmaxyx, METH_NOARGS,
48914899
"getmaxyx($self, /)\n--\n\n"
48924900
"Return a tuple (y, x) of the window height and width."},
4893-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404
4901+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) || defined(PDCURSES)
48944902
{"getparent", PyCursesWindow_getparent, METH_NOARGS,
48954903
"getparent($self, /)\n--\n\n"
48964904
"Return the parent window, or None if this is not a subwindow."},
48974905
#endif
48984906
{"getparyx", PyCursesWindow_getparyx, METH_NOARGS,
48994907
"getparyx($self, /)\n--\n\n"
49004908
"Return (y, x) relative to the parent window, or (-1, -1) if none."},
4901-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404
4909+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) || defined(PDCURSES)
49024910
{"getscrreg", PyCursesWindow_getscrreg, METH_NOARGS,
49034911
"getscrreg($self, /)\n--\n\n"
49044912
"Return a tuple (top, bottom) of the current scrolling region."},
@@ -4953,7 +4961,7 @@ static PyMethodDef PyCursesWindow_methods[] = {
49534961
{"is_wintouched", PyCursesWindow_is_wintouched, METH_NOARGS,
49544962
"is_wintouched($self, /)\n--\n\n"
49554963
"Return True if the window changed since the last refresh()."},
4956-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404
4964+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20110404) || defined(PDCURSES)
49574965
{"is_cleared", PyCursesWindow_is_cleared, METH_NOARGS,
49584966
"is_cleared($self, /)\n--\n\n"
49594967
"Return the current value set by clearok()."},
@@ -4966,21 +4974,12 @@ static PyMethodDef PyCursesWindow_methods[] = {
49664974
{"is_immedok", PyCursesWindow_is_immedok, METH_NOARGS,
49674975
"is_immedok($self, /)\n--\n\n"
49684976
"Return the current value set by immedok()."},
4969-
{"is_keypad", PyCursesWindow_is_keypad, METH_NOARGS,
4970-
"is_keypad($self, /)\n--\n\n"
4971-
"Return the current value set by keypad()."},
4972-
{"is_leaveok", PyCursesWindow_is_leaveok, METH_NOARGS,
4973-
"is_leaveok($self, /)\n--\n\n"
4974-
"Return the current value set by leaveok()."},
49754977
{"is_nodelay", PyCursesWindow_is_nodelay, METH_NOARGS,
49764978
"is_nodelay($self, /)\n--\n\n"
49774979
"Return the current value set by nodelay()."},
49784980
{"is_notimeout", PyCursesWindow_is_notimeout, METH_NOARGS,
49794981
"is_notimeout($self, /)\n--\n\n"
49804982
"Return the current value set by notimeout()."},
4981-
{"is_pad", PyCursesWindow_is_pad, METH_NOARGS,
4982-
"is_pad($self, /)\n--\n\n"
4983-
"Return True if the window is a pad."},
49844983
{"is_scrollok", PyCursesWindow_is_scrollok, METH_NOARGS,
49854984
"is_scrollok($self, /)\n--\n\n"
49864985
"Return the current value set by scrollok()."},
@@ -4990,6 +4989,21 @@ static PyMethodDef PyCursesWindow_methods[] = {
49904989
{"is_syncok", PyCursesWindow_is_syncok, METH_NOARGS,
49914990
"is_syncok($self, /)\n--\n\n"
49924991
"Return the current value set by syncok()."},
4992+
#endif
4993+
#if defined(HAVE_CURSES_IS_KEYPAD) || defined(PDCURSES)
4994+
{"is_keypad", PyCursesWindow_is_keypad, METH_NOARGS,
4995+
"is_keypad($self, /)\n--\n\n"
4996+
"Return the current value set by keypad()."},
4997+
#endif
4998+
#if defined(HAVE_CURSES_IS_LEAVEOK) || defined(PDCURSES)
4999+
{"is_leaveok", PyCursesWindow_is_leaveok, METH_NOARGS,
5000+
"is_leaveok($self, /)\n--\n\n"
5001+
"Return the current value set by leaveok()."},
5002+
#endif
5003+
#if defined(HAVE_CURSES_IS_PAD) || defined(PDCURSES)
5004+
{"is_pad", PyCursesWindow_is_pad, METH_NOARGS,
5005+
"is_pad($self, /)\n--\n\n"
5006+
"Return True if the window is a pad."},
49935007
#endif
49945008
{"keypad", PyCursesWindow_keypad, METH_VARARGS,
49955009
"keypad($self, flag, /)\n--\n\n"
@@ -5477,7 +5491,7 @@ _curses_cbreak_impl(PyObject *module, int flag)
54775491
NoArgOrFlagNoReturnFunctionBody(cbreak, flag)
54785492

54795493
/* is_cbreak()/is_echo()/is_nl()/is_raw() were added in ncurses 6.5. */
5480-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427
5494+
#if (defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20240427) || defined(PDCURSES)
54815495
/*[clinic input]
54825496
_curses.is_cbreak
54835497
@@ -5533,7 +5547,7 @@ _curses_is_raw_impl(PyObject *module)
55335547
PyCursesStatefulInitialised(module);
55345548
return PyBool_FromLong(is_raw());
55355549
}
5536-
#endif /* NCURSES_EXT_FUNCS */
5550+
#endif /* NCURSES_EXT_FUNCS >= 20240427 || PDCURSES */
55375551

55385552
/*[clinic input]
55395553
_curses.color_content
@@ -5837,7 +5851,7 @@ _curses_getsyx_impl(PyObject *module)
58375851
}
58385852
#endif
58395853

5840-
#ifdef NCURSES_MOUSE_VERSION
5854+
#if defined(HAVE_CURSES_GETMOUSE) || defined(PDCURSES)
58415855
/*[clinic input]
58425856
_curses.getmouse
58435857
@@ -7040,9 +7054,8 @@ _curses_meta_impl(PyObject *module, int yes)
70407054
return curses_check_err(module, meta(stdscr, yes), "meta", NULL);
70417055
}
70427056

7043-
#ifdef NCURSES_MOUSE_VERSION
7044-
/* has_mouse() was added to ncurses after the 5.7 release. */
7045-
#if defined(NCURSES_EXT_FUNCS) && NCURSES_EXT_FUNCS >= 20081122
7057+
#if defined(HAVE_CURSES_GETMOUSE) || defined(PDCURSES)
7058+
#if defined(HAVE_CURSES_HAS_MOUSE) || defined(PDCURSES)
70467059
/*[clinic input]
70477060
_curses.has_mouse
70487061
@@ -7057,7 +7070,7 @@ _curses_has_mouse_impl(PyObject *module)
70577070

70587071
return PyBool_FromLong(has_mouse());
70597072
}
7060-
#endif /* NCURSES_EXT_FUNCS >= 20081122 */
7073+
#endif /* HAVE_CURSES_HAS_MOUSE || PDCURSES */
70617074

70627075
/*[clinic input]
70637076
_curses.mouseinterval
@@ -8291,7 +8304,7 @@ _curses_slk_attrset_impl(PyObject *module, long attr)
82918304
"slk_attrset", NULL);
82928305
}
82938306

8294-
#ifdef NCURSES_EXT_FUNCS
8307+
#if defined(NCURSES_EXT_FUNCS) || defined(PDCURSES)
82958308
/*[clinic input]
82968309
_curses.slk_attr
82978310
@@ -9036,7 +9049,7 @@ cursesmodule_exec(PyObject *module)
90369049
SetDictInt("COLOR_CYAN", COLOR_CYAN);
90379050
SetDictInt("COLOR_WHITE", COLOR_WHITE);
90389051

9039-
#ifdef NCURSES_MOUSE_VERSION
9052+
#if defined(HAVE_CURSES_GETMOUSE) || defined(PDCURSES)
90409053
/* Mouse-related constants */
90419054
SetDictInt("BUTTON1_PRESSED", BUTTON1_PRESSED);
90429055
SetDictInt("BUTTON1_RELEASED", BUTTON1_RELEASED);
@@ -9062,7 +9075,7 @@ cursesmodule_exec(PyObject *module)
90629075
SetDictInt("BUTTON4_DOUBLE_CLICKED", BUTTON4_DOUBLE_CLICKED);
90639076
SetDictInt("BUTTON4_TRIPLE_CLICKED", BUTTON4_TRIPLE_CLICKED);
90649077

9065-
#if NCURSES_MOUSE_VERSION > 1
9078+
#ifdef BUTTON5_PRESSED
90669079
SetDictInt("BUTTON5_PRESSED", BUTTON5_PRESSED);
90679080
SetDictInt("BUTTON5_RELEASED", BUTTON5_RELEASED);
90689081
SetDictInt("BUTTON5_CLICKED", BUTTON5_CLICKED);

0 commit comments

Comments
 (0)