#define _CURSES_PAIR_CONTENT_FUNC pair_content
#endif /* _NCURSES_EXTENDED_COLOR_FUNCS */
-typedef struct _cursesmodule_state {
- PyObject *error; // PyCursesError
- PyTypeObject *window_type; // PyCursesWindow_Type
-} _cursesmodule_state;
+typedef struct {
+ PyObject *error; // curses exception type
+ PyTypeObject *window_type; // exposed by PyCursesWindow_Type
+} cursesmodule_state;
-// For now, we keep a global state variable to prepare for PEP 489.
-static _cursesmodule_state curses_global_state;
-
-static inline _cursesmodule_state *
-get_cursesmodule_state(PyObject *Py_UNUSED(module))
+static inline cursesmodule_state *
+get_cursesmodule_state(PyObject *module)
{
- return &curses_global_state;
+ void *state = PyModule_GetState(module);
+ assert(state != NULL);
+ return (cursesmodule_state *)state;
}
-static inline _cursesmodule_state *
-get_cursesmodule_state_by_cls(PyTypeObject *Py_UNUSED(cls))
+static inline cursesmodule_state *
+get_cursesmodule_state_by_cls(PyTypeObject *cls)
{
- return &curses_global_state;
+ void *state = PyType_GetModuleState(cls);
+ assert(state != NULL);
+ return (cursesmodule_state *)state;
}
-static inline _cursesmodule_state *
-get_cursesmodule_state_by_win(PyCursesWindowObject *Py_UNUSED(win))
+static inline cursesmodule_state *
+get_cursesmodule_state_by_win(PyCursesWindowObject *win)
{
- return &curses_global_state;
+ return get_cursesmodule_state_by_cls(Py_TYPE(win));
}
/*[clinic input]
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=ae6cb623018f2cbc]*/
+/* Indicate whether the module has already been loaded or not. */
+static int curses_module_loaded = 0;
+
/* Tells whether setupterm() has been called to initialise terminfo. */
static int curses_setupterm_called = FALSE;
* set and this returns 0. Otherwise, this returns 1.
*
* Since this function can be called in functions that do not
- * have a direct access to the module's state, the exception
- * type is directly taken from the global state for now.
+ * have a direct access to the module's state, '_curses.error'
+ * is imported on demand.
*/
static inline int
_PyCursesCheckFunction(int called, const char *funcname)
if (called == TRUE) {
return 1;
}
- PyErr_Format(curses_global_state.error, "must call %s() first", funcname);
+ PyObject *exc = _PyImport_GetModuleAttrString("_curses", "error");
+ if (exc != NULL) {
+ PyErr_Format(exc, "must call %s() first", funcname);
+ Py_DECREF(exc);
+ }
+ assert(PyErr_Occurred());
return 0;
}
if (called == TRUE) {
return 1;
}
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_Format(state->error, "must call %s() first", funcname);
return 0;
}
/* Utility Functions */
static inline void
-_PyCursesSetError(_cursesmodule_state *state, const char *funcname)
+_PyCursesSetError(cursesmodule_state *state, const char *funcname)
{
if (funcname == NULL) {
PyErr_SetString(state->error, catchall_ERR);
if (code != ERR) {
Py_RETURN_NONE;
} else {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
_PyCursesSetError(state, fname);
return NULL;
}
if (code != ERR) {
Py_RETURN_NONE;
} else {
- _cursesmodule_state *state = get_cursesmodule_state_by_win(win);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(win);
_PyCursesSetError(state, fname);
return NULL;
}
/* Allocation and deallocation of Window Objects */
static PyObject *
-PyCursesWindow_New(_cursesmodule_state *state,
+PyCursesWindow_New(cursesmodule_state *state,
WINDOW *win, const char *encoding)
{
if (encoding == NULL) {
win = derwin(self->win,nlines,ncols,begin_y,begin_x);
if (win == NULL) {
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
PyErr_SetString(state->error, catchall_NULL);
return NULL;
}
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
return PyCursesWindow_New(state, win, NULL);
}
/* getch() returns ERR in nodelay mode */
PyErr_CheckSignals();
if (!PyErr_Occurred()) {
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
PyErr_SetString(state->error, "no input");
}
return NULL;
return NULL;
/* get_wch() returns ERR in nodelay mode */
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
PyErr_SetString(state->error, "no input");
return NULL;
}
#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
PyErr_SetString(state->error,
"noutrefresh() called for a pad "
"requires 6 arguments");
#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
PyErr_SetString(state->error,
"refresh() for a pad requires 6 arguments");
return NULL;
win = subwin(self->win, nlines, ncols, begin_y, begin_x);
if (win == NULL) {
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
PyErr_SetString(state->error, catchall_NULL);
return NULL;
}
- _cursesmodule_state *state = get_cursesmodule_state_by_win(self);
+ cursesmodule_state *state = get_cursesmodule_state_by_win(self);
return PyCursesWindow_New(state, win, self->encoding);
}
PyCursesStatefulInitialisedColor(module);
if (_COLOR_CONTENT_FUNC(color_number, &r, &g, &b) == ERR) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_Format(state->error, "%s() returned ERR",
Py_STRINGIFY(_COLOR_CONTENT_FUNC));
return NULL;
rtn = getmouse( &event );
if (rtn == ERR) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "getmouse() returned ERR");
return NULL;
}
fseek(fp, 0, 0);
win = getwin(fp);
if (win == NULL) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, catchall_NULL);
goto error;
}
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
res = PyCursesWindow_New(state, win, NULL);
error:
COLOR_PAIRS - 1);
}
else {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_Format(state->error, "%s() returned ERR",
Py_STRINGIFY(_CURSES_INIT_PAIR_FUNC));
}
if (curses_initscr_called) {
wrefresh(stdscr);
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
return PyCursesWindow_New(state, stdscr, NULL);
}
win = initscr();
if (win == NULL) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, catchall_NULL);
return NULL;
}
SetDictInt("COLS", COLS);
#undef SetDictInt
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyObject *winobj = PyCursesWindow_New(state, win, NULL);
if (winobj == NULL) {
return NULL;
sys_stdout = PySys_GetObject("stdout");
if (sys_stdout == NULL || sys_stdout == Py_None) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "lost sys.stdout");
return NULL;
}
s = "setupterm: could not find terminfo database";
}
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, s);
return NULL;
}
win = newpad(nlines, ncols);
if (win == NULL) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, catchall_NULL);
return NULL;
}
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
return PyCursesWindow_New(state, win, NULL);
}
win = newwin(nlines,ncols,begin_y,begin_x);
if (win == NULL) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, catchall_NULL);
return NULL;
}
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
return PyCursesWindow_New(state, win, NULL);
}
COLOR_PAIRS - 1);
}
else {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_Format(state->error, "%s() returned ERR",
Py_STRINGIFY(_CURSES_PAIR_CONTENT_FUNC));
}
PyCursesStatefulInitialised(module);
if (start_color() == ERR) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "start_color() returned ERR");
return NULL;
}
result = tparm((char *)str,i1,i2,i3,i4,i5,i6,i7,i8,i9);
if (!result) {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "tparm() returned NULL");
return NULL;
}
if (code != ERR) {
Py_RETURN_NONE;
} else {
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "use_default_colors() returned ERR");
return NULL;
}
/* List of functions defined in the module */
-static PyMethodDef PyCurses_methods[] = {
+static PyMethodDef cursesmodule_methods[] = {
_CURSES_BAUDRATE_METHODDEF
_CURSES_BEEP_METHODDEF
_CURSES_CAN_CHANGE_COLOR_METHODDEF
}
static void *
-curses_capi_new(_cursesmodule_state *state)
+curses_capi_new(cursesmodule_state *state)
{
assert(state->window_type != NULL);
void **capi = (void **)PyMem_Calloc(PyCurses_API_pointers, sizeof(void *));
{
assert(capi != NULL);
void **capi_ptr = (void **)capi;
- assert(capi_ptr[0] != NULL);
- Py_DECREF(capi_ptr[0]); // decref curses window type
+ // In free-threaded builds, capi_ptr[0] may have been already cleared
+ // by curses_capi_capsule_destructor(), hence the use of Py_XDECREF().
+ Py_XDECREF(capi_ptr[0]); // decref curses window type
PyMem_Free(capi_ptr);
}
return capsule;
}
-/* Module initialization */
+/* Module initialization and cleanup functions */
+
+static int
+cursesmodule_traverse(PyObject *mod, visitproc visit, void *arg)
+{
+ cursesmodule_state *state = get_cursesmodule_state(mod);
+ Py_VISIT(state->error);
+ Py_VISIT(state->window_type);
+ return 0;
+}
+
+static int
+cursesmodule_clear(PyObject *mod)
+{
+ cursesmodule_state *state = get_cursesmodule_state(mod);
+ Py_CLEAR(state->error);
+ Py_CLEAR(state->window_type);
+ return 0;
+}
+
+static void
+cursesmodule_free(void *mod)
+{
+ (void)cursesmodule_clear((PyObject *)mod);
+ curses_module_loaded = 0; // allow reloading once garbage-collected
+}
static int
cursesmodule_exec(PyObject *module)
{
- _cursesmodule_state *state = get_cursesmodule_state(module);
+ if (curses_module_loaded) {
+ PyErr_SetString(PyExc_ImportError,
+ "module 'curses' can only be loaded once per process");
+ return -1;
+ }
+ curses_module_loaded = 1;
+
+ cursesmodule_state *state = get_cursesmodule_state(module);
/* Initialize object type */
state->window_type = (PyTypeObject *)PyType_FromModuleAndSpec(
module, &PyCursesWindow_Type_spec, NULL);
return -1;
}
rc = PyDict_SetItemString(module_dict, "error", state->error);
- Py_DECREF(state->error);
if (rc < 0) {
return -1;
}
/* Initialization function for the module */
-static struct PyModuleDef _cursesmodule = {
+static PyModuleDef_Slot cursesmodule_slots[] = {
+ {Py_mod_exec, cursesmodule_exec},
+ {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+ {0, NULL}
+};
+
+static struct PyModuleDef cursesmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_curses",
- .m_size = -1,
- .m_methods = PyCurses_methods,
+ .m_size = sizeof(cursesmodule_state),
+ .m_methods = cursesmodule_methods,
+ .m_slots = cursesmodule_slots,
+ .m_traverse = cursesmodule_traverse,
+ .m_clear = cursesmodule_clear,
+ .m_free = cursesmodule_free
};
PyMODINIT_FUNC
PyInit__curses(void)
{
- // create the module
- PyObject *mod = PyModule_Create(&_cursesmodule);
- if (mod == NULL) {
- goto error;
- }
-#ifdef Py_GIL_DISABLED
- if (PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED) < 0) {
- goto error;
- }
-#endif
- // populate the module
- if (cursesmodule_exec(mod) < 0) {
- goto error;
- }
- return mod;
-
-error:
- Py_XDECREF(mod);
- return NULL;
+ return PyModuleDef_Init(&cursesmodule);
}