-
Notifications
You must be signed in to change notification settings - Fork 324
Closed
Labels
FeatureFeature request/implementationFeature request/implementationWindowsWindows supportWindows supportproposal
Description
Description
I have managed to get partial support for urwid on Windows 10 and Python 3 using the windows-curses package. A few things don't work, but it works well enough for my needs.
What works:
- curses display
- most widgets
- most special keys
- console resizing
What doesn't work:
- unicode (including the default symbols for LineBox, etc.)
- mouse events
- high-colour modes (I don't think these are supported by windows-curses)
I don't know enough about the curses library or ANSI control sequences to debug further. I'm posting this here in the hopes that a) someone else finds it useful, and b) someone more knowledgeable can use it as a starting point to fix the remaining issues (especially unicode).
Patch
"""
Patch urwid on Windows and WSL.
On Windows, install the package "windows-curses".
"""
import curses
import platform
import re
import urwid
import urwid.curses_display
import urwid.raw_display
IS_WSL = ("Linux" in platform.platform()) and ("Microsoft" in platform.platform())
IS_WINDOWS = ("Windows" in platform.platform()) and ("Linux" not in platform.platform())
if IS_WSL:
# Filter out SI/SO under WSL. See:
# https://github.com/mitmproxy/mitmproxy/blob/8dfb8a9a7471e00b0f723de66d3b63bd089e9802/mitmproxy/tools/console/window.py#L316-L323
wsl_patch_orig_write = urwid.raw_display.Screen.write
wsl_patch_write_re = re.compile("[\x0e\x0f]")
def wsl_patch_write(self, data):
data = wsl_patch_write_re.sub("", data)
return wsl_patch_orig_write(self, data)
urwid.raw_display.Screen.write = wsl_patch_write
if IS_WINDOWS:
def win_patch_tty_signal_keys(self, intr=None, quit=None, start=None, stop=None, susp=None, fileno=None):
pass # no signals on Windows; not needed for resize
urwid.display_common.RealTerminal.tty_signal_keys = win_patch_tty_signal_keys
win_patch_orig__start = urwid.curses_display.Screen._start
def win_patch__start(self):
win_patch_orig__start(self)
# halfdelay() seems unnecessary and causes everything to slow down a lot.
curses.nocbreak() # exits halfdelay mode
# keypad(1) is needed, or we get no special keys (cursor keys, etc.)
self.s.keypad(1)
urwid.curses_display.Screen._start = win_patch__start
# The getch functions in urwid.curses_display really don't work well on Windows
# for some reason. You end up with >1s delays on input. It looks like halfdelay()
# might work differently under windows-curses (which wraps PDCurses), or possibly
# not support delays and blocking/non-blocking modes exactly correctly. These rewrites
# simplify urwid's implementation a lot: they ignore most of urwid's delay handling,
# but they seem to work ok.
def win_patch__getch(self, wait_tenths):
if wait_tenths == 0:
return self._getch_nodelay()
self.s.nodelay(False)
return self.s.getch()
urwid.curses_display.Screen._getch = win_patch__getch
def win_patch__getch_nodelay(self):
self.s.nodelay(True)
return self.s.getch()
urwid.curses_display.Screen._getch_nodelay = win_patch__getch_nodelay
# The Windows terminal uses a different colour numbering.
def win_patch__setup_colour_pairs(self):
if not self.has_color:
return
self.has_default_colors = False
colour_correct = [
curses.COLOR_BLACK,
curses.COLOR_RED,
curses.COLOR_GREEN,
curses.COLOR_YELLOW,
curses.COLOR_BLUE,
curses.COLOR_MAGENTA,
curses.COLOR_CYAN,
curses.COLOR_WHITE,
]
for fg in range(8):
for bg in range(8):
if fg == curses.COLOR_WHITE and \
bg == curses.COLOR_BLACK:
continue
curses.init_pair(bg * 8 + 7 - fg, colour_correct[fg], colour_correct[bg])
urwid.curses_display.Screen._setup_colour_pairs = win_patch__setup_colour_pairs
# Default to curses on Windows
win_patch_orig_main_loop___init__ = urwid.main_loop.MainLoop.__init__
def win_patch_main_loop__init__(self, widget, palette=(), screen=None,
handle_mouse=True, input_filter=None, unhandled_input=None,
event_loop=None, pop_ups=False):
if screen == None:
screen = urwid.curses_display.Screen()
win_patch_orig_main_loop___init__(self, widget, palette, screen,
handle_mouse, input_filter, unhandled_input, event_loop, pop_ups)
urwid.main_loop.MainLoop.__init__ = win_patch_main_loop__init__
# Some special keys seem to have different key codes under Windows.
urwid.escape._keyconv[351] = 'shift tab'
urwid.escape._keyconv[358] = 'end'
urwid.curses_display.KEY_RESIZE = 546
Usage
Save the above as urwid_win_patch.py
. To use it:
import urwid
import urwid_win_patch
# use urwid normally
For convenience, the above patch also incorporates the change from issue 264 when using Windows Subsystem for Linux.
Tested With
- Windows 10.0.18363.1256
- python 3.6.5, 32-bit
- windows-curses 2.2.0
- urwid 2.1.2
Discussion
- One obvious change was getting rid of signals. Windows doesn't support them and doesn't use them for windows resizing events; curses gives a RESIZEKEY code instead, which urwid already knows how to handle.
- Curses delays and timeouts seem horribly broken. It's possible this is a bug in the curses library on Windows. (The windows-curses Python package is a wrapper around the PDcurses C library.) Whatever the cause, timeouts don't seem to work correctly, and halfdelay() mode in particular seemed to cause a lot of problems. I don't know enough about curses programming to understand fully the consequences/subtleties of what the original curses_display code was trying to do. What I have done is a a hack, but it seems to work.
- The Windows console (or perhaps windows-curses) seems to enumerate the 8 standard colours in a different order. I've fixed that, above.
- Some of the special keys seem to have different key codes under curses-windows. I have tested the following: arrow keys, escape, backspace, delete, tab/shift-tab, home, end, and function keys. I did not test with all possible combinations of shift/ctrl/meta, so it's entirely possible I've missed some. But those allow for basic navigation between widgets. If you find other missing/different key codes you care about, add them to the
urwid.escape._keyconv
dictionary, above. - I originally took a look at the raw_display module, which would have allowed high-colour modes. That would require a lot more work to port to Windows - probably a rewrite using msvcrt.
- I'm not sure why unicode doesn't work: Python 3 and windows-curses do both support Unicode, and
curses.addstr(u"string with unicode characters like é and ├─┤")
does work with windows-curses, so it's possible this is an urwid bug (perhaps to do with default locales?), but that's speculation on my part.
Zireael07, mhils, clach04, adeak and daviewalesNikitaBeloglazov and aont
Metadata
Metadata
Assignees
Labels
FeatureFeature request/implementationFeature request/implementationWindowsWindows supportWindows supportproposal