From 0fdfceb782424dcddca848357736f24ef40c91be Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 25 Nov 2011 22:10:02 +0100 Subject: [PATCH] Issue #12567: The curses module uses Unicode functions for Unicode arguments when it is linked to the ncurses library. It encodes also Unicode strings to the locale encoding instead of UTF-8. --- Doc/library/curses.rst | 12 +- Doc/whatsnew/3.3.rst | 5 + Include/py_curses.h | 1 + Lib/test/test_curses.py | 41 ++- Misc/NEWS | 4 + Modules/_cursesmodule.c | 580 ++++++++++++++++++++++++++++++++-------- 6 files changed, 521 insertions(+), 122 deletions(-) diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index d6a35c3d169..ff3a7938d69 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -653,7 +653,7 @@ Window Objects -------------- Window objects, as returned by :func:`initscr` and :func:`newwin` above, have -the following methods: +the following methods and attributes: .. method:: window.addch([y, x,] ch[, attr]) @@ -834,6 +834,16 @@ the following methods: event. +.. attribute:: window.encoding + + Encoding used to encode method arguments (Unicode strings and characters). + The encoding attribute is inherited from by parent window when a subwindow + is created, for example with :meth:`window.subwin`. By default, the locale + encoding is used (see :func:`locale.getpreferredencoding`). + + .. versionadded:: 3.3 + + .. method:: window.erase() Clear the window. diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 0a1d0a3750a..eb41cca96dc 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -333,6 +333,11 @@ function to the :mod:`crypt` module. curses ------ + * If the :mod:`curses` module is linked to the ncursesw library, use Unicode + functions when Unicode strings or characters are passed (e.g. + :c:func:`waddwstr`), and bytes functions otherwise (e.g. :c:func:`waddstr`). + * Use the locale encoding instead of ``utf-8`` to encode Unicode strings. + * :class:`curses.window` has a new :attr:`curses.window.encoding` attribute. * The :class:`curses.window` class has a new :meth:`~curses.window.get_wch` method to get a wide character * The :mod:`curses` module has a new :meth:`~curses.unget_wch` function to diff --git a/Include/py_curses.h b/Include/py_curses.h index a891c42be65..f2c08f6413a 100644 --- a/Include/py_curses.h +++ b/Include/py_curses.h @@ -76,6 +76,7 @@ extern "C" { typedef struct { PyObject_HEAD WINDOW *win; + char *encoding; } PyCursesWindowObject; #define PyCursesWindow_Check(v) (Py_TYPE(v) == &PyCursesWindow_Type) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 72be3e7b512..b416403e900 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -267,24 +267,42 @@ def test_issue6243(stdscr): def test_unget_wch(stdscr): if not hasattr(curses, 'unget_wch'): return - ch = 'a' - curses.unget_wch(ch) - read = stdscr.get_wch() - read = chr(read) - if read != ch: - raise AssertionError("%r != %r" % (read, ch)) + for ch in ('a', '\xe9', '\u20ac', '\U0010FFFF'): + curses.unget_wch(ch) + read = stdscr.get_wch() + read = chr(read) + if read != ch: + raise AssertionError("%r != %r" % (read, ch)) - ch = ord('a') - curses.unget_wch(ch) - read = stdscr.get_wch() - if read != ch: - raise AssertionError("%r != %r" % (read, ch)) + code = ord(ch) + curses.unget_wch(code) + read = stdscr.get_wch() + if read != code: + raise AssertionError("%r != %r" % (read, code)) def test_issue10570(): b = curses.tparm(curses.tigetstr("cup"), 5, 3) assert type(b) is bytes curses.putp(b) +def test_encoding(stdscr): + import codecs + encoding = stdscr.encoding + codecs.lookup(encoding) + try: + stdscr.encoding = 10 + except TypeError: + pass + else: + raise AssertionError("TypeError not raised") + stdscr.encoding = encoding + try: + del stdscr.encoding + except TypeError: + pass + else: + raise AssertionError("TypeError not raised") + def main(stdscr): curses.savetty() try: @@ -295,6 +313,7 @@ def main(stdscr): test_issue6243(stdscr) test_unget_wch(stdscr) test_issue10570() + test_encoding(stdscr) finally: curses.resetty() diff --git a/Misc/NEWS b/Misc/NEWS index 11f3de2ae86..1b8d0cb7195 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -395,6 +395,10 @@ Core and Builtins Library ------- +- Issue #12567: The curses module uses Unicode functions for Unicode arguments + when it is linked to the ncurses library. It encodes also Unicode strings to + the locale encoding instead of UTF-8. + - Issue #12856: Ensure child processes do not inherit the parent's random seed for filename generation in the tempfile module. Patch by Brian Harring. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index cfa5b7a571c..166a93a6ecd 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -121,6 +121,10 @@ extern int setupterm(char *,int,int *); #include #endif +#ifdef HAVE_LANGINFO_H +#include +#endif + #if !defined(HAVE_NCURSES_H) && (defined(sgi) || defined(__sun) || defined(SCO5)) #define STRICT_SYSV_CURSES /* Don't use ncurses extensions */ typedef chtype attr_t; /* No attr_t type is available */ @@ -143,6 +147,8 @@ static int initialised = FALSE; /* Tells whether start_color() has been called to initialise color usage. */ static int initialisedcolors = FALSE; +static char *screen_encoding = NULL; + /* Utility Macros */ #define PyCursesSetupTermCalled \ if (initialised_setupterm != TRUE) { \ @@ -175,7 +181,7 @@ static int initialisedcolors = FALSE; */ static PyObject * -PyCursesCheckERR(int code, char *fname) +PyCursesCheckERR(int code, const char *fname) { if (code != ERR) { Py_INCREF(Py_None); @@ -190,28 +196,193 @@ PyCursesCheckERR(int code, char *fname) } } +/* Convert an object to a byte (an integer of type chtype): + + - int + - bytes of length 1 + - str of length 1 + + Return 1 on success, 0 on error (invalid type or integer overflow). */ static int -PyCurses_ConvertToChtype(PyObject *obj, chtype *ch) +PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch) { - if (PyLong_CheckExact(obj)) { - int overflow; - /* XXX should the truncation by the cast also be reported - as an error? */ - *ch = (chtype) PyLong_AsLongAndOverflow(obj, &overflow); - if (overflow) + long value; + if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) { + value = (unsigned char)PyBytes_AsString(obj)[0]; + } + else if (PyUnicode_Check(obj)) { + if (PyUnicode_GetLength(obj) != 1) { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, " + "got a str of length %zi", + PyUnicode_GET_LENGTH(obj)); return 0; - } else if(PyBytes_Check(obj) - && (PyBytes_Size(obj) == 1)) { - *ch = (chtype) *PyBytes_AsString(obj); - } else if (PyUnicode_Check(obj) && PyUnicode_GET_LENGTH(obj) == 1) { - Py_UCS4 ucs = PyUnicode_READ(PyUnicode_KIND(obj), - PyUnicode_DATA(obj), - 0); - *ch = (chtype)ucs; - } else { + } + value = PyUnicode_READ_CHAR(obj, 0); + if (128 < value) { + PyObject *bytes; + const char *encoding; + if (win) + encoding = win->encoding; + else + encoding = screen_encoding; + bytes = PyUnicode_AsEncodedObject(obj, encoding, NULL); + if (bytes == NULL) + return 0; + if (PyBytes_GET_SIZE(bytes) == 1) + value = (unsigned char)PyBytes_AS_STRING(bytes)[0]; + else + value = -1; + Py_DECREF(bytes); + if (value < 0) + goto overflow; + } + } + else if (PyLong_CheckExact(obj)) { + int long_overflow; + value = PyLong_AsLongAndOverflow(obj, &long_overflow); + if (long_overflow) + goto overflow; + } + else { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, got %s", + Py_TYPE(obj)->tp_name); return 0; } + *ch = (chtype)value; + if ((long)*ch != value) + goto overflow; return 1; + +overflow: + PyErr_SetString(PyExc_OverflowError, + "byte doesn't fit in chtype"); + return 0; +} + +/* Convert an object to a byte (chtype) or a character (cchar_t): + + - int + - bytes of length 1 + - str of length 1 + + Return: + + - 2 if obj is a character (written into *wch) + - 1 if obj is a byte (written into *ch) + - 0 on error: raise an exception */ +static int +PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj, + chtype *ch +#ifdef HAVE_NCURSESW + , cchar_t *wch +#endif + ) +{ + int ret = 0; + long value; +#ifdef HAVE_NCURSESW + wchar_t buffer[2]; +#endif + + if (PyUnicode_Check(obj)) { +#ifdef HAVE_NCURSESW + if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, " + "got a str of length %zi", + PyUnicode_GET_LENGTH(obj)); + return 0; + } + memset(wch->chars, 0, sizeof(wch->chars)); + wch->chars[0] = buffer[0]; + return 2; +#else + return PyCurses_ConvertToChtype(win, obj, ch); +#endif + } + else if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) { + value = (unsigned char)PyBytes_AsString(obj)[0]; + ret = 1; + } + else if (PyLong_CheckExact(obj)) { + int overflow; + value = PyLong_AsLongAndOverflow(obj, &overflow); + if (overflow) { + PyErr_SetString(PyExc_OverflowError, + "int doesn't fit in long"); + return 0; + } +#ifdef HAVE_NCURSESW + ret = 2; +#else + ret = 1; +#endif + } + else { + PyErr_Format(PyExc_TypeError, + "expect bytes or str of length 1, or int, got %s", + Py_TYPE(obj)->tp_name); + return 0; + } +#ifdef HAVE_NCURSESW + if (ret == 2) { + memset(wch->chars, 0, sizeof(wch->chars)); + wch->chars[0] = (wchar_t)value; + if ((long)wch->chars[0] != value) { + PyErr_Format(PyExc_OverflowError, + "character doesn't fit in wchar_t"); + return 0; + } + } + else +#endif + { + *ch = (chtype)value; + if ((long)*ch != value || value < 0 || value > 255) { + PyErr_Format(PyExc_OverflowError, + "byte doesn't fit in chtype"); + return 0; + } + } + return ret; +} + +/* Convert an object to a byte string (char*) or a wide character string + (wchar_t*). Return: + + - 2 if obj is a character string (written into *wch) + - 1 if obj is a byte string (written into *bytes) + - 0 on error: raise an exception */ +static int +PyCurses_ConvertToString(PyCursesWindowObject *win, PyObject *obj, + PyObject **bytes, wchar_t **wstr) +{ + if (PyUnicode_Check(obj)) { +#ifdef HAVE_NCURSESW + assert (wstr != NULL); + *wstr = PyUnicode_AsWideCharString(obj, NULL); + if (*wstr == NULL) + return 0; + return 2; +#else + assert (wstr == NULL); + *bytes = PyUnicode_AsEncodedObject(obj, win->encoding, NULL); + if (*bytes == NULL) + return 0; + return 1; +#endif + } + else if (PyBytes_Check(obj)) { + Py_INCREF(obj); + *bytes = obj; + return 1; + } + + PyErr_Format(PyExc_TypeError, "expect bytes or str, got %s", + Py_TYPE(obj)->tp_name); + return 0; } /* Function versions of the 3 functions for testing whether curses has been @@ -357,13 +528,37 @@ Window_TwoArgNoReturnFunction(wresize, int, "ii;lines,columns") /* Allocation and deallocation of Window Objects */ static PyObject * -PyCursesWindow_New(WINDOW *win) +PyCursesWindow_New(WINDOW *win, const char *encoding) { PyCursesWindowObject *wo; + if (encoding == NULL) { +#if defined(MS_WINDOWS) + char *buffer[100]; + UINT cp; + cp = GetConsoleOutputCP(); + if (cp != 0) { + PyOS_snprintf(buffer, sizeof(buffer), "cp%u", cp); + encoding = buffer; + } +#elif defined(CODESET) + const char *codeset = nl_langinfo(CODESET); + if (codeset != NULL && codeset[0] != 0) + encoding = codeset; +#endif + if (encoding == NULL) + encoding = "utf-8"; + } + wo = PyObject_NEW(PyCursesWindowObject, &PyCursesWindow_Type); if (wo == NULL) return NULL; wo->win = win; + wo->encoding = strdup(encoding); + if (wo->encoding == NULL) { + Py_DECREF(wo); + PyErr_NoMemory(); + return NULL; + } return (PyObject *)wo; } @@ -371,6 +566,8 @@ static void PyCursesWindow_Dealloc(PyCursesWindowObject *wo) { if (wo->win != stdscr) delwin(wo->win); + if (wo->encoding != NULL) + free(wo->encoding); PyObject_DEL(wo); } @@ -380,29 +577,34 @@ static PyObject * PyCursesWindow_AddCh(PyCursesWindowObject *self, PyObject *args) { int rtn, x, y, use_xy = FALSE; - PyObject *temp; - chtype ch = 0; + PyObject *chobj; + int type; + chtype ch; +#ifdef HAVE_NCURSESW + cchar_t wch; +#endif attr_t attr = A_NORMAL; long lattr; + const char *funcname; switch (PyTuple_Size(args)) { case 1: - if (!PyArg_ParseTuple(args, "O;ch or int", &temp)) + if (!PyArg_ParseTuple(args, "O;ch or int", &chobj)) return NULL; break; case 2: - if (!PyArg_ParseTuple(args, "Ol;ch or int,attr", &temp, &lattr)) + if (!PyArg_ParseTuple(args, "Ol;ch or int,attr", &chobj, &lattr)) return NULL; attr = lattr; break; case 3: - if (!PyArg_ParseTuple(args,"iiO;y,x,ch or int", &y, &x, &temp)) + if (!PyArg_ParseTuple(args,"iiO;y,x,ch or int", &y, &x, &chobj)) return NULL; use_xy = TRUE; break; case 4: if (!PyArg_ParseTuple(args,"iiOl;y,x,ch or int, attr", - &y, &x, &temp, &lattr)) + &y, &x, &chobj, &lattr)) return NULL; attr = lattr; use_xy = TRUE; @@ -412,17 +614,33 @@ PyCursesWindow_AddCh(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int"); +#ifdef HAVE_NCURSESW + type = PyCurses_ConvertToCchar_t(self, chobj, &ch, &wch); + if (type == 2) { + funcname = "add_wch"; + wch.attr = attr; + if (use_xy == TRUE) + rtn = mvwadd_wch(self->win,y,x, &wch); + else { + rtn = wadd_wch(self->win, &wch); + } + } + else +#else + type = PyCurses_ConvertToCchar_t(self, chobj, &ch); +#endif + if (type == 1) { + funcname = "addch"; + if (use_xy == TRUE) + rtn = mvwaddch(self->win,y,x, ch | attr); + else { + rtn = waddch(self->win, ch | attr); + } + } + else { return NULL; } - - if (use_xy == TRUE) - rtn = mvwaddch(self->win,y,x, ch | attr); - else { - rtn = waddch(self->win, ch | attr); - } - return PyCursesCheckERR(rtn, "addch"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * @@ -430,29 +648,34 @@ PyCursesWindow_AddStr(PyCursesWindowObject *self, PyObject *args) { int rtn; int x, y; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 1: - if (!PyArg_ParseTuple(args,"s;str", &str)) + if (!PyArg_ParseTuple(args,"O;str", &strobj)) return NULL; break; case 2: - if (!PyArg_ParseTuple(args,"sl;str,attr", &str, &lattr)) + if (!PyArg_ParseTuple(args,"Ol;str,attr", &strobj, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 3: - if (!PyArg_ParseTuple(args,"iis;int,int,str", &y, &x, &str)) + if (!PyArg_ParseTuple(args,"iiO;int,int,str", &y, &x, &strobj)) return NULL; use_xy = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisl;int,int,str,attr", &y, &x, &str, &lattr)) + if (!PyArg_ParseTuple(args,"iiOl;int,int,str,attr", &y, &x, &strobj, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -461,47 +684,74 @@ PyCursesWindow_AddStr(PyCursesWindowObject *self, PyObject *args) PyErr_SetString(PyExc_TypeError, "addstr requires 1 to 4 arguments"); return NULL; } - +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwaddstr(self->win,y,x,str); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "addwstr"; + if (use_xy == TRUE) + rtn = mvwaddwstr(self->win,y,x,wstr); + else + rtn = waddwstr(self->win,wstr); + PyMem_Free(wstr); + } else - rtn = waddstr(self->win,str); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "addstr"; + if (use_xy == TRUE) + rtn = mvwaddstr(self->win,y,x,str); + else + rtn = waddstr(self->win,str); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "addstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * PyCursesWindow_AddNStr(PyCursesWindowObject *self, PyObject *args) { int rtn, x, y, n; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 2: - if (!PyArg_ParseTuple(args,"si;str,n", &str, &n)) + if (!PyArg_ParseTuple(args,"Oi;str,n", &strobj, &n)) return NULL; break; case 3: - if (!PyArg_ParseTuple(args,"sil;str,n,attr", &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"Oil;str,n,attr", &strobj, &n, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisi;y,x,str,n", &y, &x, &str, &n)) + if (!PyArg_ParseTuple(args,"iiOi;y,x,str,n", &y, &x, &strobj, &n)) return NULL; use_xy = TRUE; break; case 5: - if (!PyArg_ParseTuple(args,"iisil;y,x,str,n,attr", &y, &x, &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"iiOil;y,x,str,n,attr", &y, &x, &strobj, &n, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -510,18 +760,41 @@ PyCursesWindow_AddNStr(PyCursesWindowObject *self, PyObject *args) PyErr_SetString(PyExc_TypeError, "addnstr requires 2 to 5 arguments"); return NULL; } +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwaddnstr(self->win,y,x,str,n); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "addnwstr"; + if (use_xy == TRUE) + rtn = mvwaddnwstr(self->win,y,x,wstr,n); + else + rtn = waddnwstr(self->win,wstr,n); + PyMem_Free(wstr); + } else - rtn = waddnstr(self->win,str,n); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "addnstr"; + if (use_xy == TRUE) + rtn = mvwaddnstr(self->win,y,x,str,n); + else + rtn = waddnstr(self->win,str,n); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "addnstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * @@ -547,10 +820,8 @@ PyCursesWindow_Bkgd(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &bkgd)) { - PyErr_SetString(PyExc_TypeError, "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &bkgd)) return NULL; - } return PyCursesCheckERR(wbkgd(self->win, bkgd | attr), "bkgd"); } @@ -605,10 +876,8 @@ PyCursesWindow_BkgdSet(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &bkgd)) { - PyErr_SetString(PyExc_TypeError, "argument 1 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &bkgd)) return NULL; - } wbkgdset(self->win, bkgd | attr); return PyCursesCheckERR(0, "bkgdset"); @@ -633,11 +902,8 @@ PyCursesWindow_Border(PyCursesWindowObject *self, PyObject *args) return NULL; for(i=0; i<8; i++) { - if (temp[i] != NULL && !PyCurses_ConvertToChtype(temp[i], &ch[i])) { - PyErr_Format(PyExc_TypeError, - "argument %i must be a ch or an int", i+1); + if (temp[i] != NULL && !PyCurses_ConvertToChtype(self, temp[i], &ch[i])) return NULL; - } } wborder(self->win, @@ -782,7 +1048,7 @@ PyCursesWindow_DerWin(PyCursesWindowObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, NULL); } static PyObject * @@ -810,10 +1076,8 @@ PyCursesWindow_EchoChar(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument 1 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } #ifdef WINDOW_HAS_FLAGS if (self->win->_flags & _ISPAD) @@ -1034,11 +1298,8 @@ PyCursesWindow_Hline(PyCursesWindowObject *self, PyObject *args) } if (code != ERR) { - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, - "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } return PyCursesCheckERR(whline(self->win, ch | attr, n), "hline"); } else return PyCursesCheckERR(code, "wmove"); @@ -1079,11 +1340,8 @@ PyCursesWindow_InsCh(PyCursesWindowObject *self, PyObject *args) return NULL; } - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, - "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } if (use_xy == TRUE) rtn = mvwinsch(self->win,y,x, ch | attr); @@ -1154,29 +1412,34 @@ PyCursesWindow_InsStr(PyCursesWindowObject *self, PyObject *args) { int rtn; int x, y; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 1: - if (!PyArg_ParseTuple(args,"s;str", &str)) + if (!PyArg_ParseTuple(args,"O;str", &strobj)) return NULL; break; case 2: - if (!PyArg_ParseTuple(args,"sl;str,attr", &str, &lattr)) + if (!PyArg_ParseTuple(args,"Ol;str,attr", &strobj, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 3: - if (!PyArg_ParseTuple(args,"iis;y,x,str", &y, &x, &str)) + if (!PyArg_ParseTuple(args,"iiO;y,x,str", &y, &x, &strobj)) return NULL; use_xy = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisl;y,x,str,attr", &y, &x, &str, &lattr)) + if (!PyArg_ParseTuple(args,"iiOl;y,x,str,attr", &y, &x, &strobj, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -1186,46 +1449,75 @@ PyCursesWindow_InsStr(PyCursesWindowObject *self, PyObject *args) return NULL; } +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; + if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwinsstr(self->win,y,x,str); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "inswstr"; + if (use_xy == TRUE) + rtn = mvwins_wstr(self->win,y,x,wstr); + else + rtn = wins_wstr(self->win,wstr); + PyMem_Free(wstr); + } else - rtn = winsstr(self->win,str); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "insstr"; + if (use_xy == TRUE) + rtn = mvwinsstr(self->win,y,x,str); + else + rtn = winsstr(self->win,str); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "insstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * PyCursesWindow_InsNStr(PyCursesWindowObject *self, PyObject *args) { int rtn, x, y, n; - char *str; + int strtype; + PyObject *strobj, *bytesobj; +#ifdef HAVE_NCURSESW + wchar_t *wstr = NULL; +#endif attr_t attr = A_NORMAL , attr_old = A_NORMAL; long lattr; int use_xy = FALSE, use_attr = FALSE; + const char *funcname; switch (PyTuple_Size(args)) { case 2: - if (!PyArg_ParseTuple(args,"si;str,n", &str, &n)) + if (!PyArg_ParseTuple(args,"Oi;str,n", &strobj, &n)) return NULL; break; case 3: - if (!PyArg_ParseTuple(args,"sil;str,n,attr", &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"Oil;str,n,attr", &strobj, &n, &lattr)) return NULL; attr = lattr; use_attr = TRUE; break; case 4: - if (!PyArg_ParseTuple(args,"iisi;y,x,str,n", &y, &x, &str, &n)) + if (!PyArg_ParseTuple(args,"iiOi;y,x,str,n", &y, &x, &strobj, &n)) return NULL; use_xy = TRUE; break; case 5: - if (!PyArg_ParseTuple(args,"iisil;y,x,str,n,attr", &y, &x, &str, &n, &lattr)) + if (!PyArg_ParseTuple(args,"iiOil;y,x,str,n,attr", &y, &x, &strobj, &n, &lattr)) return NULL; attr = lattr; use_xy = use_attr = TRUE; @@ -1235,17 +1527,41 @@ PyCursesWindow_InsNStr(PyCursesWindowObject *self, PyObject *args) return NULL; } +#ifdef HAVE_NCURSESW + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, &wstr); +#else + strtype = PyCurses_ConvertToString(self, strobj, &bytesobj, NULL); +#endif + if (strtype == 0) + return NULL; + if (use_attr == TRUE) { attr_old = getattrs(self->win); (void)wattrset(self->win,attr); } - if (use_xy == TRUE) - rtn = mvwinsnstr(self->win,y,x,str,n); +#ifdef HAVE_NCURSESW + if (strtype == 2) { + funcname = "insn_wstr"; + if (use_xy == TRUE) + rtn = mvwins_nwstr(self->win,y,x,wstr,n); + else + rtn = wins_nwstr(self->win,wstr,n); + PyMem_Free(wstr); + } else - rtn = winsnstr(self->win,str,n); +#endif + { + char *str = PyBytes_AS_STRING(bytesobj); + funcname = "insnstr"; + if (use_xy == TRUE) + rtn = mvwinsnstr(self->win,y,x,str,n); + else + rtn = winsnstr(self->win,str,n); + Py_DECREF(bytesobj); + } if (use_attr == TRUE) (void)wattrset(self->win,attr_old); - return PyCursesCheckERR(rtn, "insnstr"); + return PyCursesCheckERR(rtn, funcname); } static PyObject * @@ -1528,7 +1844,7 @@ PyCursesWindow_SubWin(PyCursesWindowObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, self->encoding); } static PyObject * @@ -1604,16 +1920,51 @@ PyCursesWindow_Vline(PyCursesWindowObject *self, PyObject *args) } if (code != ERR) { - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, - "argument 1 or 3 must be a ch or an int"); + if (!PyCurses_ConvertToChtype(self, temp, &ch)) return NULL; - } return PyCursesCheckERR(wvline(self->win, ch | attr, n), "vline"); } else return PyCursesCheckERR(code, "wmove"); } +static PyObject * +PyCursesWindow_get_encoding(PyCursesWindowObject *self, void *closure) +{ + return PyUnicode_FromString(self->encoding); +} + +static int +PyCursesWindow_set_encoding(PyCursesWindowObject *self, PyObject *value) +{ + PyObject *ascii; + char *encoding; + + /* It is illegal to del win.encoding */ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "encoding may not be deleted"); + return -1; + } + + if (!PyUnicode_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "setting encoding to a non-string"); + return -1; + } + ascii = PyUnicode_AsASCIIString(value); + if (ascii == NULL) + return -1; + encoding = strdup(PyBytes_AS_STRING(ascii)); + if (encoding == NULL) { + PyErr_NoMemory(); + return -1; + } + free(self->encoding); + self->encoding = encoding; + return 0; +} + + static PyMethodDef PyCursesWindow_Methods[] = { {"addch", (PyCFunction)PyCursesWindow_AddCh, METH_VARARGS}, {"addnstr", (PyCFunction)PyCursesWindow_AddNStr, METH_VARARGS}, @@ -1701,6 +2052,13 @@ static PyMethodDef PyCursesWindow_Methods[] = { {NULL, NULL} /* sentinel */ }; +static PyGetSetDef PyCursesWindow_getsets[] = { + {"encoding", + (getter)PyCursesWindow_get_encoding, + (setter)PyCursesWindow_set_encoding, + "the typecode character used to create the array"} +}; + /* -------------------------------------------------------*/ PyTypeObject PyCursesWindow_Type = { @@ -1733,6 +2091,8 @@ PyTypeObject PyCursesWindow_Type = { 0, /*tp_iter*/ 0, /*tp_iternext*/ PyCursesWindow_Methods, /*tp_methods*/ + 0, /* tp_members */ + PyCursesWindow_getsets, /* tp_getset */ }; /********************************************************************* @@ -1956,7 +2316,7 @@ PyCurses_GetWin(PyCursesWindowObject *self, PyObject *stream) PyErr_SetString(PyCursesError, catchall_NULL); return NULL; } - return PyCursesWindow_New(win); + return PyCursesWindow_New(win, NULL); } static PyObject * @@ -2034,10 +2394,11 @@ static PyObject * PyCurses_InitScr(PyObject *self) { WINDOW *win; + PyCursesWindowObject *winobj; if (initialised == TRUE) { wrefresh(stdscr); - return (PyObject *)PyCursesWindow_New(stdscr); + return (PyObject *)PyCursesWindow_New(stdscr, NULL); } win = initscr(); @@ -2129,7 +2490,9 @@ PyCurses_InitScr(PyObject *self) SetDictInt("LINES", LINES); SetDictInt("COLS", COLS); - return (PyObject *)PyCursesWindow_New(win); + winobj = (PyCursesWindowObject *)PyCursesWindow_New(win, NULL); + screen_encoding = winobj->encoding; + return (PyObject *)winobj; } static PyObject * @@ -2331,7 +2694,7 @@ PyCurses_NewPad(PyObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, NULL); } static PyObject * @@ -2363,7 +2726,7 @@ PyCurses_NewWindow(PyObject *self, PyObject *args) return NULL; } - return (PyObject *)PyCursesWindow_New(win); + return (PyObject *)PyCursesWindow_New(win, NULL); } static PyObject * @@ -2680,10 +3043,8 @@ PyCurses_UnCtrl(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args,"O;ch or int",&temp)) return NULL; - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument must be a ch or an int"); + if (!PyCurses_ConvertToChtype(NULL, temp, &ch)) return NULL; - } return PyBytes_FromString(unctrl(ch)); } @@ -2696,12 +3057,11 @@ PyCurses_UngetCh(PyObject *self, PyObject *args) PyCursesInitialised; - if (!PyArg_ParseTuple(args,"O;ch or int",&temp)) return NULL; - - if (!PyCurses_ConvertToChtype(temp, &ch)) { - PyErr_SetString(PyExc_TypeError, "argument must be a ch or an int"); + if (!PyArg_ParseTuple(args,"O;ch or int",&temp)) + return NULL; + + if (!PyCurses_ConvertToChtype(NULL, temp, &ch)) return NULL; - } return PyCursesCheckERR(ungetch(ch), "ungetch"); }