From 0709fb00e2413e631977cbbbb68f81ddd8c64f88 Mon Sep 17 00:00:00 2001 From: Daniel Edgecumbe Date: Thu, 28 Sep 2017 03:06:10 +0100 Subject: [PATCH] [splash] Add the obligatory splash screen on startup Also fix a bunch of asyncio nonsense going on. --- block.py | 8 ++--- footer.py | 20 ++++++++----- header.py | 33 +++++++++++++++------ macros.py | 2 +- main.py | 40 +++++++++++++++---------- modes.py | 9 +++--- monitor.py | 26 +++++++---------- peers.py | 18 +++++++----- splash.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 178 insertions(+), 64 deletions(-) create mode 100644 splash.py diff --git a/block.py b/block.py index 5d0d55c..4ed87e7 100644 --- a/block.py +++ b/block.py @@ -131,7 +131,7 @@ class BlockView(object): self._window_size = MIN_WINDOW_SIZE - def _draw(self, block, bestblockhash): + async def _draw(self, block, bestblockhash): # TODO: figure out window width etc. if self._pad is not None: @@ -170,9 +170,9 @@ class BlockView(object): self._pad.addstr(4, 31, "Root {}".format(block["merkleroot"]), CBOLD) - self._draw_pad_to_screen() + await self._draw_pad_to_screen() - def _draw_pad_to_screen(self): + async def _draw_pad_to_screen(self): maxy, maxx = self._window_size if maxy < 8 or maxx < 3: return # Can't do it @@ -244,7 +244,7 @@ class BlockView(object): block = await self._blockstore.get_block(self._hash) bestblockhash = await self._blockstore.get_bestblockhash() - self._draw(block, bestblockhash) + await self._draw(block, bestblockhash) async def on_bestblockhash(self, key, obj): try: diff --git a/footer.py b/footer.py index 2e4da48..6930714 100644 --- a/footer.py +++ b/footer.py @@ -16,7 +16,9 @@ class FooterView(object): self._window_size = MIN_WINDOW_SIZE - def draw(self): + self._visible = False + + async def _draw(self): # TODO: figure out window width etc. if self._pad is None: self._pad = curses.newpad(2, 100) @@ -41,9 +43,13 @@ class FooterView(object): if self._dt: self._pad.addstr(0, 81, self._dt.isoformat(timespec="seconds")[:19], CYELLOW + CBOLD) - self._draw_pad_to_screen() + await self._draw_pad_to_screen() + + async def draw(self): + if self._mode is not None and self._mode != "splash": + await self._draw() - def _draw_pad_to_screen(self): + async def _draw_pad_to_screen(self): maxy, maxx = self._window_size if maxy < 5 or maxx < 3: # Can't do it @@ -56,17 +62,17 @@ class FooterView(object): return self._mode = newmode - self.draw() + await self.draw() async def on_tick(self, dt): self._dt = dt - self.draw() + await self.draw() async def on_window_resize(self, y, x): # At the moment we ignore the x size and limit to 100. if y > self._window_size[0] and self._pad: self._pad.clear() - self._draw_pad_to_screen() + await self._draw_pad_to_screen() self._window_size = (y, x) - self.draw() + await self.draw() diff --git a/header.py b/header.py index 432780f..5291635 100644 --- a/header.py +++ b/header.py @@ -20,6 +20,8 @@ class HeaderView(object): platform.machine(), ) + self._mode = None + self._subversion = None self._chain = None self._connectioncount = None @@ -28,7 +30,9 @@ class HeaderView(object): self._window_size = MIN_WINDOW_SIZE - def draw(self): + self._visible = False + + async def _draw(self): # TODO: figure out window width etc. self._pad.clear() @@ -105,9 +109,20 @@ class HeaderView(object): else: self._pad.addstr(0, 85, "wallet disabled", CBOLD + CRED) - self._draw_pad_to_screen() + await self._draw_pad_to_screen() + + async def draw(self): + if self._mode is not None and self._mode != "splash": + await self._draw() + + async def on_mode_change(self, newmode): + if self._mode == newmode: + return + + self._mode = newmode + await self.draw() - def _draw_pad_to_screen(self): + async def _draw_pad_to_screen(self): maxy, maxx = self._window_size if maxy < 3 or maxx < 3: # can't do it @@ -121,7 +136,7 @@ class HeaderView(object): except KeyError: pass - self.draw() + await self.draw() async def on_blockchaininfo(self, key, obj): try: @@ -129,7 +144,7 @@ class HeaderView(object): except KeyError: pass - self.draw() + await self.draw() async def on_peerinfo(self, key, obj): try: @@ -137,7 +152,7 @@ class HeaderView(object): except KeyError: pass - self.draw() + await self.draw() async def on_nettotals(self, key, obj): try: @@ -147,7 +162,7 @@ class HeaderView(object): except KeyError: pass - self.draw() + await self.draw() async def on_walletinfo(self, key, obj): try: @@ -158,9 +173,9 @@ class HeaderView(object): except KeyError: pass - self.draw() + await self.draw() async def on_window_resize(self, y, x): # At the moment we ignore the x size and limit to 100. self._window_size = (y, x) - self.draw() + await self.draw() diff --git a/macros.py b/macros.py index 4e81ccd..1339c3d 100644 --- a/macros.py +++ b/macros.py @@ -9,6 +9,6 @@ VERSION_STRING = "bitcoind-ncurses v0.2.0-dev" # "tx", "console", "net", "forks", # ] MODES = ["monitor", "peers", "block"] -DEFAULT_MODE = "monitor" +# DEFAULT_MODE = "monitor" MIN_WINDOW_SIZE = (10, 20) diff --git a/main.py b/main.py index 9119815..b3ac532 100644 --- a/main.py +++ b/main.py @@ -10,13 +10,14 @@ import datetime import rpc import interface import modes +import splash import header import footer import monitor import peers import block -from macros import DEFAULT_MODE +# from macros import DEFAULT_MODE async def keypress_loop(window, callback, resize_callback): @@ -34,7 +35,7 @@ async def keypress_loop(window, callback, resize_callback): # Unhandled key. Don't care. pass - first = True + # first = True while True: # This is basically spinning which is really annoying. # TODO: find a way of having async blocking getch/getkey. @@ -42,9 +43,9 @@ async def keypress_loop(window, callback, resize_callback): key = window.getkey() except Exception: # This is bonkers and I don't understand it. - if first: - await callback(DEFAULT_MODE[0]) # hackery! - first = False + # if first: + # await callback(DEFAULT_MODE[0]) # hackery! + # first = False await asyncio.sleep(0.05) continue @@ -78,13 +79,18 @@ def initialize(): parser.add_argument("--datadir", help="path to bitcoin datadir [~/.bitcoin/]", default=os.path.expanduser("~/.bitcoin/")) + parser.add_argument("--no-splash", + help="whether to disable the splash screen [False]", + action='store_true', + dest="nosplash", + default=False) args = parser.parse_args() url = rpc.get_url_from_datadir(args.datadir) auth = rpc.get_auth_from_datadir(args.datadir) client = rpc.BitcoinRPCClient(url, auth) - return client + return client, args.nosplash def check_disablewallet(client): @@ -103,11 +109,15 @@ def check_disablewallet(client): return False -def create_tasks(client, window): +def create_tasks(client, window, nosplash): headerview = header.HeaderView() footerview = footer.FooterView() - modehandler = modes.ModeHandler(footerview.on_mode_change) + modehandler = modes.ModeHandler( + (headerview.on_mode_change, footerview.on_mode_change, ), + ) + + splashview = splash.SplashView(modehandler.set_mode) monitorview = monitor.MonitorView(client) peerview = peers.PeersView() @@ -136,18 +146,14 @@ def create_tasks(client, window): async def on_window_resize(y, x): interface.check_min_window_size(y, x) + await splashview.on_window_resize(y, x) await headerview.on_window_resize(y, x) await footerview.on_window_resize(y, x) await monitorview.on_window_resize(y, x) await peerview.on_window_resize(y, x) await blockview.on_window_resize(y, x) - # Set the initial window sizes ty, tx = window.getmaxyx() - loop2 = asyncio.new_event_loop() - loop2.run_until_complete(on_window_resize(ty, tx)) - loop2.close() - tasks = [ poll_client(client, "getbestblockhash", on_bestblockhash, 1.0), @@ -171,7 +177,9 @@ def create_tasks(client, window): poll_client(client, "uptime", monitorview.on_uptime, 5.0, params=[10]), tick(on_tick, 1.0), - keypress_loop(window, modehandler.handle_keypress, on_window_resize) + keypress_loop(window, modehandler.handle_keypress, on_window_resize), + on_window_resize(ty, tx), + splashview.draw(nosplash), ] if not check_disablewallet(client): @@ -183,13 +191,13 @@ def create_tasks(client, window): def mainfn(): - client = initialize() + client, nosplash = initialize() try: window = interface.init_curses() - tasks = create_tasks(client, window) + tasks = create_tasks(client, window, nosplash) loop = asyncio.get_event_loop() t = asyncio.gather(*tasks) diff --git a/modes.py b/modes.py index 51fcbd1..7c9cfb2 100644 --- a/modes.py +++ b/modes.py @@ -6,11 +6,11 @@ from macros import MODES class ModeHandler(object): - def __init__(self, base_callback): + def __init__(self, base_callbacks): self._mode = None self._callbacks = {} # mode -> callback, one per mode. - self._base_callback = base_callback + self._base_callbacks = base_callbacks self._keypress_handlers = {} # mode -> keypress handler. @@ -39,8 +39,9 @@ class ModeHandler(object): if cb2 is not None: await cb2(newmode) - # Base callback (generally FooterView) - await self._base_callback(newmode) + # Base callbacks (FooterView, HeaderView) + for bcb in self._base_callbacks: + await bcb(newmode) async def set_mode(self, newmode): if self._mode == newmode: diff --git a/monitor.py b/monitor.py index 96fb069..9946f35 100644 --- a/monitor.py +++ b/monitor.py @@ -31,7 +31,7 @@ class MonitorView(object): self._window_size = MIN_WINDOW_SIZE - def _draw(self): + async def _draw(self): # TODO: figure out window width etc. if self._pad is not None: @@ -132,9 +132,9 @@ class MonitorView(object): if self._uptime: self._pad.addstr(13, 1, "uptime: {}".format(datetime.timedelta(seconds=self._uptime))) - self._draw_pad_to_screen() + await self._draw_pad_to_screen() - def _draw_pad_to_screen(self): + async def _draw_pad_to_screen(self): maxy, maxx = self._window_size if maxy < 8 or maxx < 3: return # Can't do it @@ -143,7 +143,8 @@ class MonitorView(object): async def draw(self): with await self._lock: - self._draw() + if self._visible: + await self._draw() async def on_bestblockhash(self, key, obj): try: @@ -166,7 +167,7 @@ class MonitorView(object): j = await self._client.request("getrawtransaction", [j["result"]["tx"][0], 1]) self._bestcoinbase = j["result"] - if draw and self._visible: + if draw: await self.draw() async def on_mempoolinfo(self, key, obj): @@ -175,8 +176,7 @@ class MonitorView(object): except KeyError: return - if self._visible: - await self.draw() + await self.draw() async def on_estimatesmartfee(self, key, obj): try: @@ -190,15 +190,13 @@ class MonitorView(object): except KeyError: self._estimatesmartfee = None - if self._visible: - await self.draw() + await self.draw() async def on_tick(self, dt): with await self._lock: self._dt = dt - if self._visible: - await self.draw() + await self.draw() async def on_uptime(self, key, obj): try: @@ -206,8 +204,7 @@ class MonitorView(object): except KeyError: return - if self._visible: - await self.draw() + await self.draw() async def on_mode_change(self, newmode): if newmode != "monitor": @@ -220,5 +217,4 @@ class MonitorView(object): async def on_window_resize(self, y, x): # At the moment we ignore the x size and limit to 100. self._window_size = (y, x) - if self._visible: - await self.draw() + await self.draw() diff --git a/peers.py b/peers.py index 50911f5..9fabd9f 100644 --- a/peers.py +++ b/peers.py @@ -18,7 +18,7 @@ class PeersView(object): self._window_size = MIN_WINDOW_SIZE - def draw(self): + async def _draw(self): # TODO: figure out window width etc. if self._pad is not None: @@ -88,9 +88,13 @@ class PeersView(object): if 'synced_headers' in peer: self._pad.addstr(1+index-offset, 93, str(peer['synced_headers']).rjust(7) ) - self._draw_pad_to_screen() + await self._draw_pad_to_screen() - def _draw_pad_to_screen(self): + async def draw(self): + if self._visible: + await self._draw() + + async def _draw_pad_to_screen(self): maxy, maxx = self._window_size if maxy < 8 or maxx < 3: return # Can't do it @@ -103,8 +107,7 @@ class PeersView(object): except KeyError: return - if self._visible: - self.draw() + await self.draw() async def on_mode_change(self, newmode): if newmode != "peers": @@ -112,10 +115,9 @@ class PeersView(object): return self._visible = True - self.draw() + await self.draw() async def on_window_resize(self, y, x): # At the moment we ignore the x size and limit to 100. self._window_size = (y, x) - if self._visible: - await self.draw() + await self.draw() diff --git a/splash.py b/splash.py new file mode 100644 index 0000000..708d7e2 --- /dev/null +++ b/splash.py @@ -0,0 +1,86 @@ +# Copyright (c) 2014-2017 esotericnonsense (Daniel Edgecumbe) +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/licenses/mit-license.php + +import curses +import asyncio + +from macros import MIN_WINDOW_SIZE + +import time + +splash_array = [ + " BB BB BB ", + " BB BB BB BBBB BBBB BB BB BB BB ", + " BBBBB BBBB BB BB BB BBB BB BBBBB ", + " BB BB BB BB BB BB BB BB BB BB BB BB ", + " BBB BB BB BB BB BB BB BB BB BB BB BB ", + " BB BBB BB BB BBBB BBBB BB BB BB BBBB ", + " ", + " ---------------------------", + " n c u r s e s ", + " ---------------------------", +] +width = len(splash_array[0]) +height = len(splash_array) + +class SplashView(object): + def __init__(self, set_mode_callback): + self._set_mode_callback = set_mode_callback # ModeHandler + + self._pad = None + + self._window_size = MIN_WINDOW_SIZE + + async def draw(self, nosplash): + if nosplash: + await self._end_splash(nosplash) + return + + if self._pad is not None: + self._pad.clear() + else: + self._pad = curses.newpad(20, 100) + + CGREEN = curses.color_pair(1) + CRED = curses.color_pair(3) + CBOLD = curses.A_BOLD + CREVERSE = curses.A_REVERSE + + for x in range(len(splash_array[0])): + for y in range(len(splash_array)): + if splash_array[y][x] == "B": + if y < 7: + self._pad.addstr(y+1, x, " ", CGREEN + CREVERSE) + else: + self._pad.addstr(y+1, x, " ", CRED + CREVERSE) + elif splash_array[y][x] != " ": + self._pad.addstr(y+1, x, splash_array[y][x], CRED + CBOLD) + y += 1 + await self._draw_pad_to_screen() + time.sleep(0.01) + + await asyncio.sleep(0.5) + time.sleep(0.5) + await self._end_splash(nosplash) + + async def _end_splash(self, nosplash): + if not nosplash: + self._pad.clear() + await self._draw_pad_to_screen() + + await self._set_mode_callback("monitor") + + async def _draw_pad_to_screen(self): + maxy, maxx = self._window_size + if maxy < height+1 or maxx < width+1: + return # Can't do it + + t = (maxy-height)//2 + l = (maxx-width)//2 + self._pad.refresh(0, 0, t, l, t+height, l+width) + + async def on_window_resize(self, y, x): + # This should prevent the splash from crashing + # if there's a resize during the draw operations. + self._window_size = (y, x)