urwid 4.0.6__tar.gz → 4.0.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. {urwid-4.0.6/urwid.egg-info → urwid-4.0.7}/PKG-INFO +1 -1
  2. {urwid-4.0.6 → urwid-4.0.7}/docs/changelog.rst +17 -0
  3. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/graph.py +1 -1
  4. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/real_browse.py +1 -1
  5. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/real_edit.py +1 -1
  6. {urwid-4.0.6 → urwid-4.0.7}/examples/browse.py +1 -1
  7. {urwid-4.0.6 → urwid-4.0.7}/examples/calc.py +1 -1
  8. {urwid-4.0.6 → urwid-4.0.7}/examples/edit.py +1 -1
  9. {urwid-4.0.6 → urwid-4.0.7}/examples/fib.py +1 -1
  10. {urwid-4.0.6 → urwid-4.0.7}/examples/graph.py +1 -1
  11. {urwid-4.0.6 → urwid-4.0.7}/examples/treesample.py +2 -2
  12. {urwid-4.0.6 → urwid-4.0.7}/pyproject.toml +8 -0
  13. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/web.py +32 -11
  14. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/main_loop.py +67 -20
  15. {urwid-4.0.6 → urwid-4.0.7}/urwid/signals.py +19 -8
  16. {urwid-4.0.6 → urwid-4.0.7}/urwid/version.py +3 -3
  17. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/constants.py +19 -7
  18. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/filler.py +7 -2
  19. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/listbox.py +89 -67
  20. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/monitored_list.py +10 -9
  21. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/overlay.py +15 -6
  22. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/scrollable.py +23 -20
  23. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/treetools.py +25 -22
  24. {urwid-4.0.6 → urwid-4.0.7/urwid.egg-info}/PKG-INFO +1 -1
  25. {urwid-4.0.6 → urwid-4.0.7}/urwid.egg-info/scm_file_list.json +238 -238
  26. urwid-4.0.7/urwid.egg-info/scm_version.json +8 -0
  27. urwid-4.0.6/urwid.egg-info/scm_version.json +0 -8
  28. {urwid-4.0.6 → urwid-4.0.7}/.coveralls.yml +0 -0
  29. {urwid-4.0.6 → urwid-4.0.7}/.devcontainer/dev.dockerfile +0 -0
  30. {urwid-4.0.6 → urwid-4.0.7}/.devcontainer/devcontainer.json +0 -0
  31. {urwid-4.0.6 → urwid-4.0.7}/.dockerignore +0 -0
  32. {urwid-4.0.6 → urwid-4.0.7}/.editorconfig +0 -0
  33. {urwid-4.0.6 → urwid-4.0.7}/.github/CODE_OF_CONDUCT.md +0 -0
  34. {urwid-4.0.6 → urwid-4.0.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  35. {urwid-4.0.6 → urwid-4.0.7}/.github/ISSUE_TEMPLATE/docs.md +0 -0
  36. {urwid-4.0.6 → urwid-4.0.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  37. {urwid-4.0.6 → urwid-4.0.7}/.github/ISSUE_TEMPLATE/question.md +0 -0
  38. {urwid-4.0.6 → urwid-4.0.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  39. {urwid-4.0.6 → urwid-4.0.7}/.github/dependabot.yml +0 -0
  40. {urwid-4.0.6 → urwid-4.0.7}/.github/labeler.yml +0 -0
  41. {urwid-4.0.6 → urwid-4.0.7}/.github/release.yml +0 -0
  42. {urwid-4.0.6 → urwid-4.0.7}/.github/workflows/documentation.yml +0 -0
  43. {urwid-4.0.6 → urwid-4.0.7}/.github/workflows/isolated_static_check.yml +0 -0
  44. {urwid-4.0.6 → urwid-4.0.7}/.github/workflows/labels.yml +0 -0
  45. {urwid-4.0.6 → urwid-4.0.7}/.github/workflows/pythonpackage.yml +0 -0
  46. {urwid-4.0.6 → urwid-4.0.7}/.github/workflows/yamllint.yml +0 -0
  47. {urwid-4.0.6 → urwid-4.0.7}/.gitignore +0 -0
  48. {urwid-4.0.6 → urwid-4.0.7}/.pre-commit-config.yaml +0 -0
  49. {urwid-4.0.6 → urwid-4.0.7}/.yamllint.yml +0 -0
  50. {urwid-4.0.6 → urwid-4.0.7}/COPYING +0 -0
  51. {urwid-4.0.6 → urwid-4.0.7}/MANIFEST.in +0 -0
  52. {urwid-4.0.6 → urwid-4.0.7}/README.rst +0 -0
  53. {urwid-4.0.6 → urwid-4.0.7}/SECURITY.md +0 -0
  54. {urwid-4.0.6 → urwid-4.0.7}/classifiers.txt +0 -0
  55. {urwid-4.0.6 → urwid-4.0.7}/docs/Makefile +0 -0
  56. {urwid-4.0.6 → urwid-4.0.7}/docs/conf.py +0 -0
  57. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/bigtext.py +0 -0
  58. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/bigtext.py.xdotool +0 -0
  59. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/bigtext1.png +0 -0
  60. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/bigtext2.png +0 -0
  61. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/bigtext3.png +0 -0
  62. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/browse.py +0 -0
  63. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/browse.py.xdotool +0 -0
  64. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/browse1.png +0 -0
  65. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/browse2.png +0 -0
  66. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/edit.py +0 -0
  67. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/edit.py.xdotool +0 -0
  68. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/edit1.png +0 -0
  69. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/edit2.png +0 -0
  70. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/edit_text.txt +0 -0
  71. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/graph.py.xdotool +0 -0
  72. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/graph1.png +0 -0
  73. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/graph2.png +0 -0
  74. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/index.rst +0 -0
  75. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/palette_test.py +0 -0
  76. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/palette_test.py.xdotool +0 -0
  77. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/palette_test1.png +0 -0
  78. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/palette_test2.png +0 -0
  79. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/pop_up.py +0 -0
  80. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/pop_up.py.xdotool +0 -0
  81. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/pop_up1.png +0 -0
  82. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/pop_up2.png +0 -0
  83. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/subproc.py +0 -0
  84. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/subproc.py.xdotool +0 -0
  85. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/subproc1.png +0 -0
  86. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/subproc2.png +0 -0
  87. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/subproc2.py +0 -0
  88. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/tour.py +0 -0
  89. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/tour.py.xdotool +0 -0
  90. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/tour1.png +0 -0
  91. {urwid-4.0.6 → urwid-4.0.7}/docs/examples/tour2.png +0 -0
  92. {urwid-4.0.6 → urwid-4.0.7}/docs/index.rst +0 -0
  93. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/bright_combinations.py +0 -0
  94. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/bright_combinations.py.xdotool +0 -0
  95. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/bright_combinations1.png +0 -0
  96. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/canvascache.rst +0 -0
  97. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/displayattributes.rst +0 -0
  98. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/displaymodules.rst +0 -0
  99. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/encodings.rst +0 -0
  100. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/display_modules.png +0 -0
  101. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/introduction.png +0 -0
  102. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/urwid_widgets.svgz +0 -0
  103. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/urwid_widgets_1.png +0 -0
  104. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/urwid_widgets_1.xcf +0 -0
  105. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/urwid_widgets_2.png +0 -0
  106. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/urwid_widgets_2.xcf +0 -0
  107. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/images/widget_layout.png +0 -0
  108. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/index.rst +0 -0
  109. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/mainloop.rst +0 -0
  110. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/overview.rst +0 -0
  111. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/safe_combinations.py +0 -0
  112. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/safe_combinations.py.xdotool +0 -0
  113. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/safe_combinations1.png +0 -0
  114. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/textlayout.rst +0 -0
  115. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/userinput.rst +0 -0
  116. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/wanat.py +0 -0
  117. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/wanat_multi.py +0 -0
  118. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/wcur1.py +0 -0
  119. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/wcur2.py +0 -0
  120. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/widgets.rst +0 -0
  121. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/wmod.py +0 -0
  122. {urwid-4.0.6 → urwid-4.0.7}/docs/manual/wsel.py +0 -0
  123. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/attrspec.rst +0 -0
  124. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/canvas.rst +0 -0
  125. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/command_map.rst +0 -0
  126. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/constants.rst +0 -0
  127. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/deprecated.rst +0 -0
  128. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/display_modules.rst +0 -0
  129. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/exceptions.rst +0 -0
  130. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/global_settings.rst +0 -0
  131. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/index.rst +0 -0
  132. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/list_walkers.rst +0 -0
  133. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/main_loop.rst +0 -0
  134. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/meta.rst +0 -0
  135. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/signals.rst +0 -0
  136. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/text_layout.rst +0 -0
  137. {urwid-4.0.6 → urwid-4.0.7}/docs/reference/widget.rst +0 -0
  138. {urwid-4.0.6 → urwid-4.0.7}/docs/tools/compile_pngs.sh +0 -0
  139. {urwid-4.0.6 → urwid-4.0.7}/docs/tools/screenshots.sh +0 -0
  140. {urwid-4.0.6 → urwid-4.0.7}/docs/tools/static/.placeholder +0 -0
  141. {urwid-4.0.6 → urwid-4.0.7}/docs/tools/templates/indexcontent.html +0 -0
  142. {urwid-4.0.6 → urwid-4.0.7}/docs/tools/templates/indexsidebar.html +0 -0
  143. {urwid-4.0.6 → urwid-4.0.7}/docs/tools/templates/localtoc.html +0 -0
  144. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/adventure.py +0 -0
  145. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/adventure.py.xdotool +0 -0
  146. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/adventure1.png +0 -0
  147. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/adventure2.png +0 -0
  148. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/adventure3.png +0 -0
  149. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/adventure4.png +0 -0
  150. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/cmenu.py +0 -0
  151. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/cmenu.py.xdotool +0 -0
  152. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/cmenu1.png +0 -0
  153. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/cmenu2.png +0 -0
  154. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/cmenu3.png +0 -0
  155. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/cmenu4.png +0 -0
  156. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/highcolors.py +0 -0
  157. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/highcolors.py.xdotool +0 -0
  158. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/highcolors1.png +0 -0
  159. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/hmenu.py +0 -0
  160. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/hmenu.py.xdotool +0 -0
  161. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/hmenu1.png +0 -0
  162. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/hmenu2.png +0 -0
  163. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/hmenu3.png +0 -0
  164. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/hmenu4.png +0 -0
  165. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/index.rst +0 -0
  166. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input.py +0 -0
  167. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input.py.xdotool +0 -0
  168. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input1.png +0 -0
  169. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input2.png +0 -0
  170. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input3.png +0 -0
  171. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input4.png +0 -0
  172. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/input5.png +0 -0
  173. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/menu25.png +0 -0
  174. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/minimal.py +0 -0
  175. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/minimal.py.xdotool +0 -0
  176. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/minimal1.png +0 -0
  177. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/multiple.py +0 -0
  178. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/multiple.py.xdotool +0 -0
  179. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/multiple1.png +0 -0
  180. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/multiple2.png +0 -0
  181. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/multiple3.png +0 -0
  182. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/multiple4.png +0 -0
  183. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/qa.py +0 -0
  184. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/qa.py.xdotool +0 -0
  185. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/qa1.png +0 -0
  186. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/qa2.png +0 -0
  187. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/qa3.png +0 -0
  188. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/sig.py +0 -0
  189. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/sig.py.xdotool +0 -0
  190. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/sig1.png +0 -0
  191. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/sig2.png +0 -0
  192. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/sig3.png +0 -0
  193. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/sig4.png +0 -0
  194. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/smenu.py +0 -0
  195. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/smenu.py.xdotool +0 -0
  196. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/smenu1.png +0 -0
  197. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/smenu2.png +0 -0
  198. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/smenu3.png +0 -0
  199. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/urwid_attr.py +0 -0
  200. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/urwid_attr.py.xdotool +0 -0
  201. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/urwid_attr1.png +0 -0
  202. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/urwid_attr2.png +0 -0
  203. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/urwid_attr3.png +0 -0
  204. {urwid-4.0.6 → urwid-4.0.7}/docs/tutorial/urwid_attr4.png +0 -0
  205. {urwid-4.0.6 → urwid-4.0.7}/docs/urwid-logo.png +0 -0
  206. {urwid-4.0.6 → urwid-4.0.7}/examples/asyncio_socket_server.py +0 -0
  207. {urwid-4.0.6 → urwid-4.0.7}/examples/bigtext.py +0 -0
  208. {urwid-4.0.6 → urwid-4.0.7}/examples/dialog.py +0 -0
  209. {urwid-4.0.6 → urwid-4.0.7}/examples/input_test.py +0 -0
  210. {urwid-4.0.6 → urwid-4.0.7}/examples/lcd_cf635.py +0 -0
  211. {urwid-4.0.6 → urwid-4.0.7}/examples/palette_test.py +0 -0
  212. {urwid-4.0.6 → urwid-4.0.7}/examples/pop_up.py +0 -0
  213. {urwid-4.0.6 → urwid-4.0.7}/examples/subproc.py +0 -0
  214. {urwid-4.0.6 → urwid-4.0.7}/examples/subproc2.py +0 -0
  215. {urwid-4.0.6 → urwid-4.0.7}/examples/terminal.py +0 -0
  216. {urwid-4.0.6 → urwid-4.0.7}/examples/tour.py +0 -0
  217. {urwid-4.0.6 → urwid-4.0.7}/examples/twisted_serve_ssh.py +0 -0
  218. {urwid-4.0.6 → urwid-4.0.7}/examples/twisted_serve_ssh.tac +0 -0
  219. {urwid-4.0.6 → urwid-4.0.7}/pylint-requirements.txt +0 -0
  220. {urwid-4.0.6 → urwid-4.0.7}/requirements.txt +0 -0
  221. {urwid-4.0.6 → urwid-4.0.7}/ruff-requirements.txt +0 -0
  222. {urwid-4.0.6 → urwid-4.0.7}/setup.cfg +0 -0
  223. {urwid-4.0.6 → urwid-4.0.7}/test_requirements.txt +0 -0
  224. {urwid-4.0.6 → urwid-4.0.7}/tests/__init__.py +0 -0
  225. {urwid-4.0.6 → urwid-4.0.7}/tests/test_canvas.py +0 -0
  226. {urwid-4.0.6 → urwid-4.0.7}/tests/test_columns.py +0 -0
  227. {urwid-4.0.6 → urwid-4.0.7}/tests/test_container.py +0 -0
  228. {urwid-4.0.6 → urwid-4.0.7}/tests/test_doctests.py +0 -0
  229. {urwid-4.0.6 → urwid-4.0.7}/tests/test_escapes.py +0 -0
  230. {urwid-4.0.6 → urwid-4.0.7}/tests/test_event_loops.py +0 -0
  231. {urwid-4.0.6 → urwid-4.0.7}/tests/test_filler.py +0 -0
  232. {urwid-4.0.6 → urwid-4.0.7}/tests/test_floatedit.py +0 -0
  233. {urwid-4.0.6 → urwid-4.0.7}/tests/test_font.py +0 -0
  234. {urwid-4.0.6 → urwid-4.0.7}/tests/test_frame.py +0 -0
  235. {urwid-4.0.6 → urwid-4.0.7}/tests/test_grapheme_clusters.py +0 -0
  236. {urwid-4.0.6 → urwid-4.0.7}/tests/test_graphics.py +0 -0
  237. {urwid-4.0.6 → urwid-4.0.7}/tests/test_grid_flow.py +0 -0
  238. {urwid-4.0.6 → urwid-4.0.7}/tests/test_line_box.py +0 -0
  239. {urwid-4.0.6 → urwid-4.0.7}/tests/test_listbox.py +0 -0
  240. {urwid-4.0.6 → urwid-4.0.7}/tests/test_main_loop.py +0 -0
  241. {urwid-4.0.6 → urwid-4.0.7}/tests/test_overlay.py +0 -0
  242. {urwid-4.0.6 → urwid-4.0.7}/tests/test_padding.py +0 -0
  243. {urwid-4.0.6 → urwid-4.0.7}/tests/test_pile.py +0 -0
  244. {urwid-4.0.6 → urwid-4.0.7}/tests/test_raw_display.py +0 -0
  245. {urwid-4.0.6 → urwid-4.0.7}/tests/test_scrollable.py +0 -0
  246. {urwid-4.0.6 → urwid-4.0.7}/tests/test_signals.py +0 -0
  247. {urwid-4.0.6 → urwid-4.0.7}/tests/test_str_util.py +0 -0
  248. {urwid-4.0.6 → urwid-4.0.7}/tests/test_text_layout.py +0 -0
  249. {urwid-4.0.6 → urwid-4.0.7}/tests/test_tree.py +0 -0
  250. {urwid-4.0.6 → urwid-4.0.7}/tests/test_util.py +0 -0
  251. {urwid-4.0.6 → urwid-4.0.7}/tests/test_vterm.py +0 -0
  252. {urwid-4.0.6 → urwid-4.0.7}/tests/test_widget.py +0 -0
  253. {urwid-4.0.6 → urwid-4.0.7}/tests/util.py +0 -0
  254. {urwid-4.0.6 → urwid-4.0.7}/tox.ini +0 -0
  255. {urwid-4.0.6 → urwid-4.0.7}/urwid/__init__.py +0 -0
  256. {urwid-4.0.6 → urwid-4.0.7}/urwid/canvas.py +0 -0
  257. {urwid-4.0.6 → urwid-4.0.7}/urwid/command_map.py +0 -0
  258. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/__init__.py +0 -0
  259. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/_posix_raw_display.py +0 -0
  260. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/_raw_display_base.py +0 -0
  261. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/_web.css +0 -0
  262. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/_web.js +0 -0
  263. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/_win32.py +0 -0
  264. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/_win32_raw_display.py +0 -0
  265. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/common.py +0 -0
  266. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/curses.py +0 -0
  267. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/escape.py +0 -0
  268. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/html_fragment.py +0 -0
  269. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/lcd.py +0 -0
  270. {urwid-4.0.6 → urwid-4.0.7}/urwid/display/raw.py +0 -0
  271. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/__init__.py +0 -0
  272. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/abstract_loop.py +0 -0
  273. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/asyncio_loop.py +0 -0
  274. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/glib_loop.py +0 -0
  275. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/select_loop.py +0 -0
  276. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/tornado_loop.py +0 -0
  277. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/trio_loop.py +0 -0
  278. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/twisted_loop.py +0 -0
  279. {urwid-4.0.6 → urwid-4.0.7}/urwid/event_loop/zmq_loop.py +0 -0
  280. {urwid-4.0.6 → urwid-4.0.7}/urwid/font.py +0 -0
  281. {urwid-4.0.6 → urwid-4.0.7}/urwid/graphics.py +0 -0
  282. {urwid-4.0.6 → urwid-4.0.7}/urwid/numedit.py +0 -0
  283. {urwid-4.0.6 → urwid-4.0.7}/urwid/py.typed +0 -0
  284. {urwid-4.0.6 → urwid-4.0.7}/urwid/split_repr.py +0 -0
  285. {urwid-4.0.6 → urwid-4.0.7}/urwid/str_util.py +0 -0
  286. {urwid-4.0.6 → urwid-4.0.7}/urwid/text_layout.py +0 -0
  287. {urwid-4.0.6 → urwid-4.0.7}/urwid/util.py +0 -0
  288. {urwid-4.0.6 → urwid-4.0.7}/urwid/vterm.py +0 -0
  289. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/__init__.py +0 -0
  290. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/attr_map.py +0 -0
  291. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/attr_wrap.py +0 -0
  292. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/bar_graph.py +0 -0
  293. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/big_text.py +0 -0
  294. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/box_adapter.py +0 -0
  295. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/columns.py +0 -0
  296. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/container.py +0 -0
  297. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/divider.py +0 -0
  298. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/edit.py +0 -0
  299. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/frame.py +0 -0
  300. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/grid_flow.py +0 -0
  301. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/line_box.py +0 -0
  302. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/padding.py +0 -0
  303. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/pile.py +0 -0
  304. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/popup.py +0 -0
  305. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/progress_bar.py +0 -0
  306. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/solid_fill.py +0 -0
  307. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/text.py +0 -0
  308. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/widget.py +0 -0
  309. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/widget_decoration.py +0 -0
  310. {urwid-4.0.6 → urwid-4.0.7}/urwid/widget/wimp.py +0 -0
  311. {urwid-4.0.6 → urwid-4.0.7}/urwid.egg-info/SOURCES.txt +0 -0
  312. {urwid-4.0.6 → urwid-4.0.7}/urwid.egg-info/dependency_links.txt +0 -0
  313. {urwid-4.0.6 → urwid-4.0.7}/urwid.egg-info/not-zip-safe +0 -0
  314. {urwid-4.0.6 → urwid-4.0.7}/urwid.egg-info/requires.txt +0 -0
  315. {urwid-4.0.6 → urwid-4.0.7}/urwid.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urwid
3
- Version: 4.0.6
3
+ Version: 4.0.7
4
4
  Summary: A full-featured console (xterm et al.) user interface library
5
5
  Author-email: Ian Ward <ian@excess.org>
6
6
  License-Expression: LGPL-2.1-only
@@ -2,6 +2,23 @@
2
2
  Changelog
3
3
  ---------
4
4
 
5
+ Urwid 4.0.7
6
+ ===========
7
+
8
+ 2026-07-24
9
+
10
+ Bug fixes 🕷
11
+ ++++++++++++
12
+ * Fix encoding handling by web display by @penguinolog in https://github.com/urwid/urwid/pull/1201
13
+ Documentation 🕮
14
+ ++++++++++++++++
15
+ * Typing: annotate `MainLoop` and `signals` by @penguinolog in https://github.com/urwid/urwid/pull/1199
16
+ * Typing: fix `ListBox` expected widget API by @penguinolog in https://github.com/urwid/urwid/pull/1200
17
+ * Typing: partial annotation for `treetools` by @penguinolog in https://github.com/urwid/urwid/pull/1197
18
+
19
+
20
+ **Full Changelog**: https://github.com/urwid/urwid/compare/4.0.6...4.0.7
21
+
5
22
  Urwid 4.0.6
6
23
  ===========
7
24
 
@@ -276,7 +276,7 @@ class GraphView(urwid.WidgetWrap):
276
276
  def exit_program(self, w) -> typing.NoReturn:
277
277
  raise urwid.ExitMainLoop()
278
278
 
279
- def graph_controls(self):
279
+ def graph_controls(self) -> urwid.ListBox[int]:
280
280
  modes = self.controller.get_modes()
281
281
  # setup mode radio buttons
282
282
  self.mode_buttons = []
@@ -132,7 +132,7 @@ class DirectoryWidget(FlagFileWidget):
132
132
  return node.get_key()
133
133
 
134
134
 
135
- class FileNode(urwid.TreeNode):
135
+ class FileNode(urwid.TreeNode[str]):
136
136
  """Metadata storage for individual files"""
137
137
 
138
138
  def __init__(self, path: str, parent: urwid.ParentNode | None = None) -> None:
@@ -38,7 +38,7 @@ import typing
38
38
  import urwid
39
39
 
40
40
 
41
- class LineWalker(urwid.ListWalker):
41
+ class LineWalker(urwid.ListWalker[int, urwid.Edit]):
42
42
  """ListWalker-compatible class for lazily reading file contents."""
43
43
 
44
44
  def __init__(self, name: str) -> None:
@@ -132,7 +132,7 @@ class DirectoryWidget(FlagFileWidget):
132
132
  return node.get_key()
133
133
 
134
134
 
135
- class FileNode(urwid.TreeNode):
135
+ class FileNode(urwid.TreeNode[str]):
136
136
  """Metadata storage for individual files"""
137
137
 
138
138
  def __init__(self, path: str, parent: urwid.ParentNode | None = None) -> None:
@@ -225,7 +225,7 @@ class ParentEdit(urwid.Edit):
225
225
  return key
226
226
 
227
227
 
228
- class CellWalker(urwid.ListWalker):
228
+ class CellWalker(urwid.ListWalker[tuple[int, int], urwid.Divider | urwid.AttrMap[urwid.IntEdit] | urwid.Text]):
229
229
  def __init__(self, content: Iterable[Cell]) -> None:
230
230
  self.content = urwid.MonitoredList(content)
231
231
  self.content.set_modified_callback(self._modified)
@@ -38,7 +38,7 @@ import typing
38
38
  import urwid
39
39
 
40
40
 
41
- class LineWalker(urwid.ListWalker):
41
+ class LineWalker(urwid.ListWalker[int, urwid.Edit]):
42
42
  """ListWalker-compatible class for lazily reading file contents."""
43
43
 
44
44
  def __init__(self, name: str) -> None:
@@ -37,7 +37,7 @@ if typing.TYPE_CHECKING:
37
37
  from typing_extensions import Literal
38
38
 
39
39
 
40
- class FibonacciWalker(urwid.ListWalker):
40
+ class FibonacciWalker(urwid.ListWalker[tuple[int, int], urwid.Text]):
41
41
  """ListWalker-compatible class for browsing fibonacci set.
42
42
 
43
43
  positions returned are (value at position-1, value at position) tuples.
@@ -276,7 +276,7 @@ class GraphView(urwid.WidgetWrap):
276
276
  def exit_program(self, w) -> typing.NoReturn:
277
277
  raise urwid.ExitMainLoop()
278
278
 
279
- def graph_controls(self):
279
+ def graph_controls(self) -> urwid.ListBox[int]:
280
280
  modes = self.controller.get_modes()
281
281
  # setup mode radio buttons
282
282
  self.mode_buttons = []
@@ -55,14 +55,14 @@ class ExampleTreeWidget(urwid.TreeWidget):
55
55
  return self.get_node().get_value()["name"]
56
56
 
57
57
 
58
- class ExampleNode(urwid.TreeNode):
58
+ class ExampleNode(urwid.TreeNode[str | None]):
59
59
  """Data storage object for leaf nodes"""
60
60
 
61
61
  def load_widget(self) -> ExampleTreeWidget:
62
62
  return ExampleTreeWidget(self)
63
63
 
64
64
 
65
- class ExampleParentNode(urwid.ParentNode):
65
+ class ExampleParentNode(urwid.ParentNode[SampleTree]):
66
66
  """Data storage object for interior/parent nodes"""
67
67
 
68
68
  def load_widget(self) -> ExampleTreeWidget:
@@ -289,6 +289,14 @@ disable_error_code = [
289
289
  "union-attr", # chacking for started state before each operation if too expensive
290
290
  ]
291
291
  [[tool.mypy.overrides]]
292
+ module = "urwid.widget.constants"
293
+ disable_error_code = [
294
+ # False positive: plain-string `Literal`s (e.g. "relative") are reported as overlapping
295
+ # sibling `str`-based enums (`Align`/`VAlign`) in the size/alignment overloads, even though
296
+ # the underlying values are disjoint. The public API must accept both string and enum forms.
297
+ "overload-overlap",
298
+ ]
299
+ [[tool.mypy.overrides]]
292
300
  module = [ "gi.*", "serial.*", "trio.*" ]
293
301
  ignore_missing_imports = true
294
302
 
@@ -38,6 +38,7 @@ import sys
38
38
  import tempfile
39
39
  import typing
40
40
  from contextlib import suppress
41
+ from email.message import Message
41
42
 
42
43
  from urwid.str_util import calc_text_pos, calc_width, move_next_char
43
44
  from urwid.util import StoppingContext, get_encoding
@@ -264,7 +265,7 @@ class Screen(BaseScreen):
264
265
  self._started = False
265
266
 
266
267
  def set_input_timeouts(self, *args: typing.Any) -> None:
267
- pass
268
+ """Not supported for web display."""
268
269
 
269
270
  def _close_connection(self) -> None:
270
271
  if self.update_method == "polling child":
@@ -501,6 +502,12 @@ def is_web_request() -> bool:
501
502
  return "REQUEST_METHOD" in os.environ
502
503
 
503
504
 
505
+ def _request_charset() -> str:
506
+ content_type = Message()
507
+ content_type["content-type"] = os.environ.get("CONTENT_TYPE", "")
508
+ return content_type.get_content_charset() or "utf-8"
509
+
510
+
504
511
  def handle_short_request() -> bool:
505
512
  """
506
513
  Handle short requests such as passing keystrokes to the application
@@ -541,16 +548,23 @@ def handle_short_request() -> bool:
541
548
 
542
549
  if os.environ.get("HTTP_X_URWID_METHOD", None) == "polling":
543
550
  # this is a screen update request
544
- s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
545
551
  try:
546
- s.connect(os.path.join(_prefs.pipe_dir, f"urwid_{urwid_id}.update"))
547
- data = f"Content-type: text/plain\r\n\r\n{s.recv(BUF_SZ).decode('utf-8')}"
548
- while data:
549
- sys.stdout.write(data)
550
- data = s.recv(BUF_SZ).decode("utf-8")
552
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
553
+ s.connect(os.path.join(_prefs.pipe_dir, f"urwid_{urwid_id}.update"))
554
+ chunks = []
555
+ while data := s.recv(BUF_SZ):
556
+ chunks.append(data)
551
557
  except OSError:
552
558
  sys.stdout.write("Status: 404 Not Found\r\n\r\n")
553
559
  return True
560
+
561
+ try:
562
+ decoded = b"".join(chunks).decode("utf-8")
563
+ except UnicodeDecodeError:
564
+ sys.stdout.write("Status: 502 Bad Gateway\r\n\r\n")
565
+ return True
566
+
567
+ sys.stdout.write(f"Content-type: text/plain; charset=utf-8\r\n\r\n{decoded}")
554
568
  return True
555
569
 
556
570
  # this is a keyboard input request
@@ -560,10 +574,17 @@ def handle_short_request() -> bool:
560
574
  sys.stdout.write("Status: 404 Not Found\r\n\r\n")
561
575
  return True
562
576
 
563
- # FIXME: use the correct encoding based on the request
564
- keydata = sys.stdin.read(MAX_READ)
565
- os.write(fd, keydata.encode("ascii"))
566
- os.close(fd)
577
+ try:
578
+ try:
579
+ keydata = sys.stdin.read(MAX_READ)
580
+ encoded_keydata = keydata.encode(_request_charset())
581
+ except (LookupError, UnicodeError):
582
+ sys.stdout.write("Status: 400 Bad Request\r\n\r\n")
583
+ return True
584
+ os.write(fd, encoded_keydata)
585
+ finally:
586
+ with suppress(OSError):
587
+ os.close(fd)
567
588
  sys.stdout.write("Content-type: text/plain\r\n\r\n")
568
589
 
569
590
  return True
@@ -42,7 +42,7 @@ from .select_loop import SelectEventLoop
42
42
  if typing.TYPE_CHECKING:
43
43
  from collections.abc import Callable, Iterable
44
44
 
45
- from typing_extensions import Self
45
+ from typing_extensions import Literal, Self
46
46
 
47
47
  from urwid.display import BaseScreen
48
48
  from urwid.widget import AbstractWidget
@@ -51,6 +51,37 @@ if typing.TYPE_CHECKING:
51
51
 
52
52
  _T = typing.TypeVar("_T")
53
53
 
54
+ class _ExternalLoopScreen(typing.Protocol):
55
+ """Screen able to drive an external event loop (e.g. raw_display / curses screens).
56
+
57
+ These members are not part of :class:`BaseScreen`;
58
+ :class:`MainLoop` only uses them after guarding with ``hasattr``
59
+ (or after :meth:`MainLoop.start` raised :exc:`CantUseExternalLoop`),
60
+ so accessing them through this protocol via ``cast`` is safe.
61
+ """
62
+
63
+ signal_handler_setter: Callable[..., typing.Any]
64
+
65
+ def hook_event_loop(
66
+ self,
67
+ event_loop: EventLoop,
68
+ callback: Callable[[list[str | tuple[str, int, int, int]], list[int]], typing.Any],
69
+ ) -> None: ...
70
+
71
+ def unhook_event_loop(self, event_loop: EventLoop) -> None: ...
72
+
73
+ def get_input(self, raw_keys: Literal[True]) -> tuple[list[str | tuple[str, int, int, int]], list[int]]: ...
74
+
75
+ def set_input_timeouts(self, max_wait: float | None = ...) -> None: ...
76
+
77
+ class _EventLoopWithAlarmsStore(typing.Protocol):
78
+ """Event loop that supports alarms storage (like :class:`SelectEventLoop`).
79
+
80
+ Not all event loops support alarms storage.
81
+ """
82
+
83
+ _alarms: list[tuple[float, int, Callable[[], typing.Any]]]
84
+
54
85
 
55
86
  IS_WINDOWS = sys.platform == "win32"
56
87
  PIPE_BUFFER_READ_SIZE = 4096 # can expect this much on Linux, so try for that
@@ -116,7 +147,9 @@ class MainLoop:
116
147
  ] = (),
117
148
  screen: BaseScreen | None = None,
118
149
  handle_mouse: bool = True,
119
- input_filter: Callable[[list[str | tuple[str, int, int, int]], list[int]], list[str]] | None = None,
150
+ input_filter: (
151
+ Callable[[list[str | tuple[str, int, int, int]], list[int]], list[str | tuple[str, int, int, int]]] | None
152
+ ) = None,
120
153
  unhandled_input: Callable[[str | tuple[str, int, int, int]], bool | None] | None = None,
121
154
  event_loop: EventLoop | None = None,
122
155
  pop_ups: bool = False,
@@ -149,7 +182,7 @@ class MainLoop:
149
182
  if hasattr(self.screen, "signal_handler_setter"):
150
183
  # Tell the screen what function it must use to set
151
184
  # signal handlers
152
- self.screen.signal_handler_setter = self.event_loop.set_signal_handler
185
+ typing.cast("_ExternalLoopScreen", self.screen).signal_handler_setter = self.event_loop.set_signal_handler
153
186
 
154
187
  self._watch_pipes: dict[int, tuple[Callable[[], typing.Any], int]] = {}
155
188
 
@@ -164,7 +197,7 @@ class MainLoop:
164
197
  @widget.setter
165
198
  def widget(self, widget: AbstractWidget) -> None:
166
199
  self._widget = widget
167
- if self.pop_ups:
200
+ if self.pop_ups and hasattr(self._topmost_widget, "original_widget"):
168
201
  self._topmost_widget.original_widget = self._widget
169
202
  else:
170
203
  self._topmost_widget = self._widget
@@ -181,7 +214,12 @@ class MainLoop:
181
214
  else:
182
215
  self._topmost_widget = self._widget
183
216
 
184
- def set_alarm_in(self, sec: float, callback: Callable[[Self, _T], typing.Any], user_data: _T = None):
217
+ def set_alarm_in(
218
+ self,
219
+ sec: float,
220
+ callback: Callable[[Self, _T | None], typing.Any],
221
+ user_data: _T | None = None,
222
+ ) -> typing.Any:
185
223
  """
186
224
  Schedule an alarm in *sec* seconds that will call *callback* from the
187
225
  within the :meth:`run` method.
@@ -201,7 +239,12 @@ class MainLoop:
201
239
 
202
240
  return self.event_loop.alarm(sec, cb)
203
241
 
204
- def set_alarm_at(self, tm: float, callback: Callable[[Self, _T], typing.Any], user_data: _T = None):
242
+ def set_alarm_at(
243
+ self,
244
+ tm: float,
245
+ callback: Callable[[Self, _T | None], typing.Any],
246
+ user_data: _T | None = None,
247
+ ) -> typing.Any:
205
248
  """
206
249
  Schedule an alarm at *tm* time that will call *callback* from the
207
250
  within the :meth:`run` function. Returns a handle that may be passed to
@@ -223,7 +266,7 @@ class MainLoop:
223
266
 
224
267
  return self.event_loop.alarm(sec, cb)
225
268
 
226
- def remove_alarm(self, handle) -> bool:
269
+ def remove_alarm(self, handle: typing.Any) -> bool:
227
270
  """
228
271
  Remove an alarm. Return ``True`` if *handle* was found, ``False``
229
272
  otherwise.
@@ -289,7 +332,7 @@ class MainLoop:
289
332
  os.close(pipe_rd)
290
333
  return True
291
334
 
292
- def watch_file(self, fd: int, callback: Callable[[], typing.Any]):
335
+ def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> typing.Any:
293
336
  """
294
337
  Call *callback* when *fd* has some data to read. No parameters are
295
338
  passed to callback.
@@ -299,7 +342,7 @@ class MainLoop:
299
342
  self.logger.debug(f"Setting watch file descriptor {fd!r} with {callback!r}")
300
343
  return self.event_loop.watch_file(fd, callback)
301
344
 
302
- def remove_watch_file(self, handle) -> bool:
345
+ def remove_watch_file(self, handle: typing.Any) -> bool:
303
346
  """
304
347
  Remove a watch file. Returns ``True`` if the watch file
305
348
  exists, ``False`` otherwise.
@@ -399,13 +442,14 @@ class MainLoop:
399
442
  self.event_loop.remove_enter_idle(self.idle_handle)
400
443
  del self.idle_handle
401
444
  signals.disconnect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, self._reset_input_descriptors)
402
- self.screen.unhook_event_loop(self.event_loop)
445
+ typing.cast("_ExternalLoopScreen", self.screen).unhook_event_loop(self.event_loop)
403
446
 
404
447
  self.screen.stop()
405
448
 
406
449
  def _reset_input_descriptors(self) -> None:
407
- self.screen.unhook_event_loop(self.event_loop)
408
- self.screen.hook_event_loop(self.event_loop, self._update)
450
+ screen = typing.cast("_ExternalLoopScreen", self.screen)
451
+ screen.unhook_event_loop(self.event_loop)
452
+ screen.hook_event_loop(self.event_loop, self._update)
409
453
 
410
454
  def _run(self) -> None:
411
455
  try:
@@ -456,23 +500,26 @@ class MainLoop:
456
500
  # pylint: disable=protected-access # special case for alarms handling
457
501
  self.logger.debug(f"Starting screen {self.screen!r} event loop")
458
502
 
459
- next_alarm = None
503
+ screen = typing.cast("_ExternalLoopScreen", self.screen)
504
+ event_loop = typing.cast("_EventLoopWithAlarmsStore", self.event_loop)
505
+
506
+ next_alarm: tuple[float, int, Callable[[], typing.Any]] | None = None
460
507
 
461
508
  while True:
462
509
  self.draw_screen()
463
510
 
464
- if not next_alarm and self.event_loop._alarms:
465
- next_alarm = heapq.heappop(self.event_loop._alarms)
511
+ if not next_alarm and event_loop._alarms:
512
+ next_alarm = heapq.heappop(event_loop._alarms)
466
513
 
467
514
  keys: list[str | tuple[str, int, int, int]] = []
468
515
  raw: list[int] = []
469
516
  while not keys:
470
517
  if next_alarm:
471
518
  sec = max(0.0, next_alarm[0] - time.time())
472
- self.screen.set_input_timeouts(sec)
519
+ screen.set_input_timeouts(sec)
473
520
  else:
474
- self.screen.set_input_timeouts(None)
475
- keys, raw = self.screen.get_input(True)
521
+ screen.set_input_timeouts(None)
522
+ keys, raw = screen.get_input(True)
476
523
  if not keys and next_alarm and next_alarm[0] - time.time() <= 0:
477
524
  break
478
525
 
@@ -485,8 +532,8 @@ class MainLoop:
485
532
  _tm, _tie_break, callback = next_alarm
486
533
  callback()
487
534
 
488
- if self.event_loop._alarms:
489
- next_alarm = heapq.heappop(self.event_loop._alarms)
535
+ if event_loop._alarms:
536
+ next_alarm = heapq.heappop(event_loop._alarms)
490
537
  else:
491
538
  next_alarm = None
492
539
 
@@ -55,7 +55,7 @@ class MetaSignals(abc.ABCMeta):
55
55
  def setdefaultattr(obj: typing.Any, name: str, value: _T) -> _T:
56
56
  # like dict.setdefault() for object attributes
57
57
  if hasattr(obj, name):
58
- return getattr(obj, name) # type: ignore[no-any-return]
58
+ return getattr(obj, name)
59
59
  setattr(obj, name, value)
60
60
  return value
61
61
 
@@ -69,6 +69,15 @@ class Key:
69
69
  __slots__ = ()
70
70
 
71
71
 
72
+ if typing.TYPE_CHECKING:
73
+ # ``weak_args`` (converted to weakrefs) and ``user_args`` as prepared by ``_prepare_user_args``.
74
+ _UserArgs = tuple[Collection[weakref.ReferenceType], Collection[typing.Any]]
75
+ # A single connected handler: (key, callback, deprecated user_arg, prepared args).
76
+ _SignalHandler = tuple[Key, Callable[..., typing.Any], typing.Any, _UserArgs]
77
+ # Per-sender storage attached to ``obj`` under ``Signals._signal_attr``.
78
+ _SignalStore = dict[Hashable, list[_SignalHandler]]
79
+
80
+
72
81
  class Signals:
73
82
  _signal_attr = "_urwid_signals" # attribute to attach to signal senders
74
83
 
@@ -89,7 +98,7 @@ class Signals:
89
98
 
90
99
  def connect(
91
100
  self,
92
- obj,
101
+ obj: typing.Any,
93
102
  name: Hashable,
94
103
  callback: Callable[..., typing.Any],
95
104
  user_arg: typing.Any = None,
@@ -180,7 +189,8 @@ class Signals:
180
189
  # Just generate an arbitrary (but unique) key
181
190
  key = Key()
182
191
 
183
- handlers = setdefaultattr(obj, self._signal_attr, {}).setdefault(name, [])
192
+ signals: _SignalStore = setdefaultattr(obj, self._signal_attr, {})
193
+ handlers = signals.setdefault(name, [])
184
194
 
185
195
  # Remove the signal handler when any of the weakref'd arguments
186
196
  # are garbage collected. Note that this means that the handlers
@@ -216,7 +226,7 @@ class Signals:
216
226
 
217
227
  def disconnect(
218
228
  self,
219
- obj,
229
+ obj: typing.Any,
220
230
  name: Hashable,
221
231
  callback: Callable[..., typing.Any],
222
232
  user_arg: typing.Any = None,
@@ -242,7 +252,7 @@ class Signals:
242
252
  If the callback is not connected or already disconnected, this
243
253
  function will simply do nothing.
244
254
  """
245
- signals = setdefaultattr(obj, self._signal_attr, {})
255
+ signals: _SignalStore = setdefaultattr(obj, self._signal_attr, {})
246
256
  if name not in signals:
247
257
  return None
248
258
 
@@ -258,7 +268,7 @@ class Signals:
258
268
  return self.disconnect_by_key(obj, name, h[0])
259
269
  return None
260
270
 
261
- def disconnect_by_key(self, obj, name: Hashable, key: Key) -> None:
271
+ def disconnect_by_key(self, obj: typing.Any, name: Hashable, key: Key) -> None:
262
272
  """
263
273
  :param obj: the object to disconnect the signal from
264
274
  :type obj: object
@@ -275,10 +285,11 @@ class Signals:
275
285
  If the callback is not connected or already disconnected, this
276
286
  function will simply do nothing.
277
287
  """
278
- handlers = setdefaultattr(obj, self._signal_attr, {}).get(name, [])
288
+ signals: _SignalStore = setdefaultattr(obj, self._signal_attr, {})
289
+ handlers = signals.get(name, [])
279
290
  handlers[:] = [h for h in handlers if h[0] is not key]
280
291
 
281
- def emit(self, obj, name: Hashable, *args: typing.Any) -> bool:
292
+ def emit(self, obj: typing.Any, name: Hashable, *args: typing.Any) -> bool:
282
293
  """
283
294
  :param obj: the object sending a signal
284
295
  :type obj: object
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '4.0.6'
22
- __version_tuple__ = version_tuple = (4, 0, 6)
21
+ __version__ = version = '4.0.7'
22
+ __version_tuple__ = version_tuple = (4, 0, 7)
23
23
 
24
- __commit_id__ = commit_id = 'g046c400ea'
24
+ __commit_id__ = commit_id = 'g7c32ae3ff'
@@ -246,14 +246,17 @@ def normalize_width(
246
246
  if width doesn't match a valid alignment.
247
247
  """
248
248
  if width in {WHSettings.CLIP, WHSettings.PACK}:
249
- return (WHSettings(width), None)
249
+ return (typing.cast("Literal[WHSettings.CLIP, WHSettings.PACK]", WHSettings(width)), None)
250
250
 
251
251
  if isinstance(width, int):
252
252
  return (WHSettings.GIVEN, width)
253
253
 
254
254
  if isinstance(width, tuple) and len(width) == 2 and width[0] in {WHSettings.RELATIVE, WHSettings.WEIGHT}:
255
255
  width_type, width_amount = width
256
- return (WHSettings(width_type), width_amount)
256
+ return typing.cast(
257
+ "tuple[Literal[WHSettings.RELATIVE], int] | tuple[Literal[WHSettings.WEIGHT], int | float]",
258
+ (WHSettings(width_type), width_amount),
259
+ )
257
260
 
258
261
  raise err(
259
262
  f"width value {width!r} is not one of"
@@ -310,7 +313,7 @@ def simplify_width(
310
313
  Inverse of normalize_width.
311
314
  """
312
315
  if width_type in {WHSettings.CLIP, WHSettings.PACK}:
313
- return WHSettings(width_type)
316
+ return typing.cast("Literal[WHSettings.CLIP, WHSettings.PACK]", WHSettings(width_type))
314
317
 
315
318
  if not isinstance(width_amount, int):
316
319
  raise TypeError(width_amount)
@@ -318,7 +321,10 @@ def simplify_width(
318
321
  if width_type == WHSettings.GIVEN:
319
322
  return width_amount
320
323
 
321
- return (WHSettings(width_type), width_amount)
324
+ return typing.cast(
325
+ "tuple[Literal[WHSettings.RELATIVE], int] | tuple[Literal[WHSettings.WEIGHT], int | float]",
326
+ (WHSettings(width_type), width_amount),
327
+ )
322
328
 
323
329
 
324
330
  @typing.overload
@@ -373,7 +379,10 @@ def normalize_height(
373
379
  return (WHSettings.PACK, None)
374
380
 
375
381
  if isinstance(height, tuple) and len(height) == 2 and height[0] in {WHSettings.RELATIVE, WHSettings.WEIGHT}:
376
- return (WHSettings(height[0]), height[1])
382
+ return typing.cast(
383
+ "tuple[Literal[WHSettings.RELATIVE], int] | tuple[Literal[WHSettings.WEIGHT], int | float]",
384
+ (WHSettings(height[0]), height[1]),
385
+ )
377
386
 
378
387
  if isinstance(height, int):
379
388
  return (WHSettings.GIVEN, height)
@@ -444,7 +453,7 @@ def simplify_height(
444
453
  Inverse of normalize_height.
445
454
  """
446
455
  if height_type in {WHSettings.FLOW, WHSettings.PACK}:
447
- return WHSettings(height_type)
456
+ return typing.cast("Literal[WHSettings.FLOW, WHSettings.PACK]", WHSettings(height_type))
448
457
 
449
458
  if not isinstance(height_amount, int):
450
459
  raise TypeError(height_amount)
@@ -452,7 +461,10 @@ def simplify_height(
452
461
  if height_type == WHSettings.GIVEN:
453
462
  return height_amount
454
463
 
455
- return (WHSettings(height_type), height_amount)
464
+ return typing.cast(
465
+ "tuple[Literal[WHSettings.RELATIVE], int] | tuple[Literal[WHSettings.WEIGHT], int | float]",
466
+ (WHSettings(height_type), height_amount),
467
+ )
456
468
 
457
469
 
458
470
  @dataclasses.dataclass(frozen=True)
@@ -342,7 +342,7 @@ class Filler(WidgetDecoration[WrappedWidget]):
342
342
  def calculate_top_bottom_filler(
343
343
  maxrow: int,
344
344
  valign_type: Literal["top", "middle", "bottom", "relative", WHSettings.RELATIVE] | VAlign,
345
- valign_amount: int,
345
+ valign_amount: int | None,
346
346
  height_type: Literal["given", "relative", "clip", WHSettings.GIVEN, WHSettings.RELATIVE, WHSettings.CLIP],
347
347
  height_amount: int,
348
348
  min_height: int | None,
@@ -380,6 +380,9 @@ def calculate_top_bottom_filler(
380
380
  >>> ctbf(20, "relative", 30, "relative", 60, 14, 0, 0)
381
381
  (2, 4)
382
382
  """
383
+ if valign_type == WHSettings.RELATIVE and valign_amount is None:
384
+ raise TypeError("valign_amount must be specified when valign_type is relative")
385
+
383
386
  if height_type == WHSettings.RELATIVE:
384
387
  maxheight = max(maxrow - top - bottom, 0)
385
388
  height = int_scale(height_amount, 101, maxheight + 1)
@@ -388,7 +391,9 @@ def calculate_top_bottom_filler(
388
391
  else:
389
392
  height = height_amount
390
393
 
391
- valign = {VAlign.TOP: 0, VAlign.MIDDLE: 50, VAlign.BOTTOM: 100}.get(valign_type, valign_amount)
394
+ valign: int = {VAlign.TOP: 0, VAlign.MIDDLE: 50, VAlign.BOTTOM: 100}.get( # type: ignore[assignment]
395
+ valign_type, valign_amount
396
+ ) # relative + None already filtered above
392
397
 
393
398
  # add the remainder of top/bottom to the filler
394
399
  filler = maxrow - height - top - bottom