From bf645b0b89428dca7dee1e7296afe7e5d1d24c96 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 7 Dec 2023 17:56:23 +0000 Subject: [PATCH] Switch to using Unicode input. I failed to notice that getch() delivers you single bytes in trad fashion. Switched to get_wch(), which returns either a Python char (that is, a length-1 str) or an integer containing a curses key code. Also it throws an exception instead of returning curses.ERR, which I've dealt with by the simplest possible method of turning it back into curses.ERR. (The exception in this case is almost always 'stdin is nonblocking and there's nothing to see here yet'.) --- cursesclient.py | 49 +++++++++++++++++++++++++++++-------------------- util.py | 3 +++ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/cursesclient.py b/cursesclient.py index 1c02cbc..ad769f2 100644 --- a/cursesclient.py +++ b/cursesclient.py @@ -10,6 +10,8 @@ import client import text import util +from util import ctrl + class CursesUI(client.Client): def __init__(self): super().__init__() @@ -43,7 +45,7 @@ class CursesUI(client.Client): self.scr.keypad(1) self.scr.scrollok(1) - self.scr.nodelay(1) # so we can use getch() via a select loop + self.scr.nodelay(1) # so we can use get_wch() via a select loop curses.noecho() self.scr_h, self.scr_w = self.scr.getmaxyx() @@ -93,11 +95,18 @@ class CursesUI(client.Client): for y in range(self.scr_h): self.print_at(y, 0, text.ColouredString(' ' * self.scr_w)) + def get_wch(self): + try: + ch = self.scr.get_wch() + except curses.error: + return curses.ERR + return ch + def get_input(self): # There might be keystrokes still in curses's buffer from a # previous read from stdin, so deal with those before we go # back to the select loop - ch = self.scr.getch() + ch = self.get_wch() if ch != curses.ERR: return ch @@ -110,7 +119,7 @@ class CursesUI(client.Client): if sp.rfd in rfds_out and sp.check(): handler() if 0 in rfds_out: - return self.scr.getch() + return self.get_wch() else: return None @@ -135,7 +144,7 @@ class CursesUI(client.Client): # This is the same way standard curses will do it, which # is as good a way as any to signal to the main loop that # we need to do something - curses.ungetch(curses.KEY_RESIZE) + curses.unget_wch(curses.KEY_RESIZE) self.selfpipes.append((sp, handler, None)) def run(self): @@ -190,7 +199,7 @@ class CursesUI(client.Client): self.activity_stack[-1].render() self.scr.refresh() ch = self.get_input() - if ch == 27: + if ch == ctrl('['): if self.activity_stack[-1] is not self.escape_menu: self.activity_stack.append(self.escape_menu) elif ch == curses.KEY_RESIZE: @@ -263,7 +272,7 @@ class Menu: self.cc.activity_stack.append(activity) def handle_key(self, ch): - if ch in {ord('q'), ord('Q'), 10, 13}: + if ch in {'q', 'Q', '\n', '\r'}: return 'quit' class MainMenu(Menu): @@ -286,7 +295,7 @@ class MainMenu(Menu): pass def handle_key(self, ch): - if ch in {ord('h'), ord('H')}: + if ch in {'h', 'H'}: self.push_to(self.cc.home_timeline) else: return super().handle_key(ch) @@ -306,13 +315,13 @@ class EscMenu(Menu): " K "))) def handle_key(self, ch): - if ch in {ord('r'), ord('R')}: + if ch in {'r', 'R'}: self.chain_to(self.cc.mentions_timeline) - elif ch in {ord('l'), ord('L')}: + elif ch in {'l', 'L'}: self.chain_to(self.cc.log_menu) - elif ch in {ord('x'), ord('X')}: + elif ch in {'x', 'X'}: self.chain_to(self.cc.exit_menu) - elif ch in {ord('g'), ord('G')}: + elif ch in {'g', 'G'}: self.cc.activity_stack[:] = [self.cc.main_menu] else: return super().handle_key(ch) @@ -328,7 +337,7 @@ class LogMenu(Menu): " K "))) def handle_key(self, ch): - if ch in {ord('l'), ord('L')}: + if ch in {'l', 'L'}: self.chain_to(self.cc.log_menu_2) else: return super().handle_key(ch) @@ -344,7 +353,7 @@ class LogMenu2(Menu): "K "))) def handle_key(self, ch): - if ch in {ord('e'), ord('E')}: + if ch in {'e', 'E'}: self.chain_to(self.cc.ego_timeline) else: return super().handle_key(ch) @@ -360,7 +369,7 @@ class ExitMenu(Menu): " K "))) def handle_key(self, ch): - if ch in {ord('x'), ord('X')}: + if ch in {'x', 'X'}: return 'exit' else: return super().handle_key(ch) @@ -371,22 +380,22 @@ class File: self.cc = cc def handle_key(self, ch): - if ch in {ord(' '), curses.KEY_NPAGE}: + if ch in {' ', curses.KEY_NPAGE}: self.down_screen() - elif ch in {ord('-'), ord('b'), ord('B'), curses.KEY_PPAGE}: + elif ch in {'-', 'b', 'B', curses.KEY_PPAGE}: self.up_screen() elif ch == curses.KEY_DOWN: self.down_line() - elif ch in {10, 13}: + elif ch in {'\n', '\r'}: if not self.down_line(): return 'quit' elif ch in {curses.KEY_UP}: self.up_line() - elif ch in {ord('0'), curses.KEY_HOME}: + elif ch in {'0', curses.KEY_HOME}: self.goto_top() - elif ch in {ord('z'), ord('Z'), curses.KEY_END}: + elif ch in {'z', 'Z', curses.KEY_END}: self.goto_bottom() - elif ch in {ord('q'), ord('Q')}: + elif ch in {'q', 'Q'}: return 'quit' class ObjectFile(File): diff --git a/util.py b/util.py index 6114807..d5aeeca 100644 --- a/util.py +++ b/util.py @@ -1,5 +1,8 @@ import os +def ctrl(ch): + return chr(0x1F & ord(ch)) + class SelfPipe: def __init__(self): self.rfd, self.wfd = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC) -- 2.30.2