9 class RecvThread(threading.Thread):
10 """Background thread that delivers messages from the socket to urwid.
12 The message transfer to urwid is a bit weird. The urwid developers warn
13 against sharing urwid resources among threads, and recommend using urwid's
14 watch_pipe mechanism: using a pipe from non-urwid threads into a single
15 urwid thread. We could pipe the recv output directly, but then we get
16 complicated buffering situations here as well as in the urwid code that
17 receives the pipe content. It's much easier to update a third resource
18 (server_output, which references an object that's also known to the urwid
19 code) to contain the new message, and then just use the urwid pipe
20 (urwid_pipe_write_fd) to trigger the urwid code to pull the message in from
21 that third resource. We send a single b' ' through the pipe to trigger it.
24 def __init__(self, socket, urwid_pipe_write_fd, server_output):
27 self.urwid_pipe = urwid_pipe_write_fd
28 self.server_output = server_output
31 """On message receive, write to self.server_output, ping urwid pipe."""
33 for msg in plom_socket_io.recv(self.socket):
34 self.server_output[0] = msg
35 os.write(self.urwid_pipe, b' ')
39 """Helps delivering data from other thread to widget via message_container.
41 The whole class only exists to provide handle_input as a bound method, with
42 widget and message_container pre-set, as (bound) handle_input is used as a
43 callback in urwid's watch_pipe – which merely provides its callback target
44 with one parameter for a pipe to read data from an urwid-external thread.
47 def __init__(self, widget, message_container):
49 self.message_container = message_container
51 def handle_input(self, trigger):
52 """On input from other thread, either quit, or write to widget text.
54 Serves as a receiver to urwid's watch_pipe mechanism, with trigger the
55 data that a pipe defined by watch_pipe delivers. To avoid buffering
56 trouble, we don't care for that data beyond the fact that its receival
57 triggers this function: The sender is to write the data it wants to
58 deliver into the container referenced by self.message_container, and
59 just pipe the trigger to inform us about this.
61 If the message delivered is 'BYE', quits Urbit.
63 if self.message_container[0] == 'BYE':
64 raise urwid.ExitMainLoop()
66 self.widget.set_text('REPLY: ' + self.message_container[0])
69 class SocketInputWidget(urwid.Filler):
71 def __init__(self, socket, *args, **kwargs):
72 super().__init__(*args, **kwargs)
75 def keypress(self, size, key):
76 """Act like super(), except on Enter: send .edit_text, and empty it."""
78 return super().keypress(size, key)
79 plom_socket_io.send(self.socket, edit.edit_text)
83 s = socket.create_connection(('127.0.0.1', 5000))
85 edit = urwid.Edit('SEND: ')
87 pile = urwid.Pile([edit, txt])
88 fill = SocketInputWidget(s, pile)
89 loop = urwid.MainLoop(fill)
92 write_fd = loop.watch_pipe(getattr(InputHandler(txt, server_output),
94 thread = RecvThread(s, write_fd, server_output)