From c609ca998a3b8f78f95c7247e768770152236711 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 21 Dec 2020 16:04:01 +0100 Subject: [PATCH] Uses curses to have a proper terminal UI Signed-off-by: Yohann D'ANELLO --- squinnondation/squinnondation.py | 65 ++++++++++++++++++++++---------- squinnondation/term_manager.py | 38 +++++++++++++++++++ 2 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 squinnondation/term_manager.py diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index b9a3384..d14ad67 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -1,6 +1,7 @@ # Copyright (C) 2020 by eichhornchen, ÿnérant # SPDX-License-Identifier: GPL-3.0-or-later +import curses import socket from argparse import ArgumentParser from enum import Enum @@ -8,6 +9,8 @@ from ipaddress import IPv6Address from threading import Thread from typing import Any, List, Optional, Tuple +from squinnondation.term_manager import TermManager + class Squinnondation: args: Any @@ -43,28 +46,48 @@ class Squinnondation: instance = Squinnondation() instance.parse_arguments() - squirrel = Squirrel(input("Enter your nickname: "), instance.bind_address, instance.bind_port) + with TermManager() as term_manager: + screen = term_manager.screen + screen.addstr(0, 0, "Enter your nickname: ") + nickname = screen.getstr().decode("UTF-8") + squirrel = Squirrel(nickname, instance.bind_address, instance.bind_port) - if not instance.args.bind_only: - hazelnut = Hazelnut(address=instance.client_address, port=instance.client_port) - squirrel.hazelnuts[(instance.client_address, instance.client_port)] = hazelnut + if not instance.args.bind_only: + hazelnut = Hazelnut(address=instance.client_address, port=instance.client_port) + squirrel.hazelnuts[(instance.client_address, instance.client_port)] = hazelnut - Worm(squirrel).start() + squirrel.history = [] - while True: - msg = f"<{squirrel.nickname}> {input(f'<{squirrel.nickname}> ')}" - for hazelnut in list(squirrel.hazelnuts.values()): - pkt = Packet() - pkt.magic = 95 - pkt.version = 0 - tlv = DataTLV() - tlv.data = msg.encode("UTF-8") - tlv.sender_id = 42 - tlv.nonce = 18 - tlv.length = len(msg) + 1 + 1 + 8 + 4 - pkt.body = [tlv] - pkt.body_length = tlv.length + 2 - squirrel.send_packet(hazelnut, pkt) + def refresh() -> None: + screen.clear() + screen.refresh() + for i, msg in enumerate(squirrel.history[max(0, len(squirrel.history) - curses.LINES + 2):]): + screen.addstr(i, 0, msg) + + screen.addstr(curses.LINES - 1, 0, f"<{squirrel.nickname}> ") + screen.refresh() + squirrel.refresh = refresh + + Worm(squirrel).start() + + while True: + refresh() + msg = screen.getstr().decode("UTF-8") + msg = f"<{squirrel.nickname}> {msg}" + squirrel.history.append(msg) + + for hazelnut in list(squirrel.hazelnuts.values()): + pkt = Packet() + pkt.magic = 95 + pkt.version = 0 + tlv = DataTLV() + tlv.data = msg.encode("UTF-8") + tlv.sender_id = 42 + tlv.nonce = 18 + tlv.length = len(tlv.data) + 1 + 1 + 8 + 4 + pkt.body = [tlv] + pkt.body_length = tlv.length + 2 + squirrel.send_packet(hazelnut, pkt) class TLV: @@ -415,10 +438,12 @@ class Worm(Thread): self.squirrel = squirrel def run(self) -> None: + self.squirrel.history = [] while True: try: pkt, hazelnut = self.squirrel.receive_packet() except ValueError as error: print("An error occured while receiving a packet: ", error) else: - print(pkt.body[0].data.decode('UTF-8')) + self.squirrel.history.append(pkt.body[0].data.decode('UTF-8')) + self.squirrel.refresh() diff --git a/squinnondation/term_manager.py b/squinnondation/term_manager.py new file mode 100644 index 0000000..1005666 --- /dev/null +++ b/squinnondation/term_manager.py @@ -0,0 +1,38 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + +import curses +from types import TracebackType + + +class TermManager: # pragma: no cover + """ + The TermManager object initializes the terminal, returns a screen object and + de-initializes the terminal after use + """ + def __init__(self): + self.screen = curses.initscr() + # convert escapes sequences to curses abstraction + self.screen.keypad(True) + # stop printing typed keys to the terminal + # curses.noecho() + # send keys through without having to press + # curses.cbreak() + # make cursor invisible + # curses.curs_set(False) + # Catch mouse events + curses.mousemask(True) + # Enable colors + curses.start_color() + + def __enter__(self): + return self + + def __exit__(self, exc_type: type, exc_value: Exception, + exc_traceback: TracebackType) -> None: + # restore the terminal to its original state + self.screen.keypad(False) + # curses.echo() + # curses.nocbreak() + # curses.curs_set(True) + curses.endwin()