Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-109617: fix ncurses incompatibility on macOS with Xcode 15 #111258

Merged
merged 11 commits into from
May 4, 2024
5 changes: 3 additions & 2 deletions Include/py_curses.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# endif
#endif

#if !defined(HAVE_CURSES_IS_PAD) && defined(WINDOW_HAS_FLAGS)
ambv marked this conversation as resolved.
Show resolved Hide resolved
#if defined(WINDOW_HAS_FLAGS)
/* The following definition is necessary for ncurses 5.7; without it,
some of [n]curses.h set NCURSES_OPAQUE to 1, and then Python
can't get at the WINDOW flags field. */
Expand All @@ -39,7 +39,8 @@
#ifdef HAVE_NCURSES_H
/* configure was checking <curses.h>, but we will
use <ncurses.h>, which has some or all these features. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the version check in the comment?

#if !defined(WINDOW_HAS_FLAGS) && !(NCURSES_OPAQUE+0)
#if !defined(WINDOW_HAS_FLAGS) && \
(NCURSES_VERSION_PATCH+0 < 20070303 || !(NCURSES_OPAQUE+0))
#define WINDOW_HAS_FLAGS 1
#endif
#if !defined(HAVE_CURSES_IS_PAD) && NCURSES_VERSION_PATCH+0 >= 20090906
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`ncurses`: fixed a crash that could occur on macOS 13 or earlier when
Python was built with Apple Xcode 15's SDK.
61 changes: 26 additions & 35 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1155,11 +1155,19 @@ int py_mvwdelch(WINDOW *w, int y, int x)
}
#endif

static inline bool
py_is_pad(const WINDOW *win) {
#if defined(HAVE_CURSES_IS_PAD)
#define py_is_pad(win) is_pad(win)
#elif defined(WINDOW_HAS_FLAGS)
#define py_is_pad(win) ((win) ? ((win)->_flags & _ISPAD) != 0 : FALSE)
// is_pad is defined, either as a macro or as a function
ambv marked this conversation as resolved.
Show resolved Hide resolved
return is_pad(win);
#elif WINDOW_HAS_FLAGS
// is_pad is not defined, but we can inspect struct internals
ambv marked this conversation as resolved.
Show resolved Hide resolved
return (win ? (win->_flags & _ISPAD) != 0 : FALSE);
#else
// probably not ncurses
ambv marked this conversation as resolved.
Show resolved Hide resolved
return FALSE;
#endif
}

/* chgat, added by Fabian Kreutz <fabian.kreutz at gmx.net> */
#ifdef HAVE_CURSES_WCHGAT
Expand Down Expand Up @@ -1332,15 +1340,13 @@ _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
if (!PyCurses_ConvertToChtype(self, ch, &ch_))
return NULL;

#ifdef py_is_pad
if (py_is_pad(self->win)) {
return PyCursesCheckERR(pechochar(self->win, ch_ | (attr_t)attr),
"echochar");
}
else
#endif
} else {
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
return PyCursesCheckERR(wechochar(self->win, ch_ | (attr_t)attr),
"echochar");
}
}

#ifdef NCURSES_MOUSE_VERSION
Expand Down Expand Up @@ -1975,7 +1981,6 @@ _curses_window_is_linetouched_impl(PyCursesWindowObject *self, int line)
return PyBool_FromLong(erg);
}

#ifdef py_is_pad
/*[clinic input]
_curses.window.noutrefresh

Expand All @@ -2002,25 +2007,9 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self,
int sminrow, int smincol, int smaxrow,
int smaxcol)
/*[clinic end generated code: output=809a1f3c6a03e23e input=3e56898388cd739e]*/
#else
/*[clinic input]
_curses.window.noutrefresh

Mark for refresh but wait.

This function updates the data structure representing the desired state of the
window, but does not force an update of the physical screen. To accomplish
that, call doupdate().
[clinic start generated code]*/

static PyObject *
_curses_window_noutrefresh_impl(PyCursesWindowObject *self)
/*[clinic end generated code: output=6ef6dec666643fee input=876902e3fa431dbd]*/
#endif
{
int rtn;

#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
PyErr_SetString(PyCursesError,
Expand All @@ -2039,7 +2028,6 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self)
"noutrefresh() takes no arguments (6 given)");
return NULL;
}
#endif
Py_BEGIN_ALLOW_THREADS
rtn = wnoutrefresh(self->win);
Py_END_ALLOW_THREADS
Expand Down Expand Up @@ -2244,7 +2232,6 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
{
int rtn;

#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
PyErr_SetString(PyCursesError,
Expand All @@ -2257,7 +2244,6 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
Py_END_ALLOW_THREADS
return PyCursesCheckERR(rtn, "prefresh");
}
#endif
if (group_right_1) {
PyErr_SetString(PyExc_TypeError,
"refresh() takes no arguments (6 given)");
Expand Down Expand Up @@ -2320,13 +2306,11 @@ _curses_window_subwin_impl(PyCursesWindowObject *self, int group_left_1,
WINDOW *win;

/* printf("Subwin: %i %i %i %i \n", nlines, ncols, begin_y, begin_x); */
#ifdef py_is_pad
if (py_is_pad(self->win)) {
win = subpad(self->win, nlines, ncols, begin_y, begin_x);
}
else
#endif
} else {
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
win = subwin(self->win, nlines, ncols, begin_y, begin_x);
}

if (win == NULL) {
PyErr_SetString(PyCursesError, catchall_NULL);
Expand Down Expand Up @@ -4586,17 +4570,24 @@ make_ncurses_version(PyTypeObject *type)
if (ncurses_version == NULL) {
return NULL;
}

const char *str = curses_version();
unsigned long major = 0, minor = 0, patch = 0;
if (!str || sscanf(str, "%*[^0-9]%lu.%lu.%lu", &major, &minor, &patch) < 3) {
ambv marked this conversation as resolved.
Show resolved Hide resolved
// Fallback to header version, which cannot be that wrong
major = NCURSES_VERSION_MAJOR;
minor = NCURSES_VERSION_MINOR;
patch = NCURSES_VERSION_PATCH;
}
#define SetIntItem(flag) \
PyStructSequence_SET_ITEM(ncurses_version, pos++, PyLong_FromLong(flag)); \
if (PyErr_Occurred()) { \
Py_CLEAR(ncurses_version); \
return NULL; \
}

SetIntItem(NCURSES_VERSION_MAJOR)
SetIntItem(NCURSES_VERSION_MINOR)
SetIntItem(NCURSES_VERSION_PATCH)
SetIntItem(major)
SetIntItem(minor)
SetIntItem(patch)
#undef SetIntItem

return ncurses_version;
Expand Down
36 changes: 1 addition & 35 deletions Modules/clinic/_cursesmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 44 additions & 11 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -6492,7 +6492,10 @@ AC_DEFUN([PY_CHECK_CURSES_FUNC],
[py_var],
[AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM(
[@%:@include <curses.h>], [
[[
#define NCURSES_OPAQUE 0
#include <curses.h>
]], [
erlend-aasland marked this conversation as resolved.
Show resolved Hide resolved
#ifndef $1
void *x=$1
#endif
Expand Down