From 00e24d74ee94dd8efe508f44abd2e3fb4f4fefd0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 21 Dec 2020 16:04:10 +0100 Subject: [PATCH] Cut the main loop into smaller functions to have more modularity Signed-off-by: Yohann D'ANELLO --- squinnondation/squinnondation.py | 236 ++++++++++++++++++------------- 1 file changed, 137 insertions(+), 99 deletions(-) diff --git a/squinnondation/squinnondation.py b/squinnondation/squinnondation.py index 23bb01c..0182ba7 100644 --- a/squinnondation/squinnondation.py +++ b/squinnondation/squinnondation.py @@ -73,102 +73,7 @@ class Squinnondation: squirrel.hazelnuts[(instance.args.client_address, instance.args.client_port)] = hazelnut Worm(squirrel).start() - - while True: - squirrel.refresh_history() - squirrel.refresh_input() - if not instance.no_emoji: - squirrel.refresh_emoji_pad() - key = screen.getkey(curses.LINES - 1, 3 + len(squirrel.nickname) + squirrel.input_index) - if key == "\x7f": # backspace - if squirrel.input_index: - squirrel.input_index -= 1 - squirrel.input_buffer = squirrel.input_buffer[:squirrel.input_index] \ - + squirrel.input_buffer[squirrel.input_index + 1:] - continue - elif key == "KEY_LEFT": - squirrel.input_index = max(0, squirrel.input_index - 1) - continue - elif key == "KEY_RIGHT": - squirrel.input_index = min(len(squirrel.input_buffer), squirrel.input_index + 1) - continue - elif key == "KEY_UP": - squirrel.last_line = min(max(curses.LINES - 3, squirrel.last_line - 1), len(squirrel.history) - 1) - continue - elif key == "KEY_DOWN": - squirrel.last_line = min(len(squirrel.history) - 1, squirrel.last_line + 1) - continue - elif key == "KEY_PPAGE": - squirrel.last_line = min(max(curses.LINES - 3, squirrel.last_line - (curses.LINES - 3)), - len(squirrel.history) - 1) - continue - elif key == "KEY_NPAGE": - squirrel.last_line = min(len(squirrel.history) - 1, squirrel.last_line + (curses.LINES - 3)) - continue - elif key == "KEY_HOME": - squirrel.input_index = 0 - continue - elif key == "KEY_END": - squirrel.input_index = len(squirrel.input_buffer) - continue - elif key == "KEY_RESIZE": - continue - elif key == "KEY_MOUSE": - try: - _, x, y, _, attr = curses.getmouse() - except curses.error: - # This is not a valid click - continue - if not instance.no_emoji: - if y == curses.LINES - 1 and x >= curses.COLS - 3: - # Click on the emoji - squirrel.emoji_panel_page *= -1 - elif squirrel.emoji_panel_page > 0 and y == curses.LINES - 4 and x >= curses.COLS - 5: - squirrel.emoji_panel_page += 1 - elif squirrel.emoji_panel_page > 1 and y == curses.LINES - curses.LINES // 2 - 1 \ - and x >= curses.COLS - 5: - squirrel.emoji_panel_page -= 1 - elif squirrel.emoji_panel_page > 0 and y >= curses.LINES // 2 - 1 and x >= curses.COLS // 2 - 1: - pad_y, pad_x = y - (curses.LINES - curses.LINES // 2) + 1,\ - (x - (curses.COLS - curses.COLS // 3)) // 2 - height, width = squirrel.emoji_pad.getmaxyx() - height -= 1 - width -= 1 - - emojis = list(unicode_codes.UNICODE_EMOJI) - emojis = [c for c in emojis if len(c) == 1] - size = (height - 2) * (width - 4) // 2 - page = emojis[(squirrel.emoji_panel_page - 1) * size:squirrel.emoji_panel_page * size] - index = pad_y * (width - 4) // 2 + pad_x - char = page[index] - if char: - demojized = emoji.demojize(char) - if char != demojized: - for c in reversed(demojized): - curses.ungetch(c) - continue - elif len(key) > 1: - squirrel.add_system_message(f"unmanaged key press: {key}") - continue - elif key != "\n": - squirrel.input_buffer = squirrel.input_buffer[:squirrel.input_index] + key \ - + squirrel.input_buffer[squirrel.input_index:] - squirrel.input_index += 1 - continue - - msg = squirrel.input_buffer - squirrel.input_buffer = "" - squirrel.input_index = 0 - - if not msg: - continue - - msg = f"<{squirrel.nickname}> {msg}" - squirrel.add_message(msg) - - for hazelnut in list(squirrel.hazelnuts.values()): - pkt = Packet.construct(DataTLV.construct(msg)) - squirrel.send_packet(hazelnut, pkt) + squirrel.wait_for_key() class TLV: @@ -238,8 +143,7 @@ class Pad1TLV(TLV): def handle(self, squirrel: "Squirrel", sender: "Hazelnut") -> None: # TODO Add some easter eggs - squirrel.add_system_message(f"For each byte in the packet that I received, you will die today. " - "And eat cookies.") + squirrel.add_system_message("For each byte in the packet that I received, you will die today. And eat cookies.") @property def tlv_length(self) -> int: @@ -312,7 +216,7 @@ class HelloTLV(TLV): + (":3_hearts:" if self.is_long else "smiling_eyes:")) @property - def is_long(self): + def is_long(self) -> bool: return self.length == 16 @@ -608,6 +512,140 @@ class Squirrel(Hazelnut): """ return self.socket.recvfrom(1024) + def wait_for_key(self) -> None: + """ + Infinite loop where we are waiting for a key of the user. + """ + while True: + self.refresh_history() + self.refresh_input() + if not self.squinnondation.no_emoji: + self.refresh_emoji_pad() + key = self.squinnondation.screen.getkey(curses.LINES - 1, 3 + len(self.nickname) + self.input_index) + + if key == "KEY_MOUSE": + try: + _, x, y, _, attr = curses.getmouse() + except curses.error: + # This is not a valid click + pass + else: + self.handle_mouse_click(y, x, attr) + finally: + continue + + self.handle_key_pressed(key) + + def handle_key_pressed(self, key: str) -> None: + """ + Process the key press from the user. + """ + if key == "\x7f": # backspace + # delete character at the good position + if self.input_index: + self.input_index -= 1 + self.input_buffer = self.input_buffer[:self.input_index] + self.input_buffer[self.input_index + 1:] + return + elif key == "KEY_LEFT": + # Navigate in the message to the left + self.input_index = max(0, self.input_index - 1) + return + elif key == "KEY_RIGHT": + # Navigate in the message to the right + self.input_index = min(len(self.input_buffer), self.input_index + 1) + return + elif key == "KEY_UP": + # Scroll up in the history + self.last_line = min(max(curses.LINES - 3, self.last_line - 1), len(self.history) - 1) + return + elif key == "KEY_DOWN": + # Scroll down in the history + self.last_line = min(len(self.history) - 1, self.last_line + 1) + return + elif key == "KEY_PPAGE": + # Page up in the history + self.last_line = min(max(curses.LINES - 3, self.last_line - (curses.LINES - 3)), len(self.history) - 1) + return + elif key == "KEY_NPAGE": + # Page down in the history + self.last_line = min(len(self.history) - 1, self.last_line + (curses.LINES - 3)) + return + elif key == "KEY_HOME": + # Place the cursor at the beginning of the typing word + self.input_index = 0 + return + elif key == "KEY_END": + # Place the cursor at the end of the typing word + self.input_index = len(self.input_buffer) + return + elif len(key) > 1: + # Unmanaged complex key + return + elif key != "\n": + # Insert the pressed key in the current message + self.input_buffer = self.input_buffer[:self.input_index] + key + self.input_buffer[self.input_index:] + self.input_index += 1 + return + + # Send message to neighbours + msg = self.input_buffer + self.input_buffer = "" + self.input_index = 0 + + if not msg: + return + + msg = f"<{self.nickname}> {msg}" + self.add_message(msg) + + pkt = Packet.construct(DataTLV.construct(msg)) + for hazelnut in list(self.hazelnuts.values()): + self.send_packet(hazelnut, pkt) + + def handle_mouse_click(self, y: int, x: int, attr: int) -> None: + """ + The user clicks on the screen, at coordinates (y, x). + According to the position, we can indicate what can be done. + """ + + if not self.squinnondation.no_emoji: + if y == curses.LINES - 1 and x >= curses.COLS - 3: + # Click on the emoji, open or close the emoji pad + self.emoji_panel_page *= -1 + elif self.emoji_panel_page > 0 and y == curses.LINES - 4 and x >= curses.COLS - 5: + # Open next emoji page + self.emoji_panel_page += 1 + elif self.emoji_panel_page > 1 and y == curses.LINES - curses.LINES // 2 - 1 \ + and x >= curses.COLS - 5: + # Open previous emoji page + self.emoji_panel_page -= 1 + elif self.emoji_panel_page > 0 and y >= curses.LINES // 2 - 1 and x >= curses.COLS // 2 - 1: + pad_y, pad_x = y - (curses.LINES - curses.LINES // 2) + 1, \ + (x - (curses.COLS - curses.COLS // 3) + 1) // 2 + # Click on an emoji on the pad to autocomplete an emoji + self.click_on_emoji_pad(pad_y, pad_x) + + def click_on_emoji_pad(self, pad_y: int, pad_x: int) -> None: + """ + The emoji pad contains the list of all available emojis. + Clicking on a emoji auto-complete the emoji in the input pad. + """ + height, width = self.emoji_pad.getmaxyx() + height -= 1 + width -= 1 + + emojis = list(unicode_codes.UNICODE_EMOJI) + emojis = [c for c in emojis if len(c) == 1] + size = (height - 2) * (width - 4) // 2 + page = emojis[(self.emoji_panel_page - 1) * size:self.emoji_panel_page * size] + index = pad_y * (width - 4) // 2 + pad_x + char = page[index] + if char: + demojized = emoji.demojize(char) + if char != demojized: + for c in reversed(demojized): + curses.ungetch(c) + def add_message(self, msg: str) -> None: """ Store a new message into the history.