diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index e15067e5743bc4..801fbec96c6ee6 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -1040,6 +1040,26 @@ Terminfo database :func:`setupterm` (or :func:`initscr`) must be called first. +.. function:: tgetent(term) +.. function:: tgetflag(id) +.. function:: tgetnum(id) +.. function:: tgetstr(id) +.. function:: tgoto(capability, col, row) + + Access the terminal's legacy termcap entries. Call :func:`setupterm` first. + ``tgetent`` returns ``0`` or ``1``; the other lookup functions return their + termcap value, or ``None`` when no value is available. *capability* must be + a bytes object returned by :func:`tgetstr`. + +.. function:: mcprint(string, count) + + Send *count* copies of *string* to the terminal printer. + +.. function:: vidattr(attrs) +.. function:: vidputs(attrs) + + Set the terminal video attributes represented by *attrs*. + Utilities ~~~~~~~~~ diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index ec64ce52dd9e8c..180dfbf1134017 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -1886,6 +1886,35 @@ def test_misc_module_funcs(self): curses.newpad(50, 50) + @requires_curses_func('mcprint') + def test_mcprint(self): + try: + result = curses.mcprint('x', 1) + except curses.error: + pass + else: + self.assertIsNone(result) + + @requires_curses_func('tgetent') + def test_termcap_functions(self): + self.assertIn(curses.tgetent(os.environ.get('TERM', 'unknown')), (0, 1)) + self.assertIsInstance(curses.tgetflag('am'), int) + self.assertIsInstance(curses.tgetnum('co'), int) + value = curses.tgetstr('cl') + if value is None: + self.assertIsNone(value) + else: + self.assertIsInstance(value, bytes) + self.assertIsInstance(curses.tgoto(value, 0, 0), bytes) + + @requires_curses_func('vidattr') + def test_video_attributes(self): + for function in curses.vidattr, curses.vidputs: + try: + self.assertIsNone(function(curses.A_NORMAL)) + except curses.error: + pass + def test_env_queries(self): self.assertIsInstance(curses.termname(), bytes) self.assertIsInstance(curses.longname(), bytes) diff --git a/Modules/_curses_form.c b/Modules/_curses_form.c new file mode 100644 index 00000000000000..cff46c65f4d54f --- /dev/null +++ b/Modules/_curses_form.c @@ -0,0 +1,715 @@ +/* + * Interface to the ncurses panel library + * + * Original version by Thomas Gellekum + */ + +/* Release Number */ + +static const char PyCursesVersion[] = "0.1"; + +/* Includes */ + +#include "Python.h" + +#include "py_curses.h" + +#include + +typedef struct { + PyObject *PyCursesError; + PyTypeObject *PyCursesPanel_Type; +} _curses_panel_state; + +static inline _curses_panel_state * +get_curses_panel_state(PyObject *module) +{ + void *state = PyModule_GetState(module); + assert(state != NULL); + return (_curses_panel_state *)state; +} + +static int +_curses_panel_clear(PyObject *mod) +{ + _curses_panel_state *state = get_curses_panel_state(mod); + Py_CLEAR(state->PyCursesError); + Py_CLEAR(state->PyCursesPanel_Type); + return 0; +} + +static int +_curses_panel_traverse(PyObject *mod, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(mod)); + _curses_panel_state *state = get_curses_panel_state(mod); + Py_VISIT(state->PyCursesError); + Py_VISIT(state->PyCursesPanel_Type); + return 0; +} + +static void +_curses_panel_free(void *mod) +{ + _curses_panel_clear((PyObject *) mod); +} + +/* Utility Functions */ + +/* + * Check the return code from a curses function and return None + * or raise an exception as appropriate. + */ + +static PyObject * +PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname) +{ + if (code != ERR) { + Py_RETURN_NONE; + } + else { + if (fname == NULL) { + PyErr_SetString(state->PyCursesError, catchall_ERR); + } + else { + PyErr_Format(state->PyCursesError, "%s() returned ERR", fname); + } + return NULL; + } +} + +/***************************************************************************** + The Panel Object +******************************************************************************/ + +/* Definition of the panel object and panel type */ + +typedef struct { + PyObject_HEAD + PANEL *pan; + PyCursesWindowObject *wo; /* for reference counts */ +} PyCursesPanelObject; + +/* Some helper functions. The problem is that there's always a window + associated with a panel. To ensure that Python's GC doesn't pull + this window from under our feet we need to keep track of references + to the corresponding window object within Python. We can't use + dupwin(oldwin) to keep a copy of the curses WINDOW because the + contents of oldwin is copied only once; code like + + win = newwin(...) + pan = win.panel() + win.addstr(some_string) + pan.window().addstr(other_string) + + will fail. */ + +/* We keep a linked list of PyCursesPanelObjects, lop. A list should + suffice, I don't expect more than a handful or at most a few + dozens of panel objects within a typical program. */ +typedef struct _list_of_panels { + PyCursesPanelObject *po; + struct _list_of_panels *next; +} list_of_panels; + +/* list anchor */ +static list_of_panels *lop; + +/* Insert a new panel object into lop */ +static int +insert_lop(PyCursesPanelObject *po) +{ + list_of_panels *new; + + if ((new = (list_of_panels *)PyMem_Malloc(sizeof(list_of_panels))) == NULL) { + PyErr_NoMemory(); + return -1; + } + new->po = po; + new->next = lop; + lop = new; + return 0; +} + +/* Remove the panel object from lop */ +static void +remove_lop(PyCursesPanelObject *po) +{ + list_of_panels *temp, *n; + + temp = lop; + if (temp->po == po) { + lop = temp->next; + PyMem_Free(temp); + return; + } + while (temp->next == NULL || temp->next->po != po) { + if (temp->next == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "remove_lop: can't find Panel Object"); + return; + } + temp = temp->next; + } + n = temp->next->next; + PyMem_Free(temp->next); + temp->next = n; + return; +} + +/* Return the panel object that corresponds to pan */ +static PyCursesPanelObject * +find_po(PANEL *pan) +{ + list_of_panels *temp; + for (temp = lop; temp->po->pan != pan; temp = temp->next) + if (temp->next == NULL) return NULL; /* not found!? */ + return temp->po; +} + +/*[clinic input] +module _curses_panel +class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=2f4ef263ca850a31]*/ + +#include "clinic/_curses_panel.c.h" + +/* ------------- PANEL routines --------------- */ + +/*[clinic input] +_curses_panel.panel.bottom + + cls: defining_class + +Push the panel to the bottom of the stack. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=8ec7fbbc08554021 input=6b7d2c0578b5a1c4]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + return PyCursesCheckERR(state, bottom_panel(self->pan), "bottom"); +} + +/*[clinic input] +_curses_panel.panel.hide + + cls: defining_class + +Hide the panel. + +This does not delete the object, it just makes the window on screen invisible. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=cc6ab7203cdc1450 input=1bfc741f473e6055]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + return PyCursesCheckERR(state, hide_panel(self->pan), "hide"); +} + +/*[clinic input] +_curses_panel.panel.show + + cls: defining_class + +Display the panel (which might have been hidden). +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=dc3421de375f0409 input=8122e80151cb4379]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + return PyCursesCheckERR(state, show_panel(self->pan), "show"); +} + +/*[clinic input] +_curses_panel.panel.top + + cls: defining_class + +Push panel to the top of the stack. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls) +/*[clinic end generated code: output=10a072e511e873f7 input=1f372d597dda3379]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + return PyCursesCheckERR(state, top_panel(self->pan), "top"); +} + +/* Allocation and deallocation of Panel Objects */ + +static PyObject * +PyCursesPanel_New(_curses_panel_state *state, PANEL *pan, + PyCursesWindowObject *wo) +{ + PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject, + state->PyCursesPanel_Type); + if (po == NULL) { + return NULL; + } + + po->pan = pan; + if (insert_lop(po) < 0) { + po->wo = NULL; + Py_DECREF(po); + return NULL; + } + po->wo = (PyCursesWindowObject*)Py_NewRef(wo); + return (PyObject *)po; +} + +static void +PyCursesPanel_Dealloc(PyCursesPanelObject *po) +{ + PyObject *tp, *obj; + + tp = (PyObject *) Py_TYPE(po); + obj = (PyObject *) panel_userptr(po->pan); + if (obj) { + (void)set_panel_userptr(po->pan, NULL); + Py_DECREF(obj); + } + (void)del_panel(po->pan); + if (po->wo != NULL) { + Py_DECREF(po->wo); + remove_lop(po); + } + PyObject_Free(po); + Py_DECREF(tp); +} + +/* panel_above(NULL) returns the bottom panel in the stack. To get + this behaviour we use curses.panel.bottom_panel(). */ +/*[clinic input] +_curses_panel.panel.above + +Return the panel above the current panel. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_above_impl(PyCursesPanelObject *self) +/*[clinic end generated code: output=70ac06d25fd3b4da input=c059994022976788]*/ +{ + PANEL *pan; + PyCursesPanelObject *po; + + pan = panel_above(self->pan); + + if (pan == NULL) { /* valid output, it means the calling panel + is on top of the stack */ + Py_RETURN_NONE; + } + po = find_po(pan); + if (po == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "panel_above: can't find Panel Object"); + return NULL; + } + return Py_NewRef(po); +} + +/* panel_below(NULL) returns the top panel in the stack. To get + this behaviour we use curses.panel.top_panel(). */ +/*[clinic input] +_curses_panel.panel.below + +Return the panel below the current panel. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_below_impl(PyCursesPanelObject *self) +/*[clinic end generated code: output=282861122e06e3de input=cc08f61936d297c6]*/ +{ + PANEL *pan; + PyCursesPanelObject *po; + + pan = panel_below(self->pan); + + if (pan == NULL) { /* valid output, it means the calling panel + is on the bottom of the stack */ + Py_RETURN_NONE; + } + po = find_po(pan); + if (po == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "panel_below: can't find Panel Object"); + return NULL; + } + return Py_NewRef(po); +} + +/*[clinic input] +_curses_panel.panel.hidden + +Return True if the panel is hidden (not visible), False otherwise. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_hidden_impl(PyCursesPanelObject *self) +/*[clinic end generated code: output=66eebd1ab4501a71 input=453d4b4fce25e21a]*/ +{ + if (panel_hidden(self->pan)) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +/*[clinic input] +_curses_panel.panel.move + + cls: defining_class + y: int + x: int + / + +Move the panel to the screen coordinates (y, x). +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls, + int y, int x) +/*[clinic end generated code: output=ce546c93e56867da input=60a0e7912ff99849]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + return PyCursesCheckERR(state, move_panel(self->pan, y, x), "move_panel"); +} + +/*[clinic input] +_curses_panel.panel.window + +Return the window object associated with the panel. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_window_impl(PyCursesPanelObject *self) +/*[clinic end generated code: output=5f05940d4106b4cb input=6067353d2c307901]*/ +{ + return Py_NewRef(self->wo); +} + +/*[clinic input] +_curses_panel.panel.replace + + cls: defining_class + win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type") + / + +Change the window associated with the panel to the window win. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_replace_impl(PyCursesPanelObject *self, + PyTypeObject *cls, + PyCursesWindowObject *win) +/*[clinic end generated code: output=c71f95c212d58ae7 input=dbec7180ece41ff5]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + + PyCursesPanelObject *po = find_po(self->pan); + if (po == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "replace_panel: can't find Panel Object"); + return NULL; + } + + int rtn = replace_panel(self->pan, win->win); + if (rtn == ERR) { + PyErr_SetString(state->PyCursesError, "replace_panel() returned ERR"); + return NULL; + } + Py_SETREF(po->wo, (PyCursesWindowObject*)Py_NewRef(win)); + Py_RETURN_NONE; +} + +/*[clinic input] +_curses_panel.panel.set_userptr + + cls: defining_class + obj: object + / + +Set the panel's user pointer to obj. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self, + PyTypeObject *cls, PyObject *obj) +/*[clinic end generated code: output=db74f3db07b28080 input=e3fee2ff7b1b8e48]*/ +{ + PyCursesInitialised; + Py_INCREF(obj); + PyObject *oldobj = (PyObject *) panel_userptr(self->pan); + int rc = set_panel_userptr(self->pan, (void*)obj); + if (rc == ERR) { + /* In case of an ncurses error, decref the new object again */ + Py_DECREF(obj); + } + else { + Py_XDECREF(oldobj); + } + + _curses_panel_state *state = PyType_GetModuleState(cls); + return PyCursesCheckERR(state, rc, "set_panel_userptr"); +} + +/*[clinic input] +_curses_panel.panel.userptr + + cls: defining_class + +Return the user pointer for the panel. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_panel_userptr_impl(PyCursesPanelObject *self, + PyTypeObject *cls) +/*[clinic end generated code: output=eea6e6f39ffc0179 input=f22ca4f115e30a80]*/ +{ + _curses_panel_state *state = PyType_GetModuleState(cls); + + PyCursesInitialised; + PyObject *obj = (PyObject *) panel_userptr(self->pan); + if (obj == NULL) { + PyErr_SetString(state->PyCursesError, "no userptr set"); + return NULL; + } + + return Py_NewRef(obj); +} + + +/* Module interface */ + +static PyMethodDef PyCursesPanel_Methods[] = { + _CURSES_PANEL_PANEL_ABOVE_METHODDEF + _CURSES_PANEL_PANEL_BELOW_METHODDEF + _CURSES_PANEL_PANEL_BOTTOM_METHODDEF + _CURSES_PANEL_PANEL_HIDDEN_METHODDEF + _CURSES_PANEL_PANEL_HIDE_METHODDEF + _CURSES_PANEL_PANEL_MOVE_METHODDEF + _CURSES_PANEL_PANEL_REPLACE_METHODDEF + _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF + _CURSES_PANEL_PANEL_SHOW_METHODDEF + _CURSES_PANEL_PANEL_TOP_METHODDEF + _CURSES_PANEL_PANEL_USERPTR_METHODDEF + _CURSES_PANEL_PANEL_WINDOW_METHODDEF + {NULL, NULL} /* sentinel */ +}; + +/* -------------------------------------------------------*/ + +static PyType_Slot PyCursesPanel_Type_slots[] = { + {Py_tp_dealloc, PyCursesPanel_Dealloc}, + {Py_tp_methods, PyCursesPanel_Methods}, + {0, 0}, +}; + +static PyType_Spec PyCursesPanel_Type_spec = { + .name = "_curses_panel.panel", + .basicsize = sizeof(PyCursesPanelObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION, + .slots = PyCursesPanel_Type_slots +}; + +/* Wrapper for panel_above(NULL). This function returns the bottom + panel of the stack, so it's renamed to bottom_panel(). + panel.above() *requires* a panel object in the first place which + may be undesirable. */ +/*[clinic input] +_curses_panel.bottom_panel + +Return the bottom panel in the panel stack. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_bottom_panel_impl(PyObject *module) +/*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/ +{ + PANEL *pan; + PyCursesPanelObject *po; + + PyCursesInitialised; + + pan = panel_above(NULL); + + if (pan == NULL) { /* valid output, it means + there's no panel at all */ + Py_RETURN_NONE; + } + po = find_po(pan); + if (po == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "panel_above: can't find Panel Object"); + return NULL; + } + return Py_NewRef(po); +} + +/*[clinic input] +_curses_panel.new_panel + + win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type") + / + +Return a panel object, associating it with the given window win. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win) +/*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/ +{ + _curses_panel_state *state = get_curses_panel_state(module); + + PANEL *pan = new_panel(win->win); + if (pan == NULL) { + PyErr_SetString(state->PyCursesError, catchall_NULL); + return NULL; + } + return (PyObject *)PyCursesPanel_New(state, pan, win); +} + + +/* Wrapper for panel_below(NULL). This function returns the top panel + of the stack, so it's renamed to top_panel(). panel.below() + *requires* a panel object in the first place which may be + undesirable. */ +/*[clinic input] +_curses_panel.top_panel + +Return the top panel in the panel stack. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_top_panel_impl(PyObject *module) +/*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/ +{ + PANEL *pan; + PyCursesPanelObject *po; + + PyCursesInitialised; + + pan = panel_below(NULL); + + if (pan == NULL) { /* valid output, it means + there's no panel at all */ + Py_RETURN_NONE; + } + po = find_po(pan); + if (po == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "panel_below: can't find Panel Object"); + return NULL; + } + return Py_NewRef(po); +} + +/*[clinic input] +_curses_panel.update_panels + +Updates the virtual screen after changes in the panel stack. + +This does not call curses.doupdate(), so you'll have to do this yourself. +[clinic start generated code]*/ + +static PyObject * +_curses_panel_update_panels_impl(PyObject *module) +/*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/ +{ + PyCursesInitialised; + update_panels(); + Py_RETURN_NONE; +} + +/* List of functions defined in the module */ + +static PyMethodDef PyCurses_methods[] = { + _CURSES_PANEL_BOTTOM_PANEL_METHODDEF + _CURSES_PANEL_NEW_PANEL_METHODDEF + _CURSES_PANEL_TOP_PANEL_METHODDEF + _CURSES_PANEL_UPDATE_PANELS_METHODDEF + {NULL, NULL} /* sentinel */ +}; + +/* Initialization function for the module */ +static int +_curses_panel_exec(PyObject *mod) +{ + _curses_panel_state *state = get_curses_panel_state(mod); + /* Initialize object type */ + state->PyCursesPanel_Type = (PyTypeObject *)PyType_FromModuleAndSpec( + mod, &PyCursesPanel_Type_spec, NULL); + if (state->PyCursesPanel_Type == NULL) { + return -1; + } + + if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) { + return -1; + } + + import_curses(); + if (PyErr_Occurred()) { + return -1; + } + + /* For exception _curses_panel.error */ + state->PyCursesError = PyErr_NewException( + "_curses_form.error", NULL, NULL); + + if (PyModule_AddObjectRef(mod, "error", state->PyCursesError) < 0) { + return -1; + } + + /* Make the version available */ + PyObject *v = PyUnicode_FromString(PyCursesVersion); + if (v == NULL) { + return -1; + } + + PyObject *d = PyModule_GetDict(mod); + if (PyDict_SetItemString(d, "version", v) < 0) { + Py_DECREF(v); + return -1; + } + if (PyDict_SetItemString(d, "__version__", v) < 0) { + Py_DECREF(v); + return -1; + } + + Py_DECREF(v); + + return 0; +} + +static PyModuleDef_Slot _curses_slots[] = { + {Py_mod_exec, _curses_panel_exec}, + // XXX gh-103092: fix isolation. + {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED}, + //{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {0, NULL} +}; + +/* Module definition */ +static struct PyModuleDef _curses_formmodule = { + PyModuleDef_HEAD_INIT, + .m_name = "_curses_form", + .m_size = sizeof(_curses_form_state), + .m_methods = PyCurses_methods, + .m_slots = _curses_slots, + .m_traverse = _curses_form_traverse, + .m_clear = _curses_form_clear, + .m_free = _curses_form_free +}; + +/* Initiate module */ +PyMODINIT_FUNC +PyInit__curses_form(void) +{ + return PyModuleDef_Init(&_curses_formmodule); +} diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index b2b410cfe5f5cc..2bb9fb194d0311 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -40,8 +40,8 @@ Here's a list of currently unsupported functions: - del_curterm mcprint mvcur restartterm - ripoffline set_curterm setterm + del_curterm mvcur restartterm + set_curterm setterm tgetent tgetflag tgetnum tgetstr tgoto tputs vidattr vidputs @@ -2063,6 +2063,109 @@ PyCursesWindow_New(cursesmodule_state *state, return (PyObject *)wo; } +typedef struct { + cursesmodule_state *state; + PyObject *callbacks[5]; + PyObject *exception; + int count; + int index; +} curses_ripoffline_context; + +static curses_ripoffline_context ripoffline_context; + +static void +PyCurses_RipofflineClear(void) +{ + for (int i = 0; i < ripoffline_context.count; i++) { + Py_CLEAR(ripoffline_context.callbacks[i]); + } + Py_CLEAR(ripoffline_context.exception); + ripoffline_context.state = NULL; + ripoffline_context.count = 0; + ripoffline_context.index = 0; +} + +static void +PyCurses_RipofflineSaveException(void) +{ + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + Py_XDECREF(type); + Py_XDECREF(traceback); + if (ripoffline_context.exception == NULL) { + ripoffline_context.exception = value; + } + else { + Py_XDECREF(value); + } +} + +static int +PyCurses_RipofflineCallback(WINDOW *window, int columns) +{ + if (ripoffline_context.index >= ripoffline_context.count) { + return 0; + } + PyObject *callback = ripoffline_context.callbacks[ripoffline_context.index++]; + PyObject *window_object = PyCursesWindow_New( + ripoffline_context.state, window, NULL, NULL, NULL); + if (window_object == NULL) { + PyCurses_RipofflineSaveException(); + return 0; + } + PyObject *result = PyObject_CallFunction(callback, "Oi", window_object, + columns); + Py_DECREF(window_object); + if (result == NULL) { + PyCurses_RipofflineSaveException(); + return 0; + } + Py_DECREF(result); + return 0; +} + +static int +PyCurses_RipofflineFinish(void) +{ + if (ripoffline_context.exception != NULL) { + PyObject *exception = ripoffline_context.exception; + PyErr_SetObject((PyObject *)Py_TYPE(exception), exception); + PyCurses_RipofflineClear(); + return -1; + } + PyCurses_RipofflineClear(); + return 0; +} + +static PyObject * +PyCurses_Ripoffline(PyObject *module, PyObject *args) +{ + int line; + PyObject *callback; + if (!PyArg_ParseTuple(args, "iO:ripoffline", &line, &callback)) { + return NULL; + } + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable"); + return NULL; + } + if (ripoffline_context.count >= 5) { + cursesmodule_state *state = get_cursesmodule_state(module); + PyErr_SetString(state->error, + "ripoffline() called more than five times"); + return NULL; + } + if (ripoffline_context.count == 0) { + ripoffline_context.state = get_cursesmodule_state(module); + } + if (ripoffline(line, PyCurses_RipofflineCallback) == ERR) { + return curses_check_err(module, ERR, "ripoffline", NULL); + } + ripoffline_context.callbacks[ripoffline_context.count++] = + Py_NewRef(callback); + Py_RETURN_NONE; +} + static void PyCursesWindow_dealloc(PyObject *self) { @@ -7010,6 +7113,9 @@ _curses_initscr_impl(PyObject *module) curses_set_null_error(module, "initscr", NULL); return NULL; } + if (PyCurses_RipofflineFinish() < 0) { + return NULL; + } cursesmodule_state *state = get_cursesmodule_state(module); if (state->prescreen != NULL) { @@ -7192,6 +7298,12 @@ _curses_newterm_impl(PyObject *module, const char *type, PyObject *fd, fclose(infp); return NULL; } + if (PyCurses_RipofflineFinish() < 0) { + delscreen(screen); + fclose(outfp); + fclose(infp); + return NULL; + } /* newterm() makes the new screen the current one, so stdscr now refers to its standard window. */ curses_initscr_called = curses_setupterm_called = TRUE; @@ -7901,6 +8013,111 @@ _curses_putp_impl(PyObject *module, const char *string) return curses_check_err(module, putp(string), "putp", NULL); } +static PyObject * +PyCurses_Mcprint(PyObject *module, PyObject *args) +{ + const char *text; + Py_ssize_t length; + int count; + if (!PyArg_ParseTuple(args, "s#i:mcprint", &text, &length, &count)) { + return NULL; + } + PyCursesStatefulInitialised(module); + return curses_check_err(module, mcprint((char *)text, count), + "mcprint", NULL); +} + +#if defined(HAVE_TERM_H) && !defined(STRICT_SYSV_CURSES) +static PyObject * +PyCurses_Tgetent(PyObject *module, PyObject *args) +{ + char *term; + if (!PyArg_ParseTuple(args, "s:tgetent", &term)) { + return NULL; + } + PyCursesStatefulSetupTermCalled(module); + char buffer[2048]; + return PyLong_FromLong(tgetent(buffer, term)); +} + +static PyObject * +PyCurses_Tgetflag(PyObject *module, PyObject *args) +{ + char *id; + if (!PyArg_ParseTuple(args, "s:tgetflag", &id)) { + return NULL; + } + PyCursesStatefulSetupTermCalled(module); + return PyLong_FromLong(tgetflag(id)); +} + +static PyObject * +PyCurses_Tgetnum(PyObject *module, PyObject *args) +{ + char *id; + if (!PyArg_ParseTuple(args, "s:tgetnum", &id)) { + return NULL; + } + PyCursesStatefulSetupTermCalled(module); + return PyLong_FromLong(tgetnum(id)); +} + +static PyObject * +PyCurses_Tgetstr(PyObject *module, PyObject *args) +{ + char *id; + if (!PyArg_ParseTuple(args, "s:tgetstr", &id)) { + return NULL; + } + PyCursesStatefulSetupTermCalled(module); + char *area = NULL; + char *result = tgetstr(id, &area); + if (result == NULL || result == (char *)-1) { + Py_RETURN_NONE; + } + return PyBytes_FromString(result); +} + +static PyObject * +PyCurses_Tgoto(PyObject *module, PyObject *args) +{ + char *capability; + int col, row; + if (!PyArg_ParseTuple(args, "yii:tgoto", &capability, &col, &row)) { + return NULL; + } + PyCursesStatefulSetupTermCalled(module); + char *result = tgoto(capability, col, row); + if (result == NULL || result == (char *)-1) { + Py_RETURN_NONE; + } + return PyBytes_FromString(result); +} +#endif + +static PyObject * +PyCurses_Vidattr(PyObject *module, PyObject *args) +{ + unsigned long attrs; + if (!PyArg_ParseTuple(args, "k:vidattr", &attrs)) { + return NULL; + } + PyCursesStatefulInitialised(module); + return curses_check_err(module, vidattr((chtype)attrs), "vidattr", NULL); +} + +static PyObject * +PyCurses_Vidputs(PyObject *module, PyObject *args) +{ + unsigned long attrs; + if (!PyArg_ParseTuple(args, "k:vidputs", &attrs)) { + return NULL; + } + PyCursesStatefulInitialised(module); + return curses_check_err(module, vidputs((chtype)attrs, putchar), + "vidputs", NULL); +} + /*[clinic input] _curses.qiflush @@ -9171,6 +9388,7 @@ static PyMethodDef cursesmodule_methods[] = { _CURSES_KILLCHAR_METHODDEF _CURSES_KILLWCHAR_METHODDEF _CURSES_LONGNAME_METHODDEF + {"mcprint", (PyCFunction)PyCurses_Mcprint, METH_VARARGS}, _CURSES_META_METHODDEF _CURSES_MOUSEINTERVAL_METHODDEF _CURSES_MOUSEMASK_METHODDEF @@ -9190,6 +9408,15 @@ static PyMethodDef cursesmodule_methods[] = { _CURSES_PAIR_CONTENT_METHODDEF _CURSES_PAIR_NUMBER_METHODDEF _CURSES_PUTP_METHODDEF + {"vidattr", (PyCFunction)PyCurses_Vidattr, METH_VARARGS}, + {"vidputs", (PyCFunction)PyCurses_Vidputs, METH_VARARGS}, +#if defined(HAVE_TERM_H) && !defined(STRICT_SYSV_CURSES) + {"tgetent", (PyCFunction)PyCurses_Tgetent, METH_VARARGS}, + {"tgetflag", (PyCFunction)PyCurses_Tgetflag, METH_VARARGS}, + {"tgetnum", (PyCFunction)PyCurses_Tgetnum, METH_VARARGS}, + {"tgetstr", (PyCFunction)PyCurses_Tgetstr, METH_VARARGS}, + {"tgoto", (PyCFunction)PyCurses_Tgoto, METH_VARARGS}, +#endif _CURSES_QIFLUSH_METHODDEF _CURSES_RAW_METHODDEF _CURSES_RESET_COLOR_PAIRS_METHODDEF @@ -9198,6 +9425,7 @@ static PyMethodDef cursesmodule_methods[] = { _CURSES_RESETTY_METHODDEF _CURSES_RESIZETERM_METHODDEF _CURSES_RESIZE_TERM_METHODDEF + {"ripoffline", (PyCFunction)PyCurses_Ripoffline, METH_VARARGS}, _CURSES_SAVETTY_METHODDEF _CURSES_SCR_DUMP_METHODDEF _CURSES_SCR_INIT_METHODDEF diff --git a/Modules/clinic/_curses_form.c.h b/Modules/clinic/_curses_form.c.h new file mode 100644 index 00000000000000..3baaecf3f836e6 --- /dev/null +++ b/Modules/clinic/_curses_form.c.h @@ -0,0 +1,385 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#include "pycore_modsupport.h" // _PyArg_CheckPositional() + +PyDoc_STRVAR(_curses_panel_panel_bottom__doc__, +"bottom($self, /)\n" +"--\n" +"\n" +"Push the panel to the bottom of the stack."); + +#define _CURSES_PANEL_PANEL_BOTTOM_METHODDEF \ + {"bottom", _PyCFunction_CAST(_curses_panel_panel_bottom), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_bottom__doc__}, + +static PyObject * +_curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls); + +static PyObject * +_curses_panel_panel_bottom(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "bottom() takes no arguments"); + return NULL; + } + return _curses_panel_panel_bottom_impl((PyCursesPanelObject *)self, cls); +} + +PyDoc_STRVAR(_curses_panel_panel_hide__doc__, +"hide($self, /)\n" +"--\n" +"\n" +"Hide the panel.\n" +"\n" +"This does not delete the object, it just makes the window on screen invisible."); + +#define _CURSES_PANEL_PANEL_HIDE_METHODDEF \ + {"hide", _PyCFunction_CAST(_curses_panel_panel_hide), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_hide__doc__}, + +static PyObject * +_curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls); + +static PyObject * +_curses_panel_panel_hide(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "hide() takes no arguments"); + return NULL; + } + return _curses_panel_panel_hide_impl((PyCursesPanelObject *)self, cls); +} + +PyDoc_STRVAR(_curses_panel_panel_show__doc__, +"show($self, /)\n" +"--\n" +"\n" +"Display the panel (which might have been hidden)."); + +#define _CURSES_PANEL_PANEL_SHOW_METHODDEF \ + {"show", _PyCFunction_CAST(_curses_panel_panel_show), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_show__doc__}, + +static PyObject * +_curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls); + +static PyObject * +_curses_panel_panel_show(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "show() takes no arguments"); + return NULL; + } + return _curses_panel_panel_show_impl((PyCursesPanelObject *)self, cls); +} + +PyDoc_STRVAR(_curses_panel_panel_top__doc__, +"top($self, /)\n" +"--\n" +"\n" +"Push panel to the top of the stack."); + +#define _CURSES_PANEL_PANEL_TOP_METHODDEF \ + {"top", _PyCFunction_CAST(_curses_panel_panel_top), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_top__doc__}, + +static PyObject * +_curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls); + +static PyObject * +_curses_panel_panel_top(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "top() takes no arguments"); + return NULL; + } + return _curses_panel_panel_top_impl((PyCursesPanelObject *)self, cls); +} + +PyDoc_STRVAR(_curses_panel_panel_above__doc__, +"above($self, /)\n" +"--\n" +"\n" +"Return the panel above the current panel."); + +#define _CURSES_PANEL_PANEL_ABOVE_METHODDEF \ + {"above", (PyCFunction)_curses_panel_panel_above, METH_NOARGS, _curses_panel_panel_above__doc__}, + +static PyObject * +_curses_panel_panel_above_impl(PyCursesPanelObject *self); + +static PyObject * +_curses_panel_panel_above(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_panel_above_impl((PyCursesPanelObject *)self); +} + +PyDoc_STRVAR(_curses_panel_panel_below__doc__, +"below($self, /)\n" +"--\n" +"\n" +"Return the panel below the current panel."); + +#define _CURSES_PANEL_PANEL_BELOW_METHODDEF \ + {"below", (PyCFunction)_curses_panel_panel_below, METH_NOARGS, _curses_panel_panel_below__doc__}, + +static PyObject * +_curses_panel_panel_below_impl(PyCursesPanelObject *self); + +static PyObject * +_curses_panel_panel_below(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_panel_below_impl((PyCursesPanelObject *)self); +} + +PyDoc_STRVAR(_curses_panel_panel_hidden__doc__, +"hidden($self, /)\n" +"--\n" +"\n" +"Return True if the panel is hidden (not visible), False otherwise."); + +#define _CURSES_PANEL_PANEL_HIDDEN_METHODDEF \ + {"hidden", (PyCFunction)_curses_panel_panel_hidden, METH_NOARGS, _curses_panel_panel_hidden__doc__}, + +static PyObject * +_curses_panel_panel_hidden_impl(PyCursesPanelObject *self); + +static PyObject * +_curses_panel_panel_hidden(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_panel_hidden_impl((PyCursesPanelObject *)self); +} + +PyDoc_STRVAR(_curses_panel_panel_move__doc__, +"move($self, y, x, /)\n" +"--\n" +"\n" +"Move the panel to the screen coordinates (y, x)."); + +#define _CURSES_PANEL_PANEL_MOVE_METHODDEF \ + {"move", _PyCFunction_CAST(_curses_panel_panel_move), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_move__doc__}, + +static PyObject * +_curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls, + int y, int x); + +static PyObject * +_curses_panel_panel_move(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + int y; + int x; + + if (!_PyArg_NoKwnames("move", kwnames)) { + goto exit; + } + if (!_PyArg_CheckPositional("move", nargs, 2, 2)) { + goto exit; + } + y = PyLong_AsInt(args[0]); + if (y == -1 && PyErr_Occurred()) { + goto exit; + } + x = PyLong_AsInt(args[1]); + if (x == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = _curses_panel_panel_move_impl((PyCursesPanelObject *)self, cls, y, x); + +exit: + return return_value; +} + +PyDoc_STRVAR(_curses_panel_panel_window__doc__, +"window($self, /)\n" +"--\n" +"\n" +"Return the window object associated with the panel."); + +#define _CURSES_PANEL_PANEL_WINDOW_METHODDEF \ + {"window", (PyCFunction)_curses_panel_panel_window, METH_NOARGS, _curses_panel_panel_window__doc__}, + +static PyObject * +_curses_panel_panel_window_impl(PyCursesPanelObject *self); + +static PyObject * +_curses_panel_panel_window(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_panel_window_impl((PyCursesPanelObject *)self); +} + +PyDoc_STRVAR(_curses_panel_panel_replace__doc__, +"replace($self, win, /)\n" +"--\n" +"\n" +"Change the window associated with the panel to the window win."); + +#define _CURSES_PANEL_PANEL_REPLACE_METHODDEF \ + {"replace", _PyCFunction_CAST(_curses_panel_panel_replace), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_replace__doc__}, + +static PyObject * +_curses_panel_panel_replace_impl(PyCursesPanelObject *self, + PyTypeObject *cls, + PyCursesWindowObject *win); + +static PyObject * +_curses_panel_panel_replace(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + PyCursesWindowObject *win; + + if (!_PyArg_NoKwnames("replace", kwnames)) { + goto exit; + } + if (!_PyArg_CheckPositional("replace", nargs, 1, 1)) { + goto exit; + } + if (!PyObject_TypeCheck(args[0], &PyCursesWindow_Type)) { + _PyArg_BadArgument("replace", "argument 1", (&PyCursesWindow_Type)->tp_name, args[0]); + goto exit; + } + win = (PyCursesWindowObject *)args[0]; + return_value = _curses_panel_panel_replace_impl((PyCursesPanelObject *)self, cls, win); + +exit: + return return_value; +} + +PyDoc_STRVAR(_curses_panel_panel_set_userptr__doc__, +"set_userptr($self, obj, /)\n" +"--\n" +"\n" +"Set the panel\'s user pointer to obj."); + +#define _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF \ + {"set_userptr", _PyCFunction_CAST(_curses_panel_panel_set_userptr), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_set_userptr__doc__}, + +static PyObject * +_curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self, + PyTypeObject *cls, PyObject *obj); + +static PyObject * +_curses_panel_panel_set_userptr(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + PyObject *obj; + + if (!_PyArg_NoKwnames("set_userptr", kwnames)) { + goto exit; + } + if (!_PyArg_CheckPositional("set_userptr", nargs, 1, 1)) { + goto exit; + } + obj = args[0]; + return_value = _curses_panel_panel_set_userptr_impl((PyCursesPanelObject *)self, cls, obj); + +exit: + return return_value; +} + +PyDoc_STRVAR(_curses_panel_panel_userptr__doc__, +"userptr($self, /)\n" +"--\n" +"\n" +"Return the user pointer for the panel."); + +#define _CURSES_PANEL_PANEL_USERPTR_METHODDEF \ + {"userptr", _PyCFunction_CAST(_curses_panel_panel_userptr), METH_METHOD|METH_FASTCALL|METH_KEYWORDS, _curses_panel_panel_userptr__doc__}, + +static PyObject * +_curses_panel_panel_userptr_impl(PyCursesPanelObject *self, + PyTypeObject *cls); + +static PyObject * +_curses_panel_panel_userptr(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + if (nargs || (kwnames && PyTuple_GET_SIZE(kwnames))) { + PyErr_SetString(PyExc_TypeError, "userptr() takes no arguments"); + return NULL; + } + return _curses_panel_panel_userptr_impl((PyCursesPanelObject *)self, cls); +} + +PyDoc_STRVAR(_curses_panel_bottom_panel__doc__, +"bottom_panel($module, /)\n" +"--\n" +"\n" +"Return the bottom panel in the panel stack."); + +#define _CURSES_PANEL_BOTTOM_PANEL_METHODDEF \ + {"bottom_panel", (PyCFunction)_curses_panel_bottom_panel, METH_NOARGS, _curses_panel_bottom_panel__doc__}, + +static PyObject * +_curses_panel_bottom_panel_impl(PyObject *module); + +static PyObject * +_curses_panel_bottom_panel(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_bottom_panel_impl(module); +} + +PyDoc_STRVAR(_curses_panel_new_panel__doc__, +"new_panel($module, win, /)\n" +"--\n" +"\n" +"Return a panel object, associating it with the given window win."); + +#define _CURSES_PANEL_NEW_PANEL_METHODDEF \ + {"new_panel", (PyCFunction)_curses_panel_new_panel, METH_O, _curses_panel_new_panel__doc__}, + +static PyObject * +_curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win); + +static PyObject * +_curses_panel_new_panel(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + PyCursesWindowObject *win; + + if (!PyObject_TypeCheck(arg, &PyCursesWindow_Type)) { + _PyArg_BadArgument("new_panel", "argument", (&PyCursesWindow_Type)->tp_name, arg); + goto exit; + } + win = (PyCursesWindowObject *)arg; + return_value = _curses_panel_new_panel_impl(module, win); + +exit: + return return_value; +} + +PyDoc_STRVAR(_curses_panel_top_panel__doc__, +"top_panel($module, /)\n" +"--\n" +"\n" +"Return the top panel in the panel stack."); + +#define _CURSES_PANEL_TOP_PANEL_METHODDEF \ + {"top_panel", (PyCFunction)_curses_panel_top_panel, METH_NOARGS, _curses_panel_top_panel__doc__}, + +static PyObject * +_curses_panel_top_panel_impl(PyObject *module); + +static PyObject * +_curses_panel_top_panel(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_top_panel_impl(module); +} + +PyDoc_STRVAR(_curses_panel_update_panels__doc__, +"update_panels($module, /)\n" +"--\n" +"\n" +"Updates the virtual screen after changes in the panel stack.\n" +"\n" +"This does not call curses.doupdate(), so you\'ll have to do this yourself."); + +#define _CURSES_PANEL_UPDATE_PANELS_METHODDEF \ + {"update_panels", (PyCFunction)_curses_panel_update_panels, METH_NOARGS, _curses_panel_update_panels__doc__}, + +static PyObject * +_curses_panel_update_panels_impl(PyObject *module); + +static PyObject * +_curses_panel_update_panels(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _curses_panel_update_panels_impl(module); +} +/*[clinic end generated code: output=a331de3267fc5252 input=a9049054013a1b77]*/