chiark / gitweb /
Switch to using Unicode input.
authorSimon Tatham <anakin@pobox.com>
Thu, 7 Dec 2023 17:56:23 +0000 (17:56 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 7 Dec 2023 17:56:23 +0000 (17:56 +0000)
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
util.py

index 1c02cbc8461a1a1a483283b8a280779061c7e7b6..ad769f2a293ee8f032fa928f88faa4306900c14d 100644 (file)
@@ -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 611480796ac69b6c115fb0bba0ba7f0a65b00944..d5aeecae977481eca0e9143272935edee6314181 100644 (file)
--- 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)