1 /*
2  *   Interface to the ncurses panel library
3  *
4  * Original version by Thomas Gellekum
5  */
6 
7 /* Release Number */
8 
9 static const char PyCursesVersion[] = "2.1";
10 
11 /* Includes */
12 
13 #include "Python.h"
14 
15 #include "py_curses.h"
16 
17 #include <panel.h>
18 
19 typedef struct {
20     PyObject *PyCursesError;
21     PyTypeObject *PyCursesPanel_Type;
22 } _curses_panel_state;
23 
24 static inline _curses_panel_state *
get_curses_panel_state(PyObject * module)25 get_curses_panel_state(PyObject *module)
26 {
27     void *state = PyModule_GetState(module);
28     assert(state != NULL);
29     return (_curses_panel_state *)state;
30 }
31 
32 static int
_curses_panel_clear(PyObject * mod)33 _curses_panel_clear(PyObject *mod)
34 {
35     _curses_panel_state *state = get_curses_panel_state(mod);
36     Py_CLEAR(state->PyCursesError);
37     Py_CLEAR(state->PyCursesPanel_Type);
38     return 0;
39 }
40 
41 static int
_curses_panel_traverse(PyObject * mod,visitproc visit,void * arg)42 _curses_panel_traverse(PyObject *mod, visitproc visit, void *arg)
43 {
44     Py_VISIT(Py_TYPE(mod));
45     _curses_panel_state *state = get_curses_panel_state(mod);
46     Py_VISIT(state->PyCursesError);
47     Py_VISIT(state->PyCursesPanel_Type);
48     return 0;
49 }
50 
51 static void
_curses_panel_free(void * mod)52 _curses_panel_free(void *mod)
53 {
54     _curses_panel_clear((PyObject *) mod);
55 }
56 
57 /* Utility Functions */
58 
59 /*
60  * Check the return code from a curses function and return None
61  * or raise an exception as appropriate.
62  */
63 
64 static PyObject *
PyCursesCheckERR(_curses_panel_state * state,int code,const char * fname)65 PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname)
66 {
67     if (code != ERR) {
68         Py_RETURN_NONE;
69     }
70     else {
71         if (fname == NULL) {
72             PyErr_SetString(state->PyCursesError, catchall_ERR);
73         }
74         else {
75             PyErr_Format(state->PyCursesError, "%s() returned ERR", fname);
76         }
77         return NULL;
78     }
79 }
80 
81 /*****************************************************************************
82  The Panel Object
83 ******************************************************************************/
84 
85 /* Definition of the panel object and panel type */
86 
87 typedef struct {
88     PyObject_HEAD
89     PANEL *pan;
90     PyCursesWindowObject *wo;   /* for reference counts */
91 } PyCursesPanelObject;
92 
93 /* Some helper functions. The problem is that there's always a window
94    associated with a panel. To ensure that Python's GC doesn't pull
95    this window from under our feet we need to keep track of references
96    to the corresponding window object within Python. We can't use
97    dupwin(oldwin) to keep a copy of the curses WINDOW because the
98    contents of oldwin is copied only once; code like
99 
100    win = newwin(...)
101    pan = win.panel()
102    win.addstr(some_string)
103    pan.window().addstr(other_string)
104 
105    will fail. */
106 
107 /* We keep a linked list of PyCursesPanelObjects, lop. A list should
108    suffice, I don't expect more than a handful or at most a few
109    dozens of panel objects within a typical program. */
110 typedef struct _list_of_panels {
111     PyCursesPanelObject *po;
112     struct _list_of_panels *next;
113 } list_of_panels;
114 
115 /* list anchor */
116 static list_of_panels *lop;
117 
118 /* Insert a new panel object into lop */
119 static int
insert_lop(PyCursesPanelObject * po)120 insert_lop(PyCursesPanelObject *po)
121 {
122     list_of_panels *new;
123 
124     if ((new = (list_of_panels *)PyMem_Malloc(sizeof(list_of_panels))) == NULL) {
125         PyErr_NoMemory();
126         return -1;
127     }
128     new->po = po;
129     new->next = lop;
130     lop = new;
131     return 0;
132 }
133 
134 /* Remove the panel object from lop */
135 static void
remove_lop(PyCursesPanelObject * po)136 remove_lop(PyCursesPanelObject *po)
137 {
138     list_of_panels *temp, *n;
139 
140     temp = lop;
141     if (temp->po == po) {
142         lop = temp->next;
143         PyMem_Free(temp);
144         return;
145     }
146     while (temp->next == NULL || temp->next->po != po) {
147         if (temp->next == NULL) {
148             PyErr_SetString(PyExc_RuntimeError,
149                             "remove_lop: can't find Panel Object");
150             return;
151         }
152         temp = temp->next;
153     }
154     n = temp->next->next;
155     PyMem_Free(temp->next);
156     temp->next = n;
157     return;
158 }
159 
160 /* Return the panel object that corresponds to pan */
161 static PyCursesPanelObject *
find_po(PANEL * pan)162 find_po(PANEL *pan)
163 {
164     list_of_panels *temp;
165     for (temp = lop; temp->po->pan != pan; temp = temp->next)
166         if (temp->next == NULL) return NULL;    /* not found!? */
167     return temp->po;
168 }
169 
170 /*[clinic input]
171 module _curses_panel
172 class _curses_panel.panel "PyCursesPanelObject *" "&PyCursesPanel_Type"
173 [clinic start generated code]*/
174 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=2f4ef263ca850a31]*/
175 
176 #include "clinic/_curses_panel.c.h"
177 
178 /* ------------- PANEL routines --------------- */
179 
180 /*[clinic input]
181 _curses_panel.panel.bottom
182 
183     cls: defining_class
184 
185 Push the panel to the bottom of the stack.
186 [clinic start generated code]*/
187 
188 static PyObject *
_curses_panel_panel_bottom_impl(PyCursesPanelObject * self,PyTypeObject * cls)189 _curses_panel_panel_bottom_impl(PyCursesPanelObject *self, PyTypeObject *cls)
190 /*[clinic end generated code: output=8ec7fbbc08554021 input=6b7d2c0578b5a1c4]*/
191 {
192     _curses_panel_state *state = PyType_GetModuleState(cls);
193     return PyCursesCheckERR(state, bottom_panel(self->pan), "bottom");
194 }
195 
196 /*[clinic input]
197 _curses_panel.panel.hide
198 
199     cls: defining_class
200 
201 Hide the panel.
202 
203 This does not delete the object, it just makes the window on screen invisible.
204 [clinic start generated code]*/
205 
206 static PyObject *
_curses_panel_panel_hide_impl(PyCursesPanelObject * self,PyTypeObject * cls)207 _curses_panel_panel_hide_impl(PyCursesPanelObject *self, PyTypeObject *cls)
208 /*[clinic end generated code: output=cc6ab7203cdc1450 input=1bfc741f473e6055]*/
209 {
210     _curses_panel_state *state = PyType_GetModuleState(cls);
211     return PyCursesCheckERR(state, hide_panel(self->pan), "hide");
212 }
213 
214 /*[clinic input]
215 _curses_panel.panel.show
216 
217     cls: defining_class
218 
219 Display the panel (which might have been hidden).
220 [clinic start generated code]*/
221 
222 static PyObject *
_curses_panel_panel_show_impl(PyCursesPanelObject * self,PyTypeObject * cls)223 _curses_panel_panel_show_impl(PyCursesPanelObject *self, PyTypeObject *cls)
224 /*[clinic end generated code: output=dc3421de375f0409 input=8122e80151cb4379]*/
225 {
226     _curses_panel_state *state = PyType_GetModuleState(cls);
227     return PyCursesCheckERR(state, show_panel(self->pan), "show");
228 }
229 
230 /*[clinic input]
231 _curses_panel.panel.top
232 
233     cls: defining_class
234 
235 Push panel to the top of the stack.
236 [clinic start generated code]*/
237 
238 static PyObject *
_curses_panel_panel_top_impl(PyCursesPanelObject * self,PyTypeObject * cls)239 _curses_panel_panel_top_impl(PyCursesPanelObject *self, PyTypeObject *cls)
240 /*[clinic end generated code: output=10a072e511e873f7 input=1f372d597dda3379]*/
241 {
242     _curses_panel_state *state = PyType_GetModuleState(cls);
243     return PyCursesCheckERR(state, top_panel(self->pan), "top");
244 }
245 
246 /* Allocation and deallocation of Panel Objects */
247 
248 static PyObject *
PyCursesPanel_New(_curses_panel_state * state,PANEL * pan,PyCursesWindowObject * wo)249 PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
250                   PyCursesWindowObject *wo)
251 {
252     PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject,
253                                            state->PyCursesPanel_Type);
254     if (po == NULL) {
255         return NULL;
256     }
257 
258     po->pan = pan;
259     if (insert_lop(po) < 0) {
260         po->wo = NULL;
261         Py_DECREF(po);
262         return NULL;
263     }
264     po->wo = wo;
265     Py_INCREF(wo);
266     return (PyObject *)po;
267 }
268 
269 static void
PyCursesPanel_Dealloc(PyCursesPanelObject * po)270 PyCursesPanel_Dealloc(PyCursesPanelObject *po)
271 {
272     PyObject *tp, *obj;
273 
274     tp = (PyObject *) Py_TYPE(po);
275     obj = (PyObject *) panel_userptr(po->pan);
276     if (obj) {
277         (void)set_panel_userptr(po->pan, NULL);
278         Py_DECREF(obj);
279     }
280     (void)del_panel(po->pan);
281     if (po->wo != NULL) {
282         Py_DECREF(po->wo);
283         remove_lop(po);
284     }
285     PyObject_Free(po);
286     Py_DECREF(tp);
287 }
288 
289 /* panel_above(NULL) returns the bottom panel in the stack. To get
290    this behaviour we use curses.panel.bottom_panel(). */
291 /*[clinic input]
292 _curses_panel.panel.above
293 
294 Return the panel above the current panel.
295 [clinic start generated code]*/
296 
297 static PyObject *
_curses_panel_panel_above_impl(PyCursesPanelObject * self)298 _curses_panel_panel_above_impl(PyCursesPanelObject *self)
299 /*[clinic end generated code: output=70ac06d25fd3b4da input=c059994022976788]*/
300 {
301     PANEL *pan;
302     PyCursesPanelObject *po;
303 
304     pan = panel_above(self->pan);
305 
306     if (pan == NULL) {          /* valid output, it means the calling panel
307                                    is on top of the stack */
308         Py_RETURN_NONE;
309     }
310     po = find_po(pan);
311     if (po == NULL) {
312         PyErr_SetString(PyExc_RuntimeError,
313                         "panel_above: can't find Panel Object");
314         return NULL;
315     }
316     Py_INCREF(po);
317     return (PyObject *)po;
318 }
319 
320 /* panel_below(NULL) returns the top panel in the stack. To get
321    this behaviour we use curses.panel.top_panel(). */
322 /*[clinic input]
323 _curses_panel.panel.below
324 
325 Return the panel below the current panel.
326 [clinic start generated code]*/
327 
328 static PyObject *
_curses_panel_panel_below_impl(PyCursesPanelObject * self)329 _curses_panel_panel_below_impl(PyCursesPanelObject *self)
330 /*[clinic end generated code: output=282861122e06e3de input=cc08f61936d297c6]*/
331 {
332     PANEL *pan;
333     PyCursesPanelObject *po;
334 
335     pan = panel_below(self->pan);
336 
337     if (pan == NULL) {          /* valid output, it means the calling panel
338                                    is on the bottom of the stack */
339         Py_RETURN_NONE;
340     }
341     po = find_po(pan);
342     if (po == NULL) {
343         PyErr_SetString(PyExc_RuntimeError,
344                         "panel_below: can't find Panel Object");
345         return NULL;
346     }
347     Py_INCREF(po);
348     return (PyObject *)po;
349 }
350 
351 /*[clinic input]
352 _curses_panel.panel.hidden
353 
354 Return True if the panel is hidden (not visible), False otherwise.
355 [clinic start generated code]*/
356 
357 static PyObject *
_curses_panel_panel_hidden_impl(PyCursesPanelObject * self)358 _curses_panel_panel_hidden_impl(PyCursesPanelObject *self)
359 /*[clinic end generated code: output=66eebd1ab4501a71 input=453d4b4fce25e21a]*/
360 {
361     if (panel_hidden(self->pan))
362         Py_RETURN_TRUE;
363     else
364         Py_RETURN_FALSE;
365 }
366 
367 /*[clinic input]
368 _curses_panel.panel.move
369 
370     cls: defining_class
371     y: int
372     x: int
373     /
374 
375 Move the panel to the screen coordinates (y, x).
376 [clinic start generated code]*/
377 
378 static PyObject *
_curses_panel_panel_move_impl(PyCursesPanelObject * self,PyTypeObject * cls,int y,int x)379 _curses_panel_panel_move_impl(PyCursesPanelObject *self, PyTypeObject *cls,
380                               int y, int x)
381 /*[clinic end generated code: output=ce546c93e56867da input=60a0e7912ff99849]*/
382 {
383     _curses_panel_state *state = PyType_GetModuleState(cls);
384     return PyCursesCheckERR(state, move_panel(self->pan, y, x), "move_panel");
385 }
386 
387 /*[clinic input]
388 _curses_panel.panel.window
389 
390 Return the window object associated with the panel.
391 [clinic start generated code]*/
392 
393 static PyObject *
_curses_panel_panel_window_impl(PyCursesPanelObject * self)394 _curses_panel_panel_window_impl(PyCursesPanelObject *self)
395 /*[clinic end generated code: output=5f05940d4106b4cb input=6067353d2c307901]*/
396 {
397     Py_INCREF(self->wo);
398     return (PyObject *)self->wo;
399 }
400 
401 /*[clinic input]
402 _curses_panel.panel.replace
403 
404     cls: defining_class
405     win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
406     /
407 
408 Change the window associated with the panel to the window win.
409 [clinic start generated code]*/
410 
411 static PyObject *
_curses_panel_panel_replace_impl(PyCursesPanelObject * self,PyTypeObject * cls,PyCursesWindowObject * win)412 _curses_panel_panel_replace_impl(PyCursesPanelObject *self,
413                                  PyTypeObject *cls,
414                                  PyCursesWindowObject *win)
415 /*[clinic end generated code: output=c71f95c212d58ae7 input=dbec7180ece41ff5]*/
416 {
417     _curses_panel_state *state = PyType_GetModuleState(cls);
418 
419     PyCursesPanelObject *po = find_po(self->pan);
420     if (po == NULL) {
421         PyErr_SetString(PyExc_RuntimeError,
422                         "replace_panel: can't find Panel Object");
423         return NULL;
424     }
425 
426     int rtn = replace_panel(self->pan, win->win);
427     if (rtn == ERR) {
428         PyErr_SetString(state->PyCursesError, "replace_panel() returned ERR");
429         return NULL;
430     }
431     Py_INCREF(win);
432     Py_SETREF(po->wo, win);
433     Py_RETURN_NONE;
434 }
435 
436 /*[clinic input]
437 _curses_panel.panel.set_userptr
438 
439     cls: defining_class
440     obj: object
441     /
442 
443 Set the panel's user pointer to obj.
444 [clinic start generated code]*/
445 
446 static PyObject *
_curses_panel_panel_set_userptr_impl(PyCursesPanelObject * self,PyTypeObject * cls,PyObject * obj)447 _curses_panel_panel_set_userptr_impl(PyCursesPanelObject *self,
448                                      PyTypeObject *cls, PyObject *obj)
449 /*[clinic end generated code: output=db74f3db07b28080 input=e3fee2ff7b1b8e48]*/
450 {
451     PyCursesInitialised;
452     Py_INCREF(obj);
453     PyObject *oldobj = (PyObject *) panel_userptr(self->pan);
454     int rc = set_panel_userptr(self->pan, (void*)obj);
455     if (rc == ERR) {
456         /* In case of an ncurses error, decref the new object again */
457         Py_DECREF(obj);
458     }
459     else {
460         Py_XDECREF(oldobj);
461     }
462 
463     _curses_panel_state *state = PyType_GetModuleState(cls);
464     return PyCursesCheckERR(state, rc, "set_panel_userptr");
465 }
466 
467 /*[clinic input]
468 _curses_panel.panel.userptr
469 
470     cls: defining_class
471 
472 Return the user pointer for the panel.
473 [clinic start generated code]*/
474 
475 static PyObject *
_curses_panel_panel_userptr_impl(PyCursesPanelObject * self,PyTypeObject * cls)476 _curses_panel_panel_userptr_impl(PyCursesPanelObject *self,
477                                  PyTypeObject *cls)
478 /*[clinic end generated code: output=eea6e6f39ffc0179 input=f22ca4f115e30a80]*/
479 {
480     _curses_panel_state *state = PyType_GetModuleState(cls);
481 
482     PyCursesInitialised;
483     PyObject *obj = (PyObject *) panel_userptr(self->pan);
484     if (obj == NULL) {
485         PyErr_SetString(state->PyCursesError, "no userptr set");
486         return NULL;
487     }
488 
489     Py_INCREF(obj);
490     return obj;
491 }
492 
493 
494 /* Module interface */
495 
496 static PyMethodDef PyCursesPanel_Methods[] = {
497     _CURSES_PANEL_PANEL_ABOVE_METHODDEF
498     _CURSES_PANEL_PANEL_BELOW_METHODDEF
499     _CURSES_PANEL_PANEL_BOTTOM_METHODDEF
500     _CURSES_PANEL_PANEL_HIDDEN_METHODDEF
501     _CURSES_PANEL_PANEL_HIDE_METHODDEF
502     _CURSES_PANEL_PANEL_MOVE_METHODDEF
503     _CURSES_PANEL_PANEL_REPLACE_METHODDEF
504     _CURSES_PANEL_PANEL_SET_USERPTR_METHODDEF
505     _CURSES_PANEL_PANEL_SHOW_METHODDEF
506     _CURSES_PANEL_PANEL_TOP_METHODDEF
507     _CURSES_PANEL_PANEL_USERPTR_METHODDEF
508     _CURSES_PANEL_PANEL_WINDOW_METHODDEF
509     {NULL,              NULL}   /* sentinel */
510 };
511 
512 /* -------------------------------------------------------*/
513 
514 static PyType_Slot PyCursesPanel_Type_slots[] = {
515     {Py_tp_dealloc, PyCursesPanel_Dealloc},
516     {Py_tp_methods, PyCursesPanel_Methods},
517     {0, 0},
518 };
519 
520 static PyType_Spec PyCursesPanel_Type_spec = {
521     .name = "_curses_panel.panel",
522     .basicsize = sizeof(PyCursesPanelObject),
523     .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
524     .slots = PyCursesPanel_Type_slots
525 };
526 
527 /* Wrapper for panel_above(NULL). This function returns the bottom
528    panel of the stack, so it's renamed to bottom_panel().
529    panel.above() *requires* a panel object in the first place which
530    may be undesirable. */
531 /*[clinic input]
532 _curses_panel.bottom_panel
533 
534 Return the bottom panel in the panel stack.
535 [clinic start generated code]*/
536 
537 static PyObject *
_curses_panel_bottom_panel_impl(PyObject * module)538 _curses_panel_bottom_panel_impl(PyObject *module)
539 /*[clinic end generated code: output=3aba9f985f4c2bd0 input=634c2a8078b3d7e4]*/
540 {
541     PANEL *pan;
542     PyCursesPanelObject *po;
543 
544     PyCursesInitialised;
545 
546     pan = panel_above(NULL);
547 
548     if (pan == NULL) {          /* valid output, it means
549                                    there's no panel at all */
550         Py_RETURN_NONE;
551     }
552     po = find_po(pan);
553     if (po == NULL) {
554         PyErr_SetString(PyExc_RuntimeError,
555                         "panel_above: can't find Panel Object");
556         return NULL;
557     }
558     Py_INCREF(po);
559     return (PyObject *)po;
560 }
561 
562 /*[clinic input]
563 _curses_panel.new_panel
564 
565     win: object(type="PyCursesWindowObject *", subclass_of="&PyCursesWindow_Type")
566     /
567 
568 Return a panel object, associating it with the given window win.
569 [clinic start generated code]*/
570 
571 static PyObject *
_curses_panel_new_panel_impl(PyObject * module,PyCursesWindowObject * win)572 _curses_panel_new_panel_impl(PyObject *module, PyCursesWindowObject *win)
573 /*[clinic end generated code: output=45e948e0176a9bd2 input=74d4754e0ebe4800]*/
574 {
575     _curses_panel_state *state = get_curses_panel_state(module);
576 
577     PANEL *pan = new_panel(win->win);
578     if (pan == NULL) {
579         PyErr_SetString(state->PyCursesError, catchall_NULL);
580         return NULL;
581     }
582     return (PyObject *)PyCursesPanel_New(state, pan, win);
583 }
584 
585 
586 /* Wrapper for panel_below(NULL). This function returns the top panel
587    of the stack, so it's renamed to top_panel(). panel.below()
588    *requires* a panel object in the first place which may be
589    undesirable. */
590 /*[clinic input]
591 _curses_panel.top_panel
592 
593 Return the top panel in the panel stack.
594 [clinic start generated code]*/
595 
596 static PyObject *
_curses_panel_top_panel_impl(PyObject * module)597 _curses_panel_top_panel_impl(PyObject *module)
598 /*[clinic end generated code: output=86704988bea8508e input=e62d6278dba39e79]*/
599 {
600     PANEL *pan;
601     PyCursesPanelObject *po;
602 
603     PyCursesInitialised;
604 
605     pan = panel_below(NULL);
606 
607     if (pan == NULL) {          /* valid output, it means
608                                    there's no panel at all */
609         Py_RETURN_NONE;
610     }
611     po = find_po(pan);
612     if (po == NULL) {
613         PyErr_SetString(PyExc_RuntimeError,
614                         "panel_below: can't find Panel Object");
615         return NULL;
616     }
617     Py_INCREF(po);
618     return (PyObject *)po;
619 }
620 
621 /*[clinic input]
622 _curses_panel.update_panels
623 
624 Updates the virtual screen after changes in the panel stack.
625 
626 This does not call curses.doupdate(), so you'll have to do this yourself.
627 [clinic start generated code]*/
628 
629 static PyObject *
_curses_panel_update_panels_impl(PyObject * module)630 _curses_panel_update_panels_impl(PyObject *module)
631 /*[clinic end generated code: output=2f3b4c2e03d90ded input=5299624c9a708621]*/
632 {
633     PyCursesInitialised;
634     update_panels();
635     Py_RETURN_NONE;
636 }
637 
638 /* List of functions defined in the module */
639 
640 static PyMethodDef PyCurses_methods[] = {
641     _CURSES_PANEL_BOTTOM_PANEL_METHODDEF
642     _CURSES_PANEL_NEW_PANEL_METHODDEF
643     _CURSES_PANEL_TOP_PANEL_METHODDEF
644     _CURSES_PANEL_UPDATE_PANELS_METHODDEF
645     {NULL,              NULL}           /* sentinel */
646 };
647 
648 /* Initialization function for the module */
649 static int
_curses_panel_exec(PyObject * mod)650 _curses_panel_exec(PyObject *mod)
651 {
652     _curses_panel_state *state = get_curses_panel_state(mod);
653     /* Initialize object type */
654     state->PyCursesPanel_Type = (PyTypeObject *)PyType_FromModuleAndSpec(
655         mod, &PyCursesPanel_Type_spec, NULL);
656     if (state->PyCursesPanel_Type == NULL) {
657         return -1;
658     }
659 
660     if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) {
661         return -1;
662     }
663 
664     import_curses();
665     if (PyErr_Occurred()) {
666         return -1;
667     }
668 
669     /* For exception _curses_panel.error */
670     state->PyCursesError = PyErr_NewException(
671         "_curses_panel.error", NULL, NULL);
672 
673     Py_INCREF(state->PyCursesError);
674     if (PyModule_AddObject(mod, "error", state->PyCursesError) < 0) {
675         Py_DECREF(state->PyCursesError);
676         return -1;
677     }
678 
679     /* Make the version available */
680     PyObject *v = PyUnicode_FromString(PyCursesVersion);
681     if (v == NULL) {
682         return -1;
683     }
684 
685     PyObject *d = PyModule_GetDict(mod);
686     if (PyDict_SetItemString(d, "version", v) < 0) {
687         Py_DECREF(v);
688         return -1;
689     }
690     if (PyDict_SetItemString(d, "__version__", v) < 0) {
691         Py_DECREF(v);
692         return -1;
693     }
694 
695     Py_DECREF(v);
696 
697     return 0;
698 }
699 
700 static PyModuleDef_Slot _curses_slots[] = {
701     {Py_mod_exec, _curses_panel_exec},
702     {0, NULL}
703 };
704 
705 static struct PyModuleDef _curses_panelmodule = {
706     PyModuleDef_HEAD_INIT,
707     .m_name = "_curses_panel",
708     .m_size = sizeof(_curses_panel_state),
709     .m_methods = PyCurses_methods,
710     .m_slots = _curses_slots,
711     .m_traverse = _curses_panel_traverse,
712     .m_clear = _curses_panel_clear,
713     .m_free = _curses_panel_free
714 };
715 
716 PyMODINIT_FUNC
PyInit__curses_panel(void)717 PyInit__curses_panel(void)
718 {
719     return PyModuleDef_Init(&_curses_panelmodule);
720 }
721