[splash] Add the obligatory splash screen on startup

Also fix a bunch of asyncio nonsense going on.
master
Daniel Edgecumbe 7 years ago
parent a4dea8dcdd
commit 0709fb00e2

@ -131,7 +131,7 @@ class BlockView(object):
self._window_size = MIN_WINDOW_SIZE self._window_size = MIN_WINDOW_SIZE
def _draw(self, block, bestblockhash): async def _draw(self, block, bestblockhash):
# TODO: figure out window width etc. # TODO: figure out window width etc.
if self._pad is not None: if self._pad is not None:
@ -170,9 +170,9 @@ class BlockView(object):
self._pad.addstr(4, 31, "Root {}".format(block["merkleroot"]), CBOLD) 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 maxy, maxx = self._window_size
if maxy < 8 or maxx < 3: if maxy < 8 or maxx < 3:
return # Can't do it return # Can't do it
@ -244,7 +244,7 @@ class BlockView(object):
block = await self._blockstore.get_block(self._hash) block = await self._blockstore.get_block(self._hash)
bestblockhash = await self._blockstore.get_bestblockhash() bestblockhash = await self._blockstore.get_bestblockhash()
self._draw(block, bestblockhash) await self._draw(block, bestblockhash)
async def on_bestblockhash(self, key, obj): async def on_bestblockhash(self, key, obj):
try: try:

@ -16,7 +16,9 @@ class FooterView(object):
self._window_size = MIN_WINDOW_SIZE self._window_size = MIN_WINDOW_SIZE
def draw(self): self._visible = False
async def _draw(self):
# TODO: figure out window width etc. # TODO: figure out window width etc.
if self._pad is None: if self._pad is None:
self._pad = curses.newpad(2, 100) self._pad = curses.newpad(2, 100)
@ -41,9 +43,13 @@ class FooterView(object):
if self._dt: if self._dt:
self._pad.addstr(0, 81, self._dt.isoformat(timespec="seconds")[:19], CYELLOW + CBOLD) 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 maxy, maxx = self._window_size
if maxy < 5 or maxx < 3: if maxy < 5 or maxx < 3:
# Can't do it # Can't do it
@ -56,17 +62,17 @@ class FooterView(object):
return return
self._mode = newmode self._mode = newmode
self.draw() await self.draw()
async def on_tick(self, dt): async def on_tick(self, dt):
self._dt = dt self._dt = dt
self.draw() await self.draw()
async def on_window_resize(self, y, x): async def on_window_resize(self, y, x):
# At the moment we ignore the x size and limit to 100. # At the moment we ignore the x size and limit to 100.
if y > self._window_size[0] and self._pad: if y > self._window_size[0] and self._pad:
self._pad.clear() self._pad.clear()
self._draw_pad_to_screen() await self._draw_pad_to_screen()
self._window_size = (y, x) self._window_size = (y, x)
self.draw() await self.draw()

@ -20,6 +20,8 @@ class HeaderView(object):
platform.machine(), platform.machine(),
) )
self._mode = None
self._subversion = None self._subversion = None
self._chain = None self._chain = None
self._connectioncount = None self._connectioncount = None
@ -28,7 +30,9 @@ class HeaderView(object):
self._window_size = MIN_WINDOW_SIZE self._window_size = MIN_WINDOW_SIZE
def draw(self): self._visible = False
async def _draw(self):
# TODO: figure out window width etc. # TODO: figure out window width etc.
self._pad.clear() self._pad.clear()
@ -105,9 +109,20 @@ class HeaderView(object):
else: else:
self._pad.addstr(0, 85, "wallet disabled", CBOLD + CRED) 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 maxy, maxx = self._window_size
if maxy < 3 or maxx < 3: if maxy < 3 or maxx < 3:
# can't do it # can't do it
@ -121,7 +136,7 @@ class HeaderView(object):
except KeyError: except KeyError:
pass pass
self.draw() await self.draw()
async def on_blockchaininfo(self, key, obj): async def on_blockchaininfo(self, key, obj):
try: try:
@ -129,7 +144,7 @@ class HeaderView(object):
except KeyError: except KeyError:
pass pass
self.draw() await self.draw()
async def on_peerinfo(self, key, obj): async def on_peerinfo(self, key, obj):
try: try:
@ -137,7 +152,7 @@ class HeaderView(object):
except KeyError: except KeyError:
pass pass
self.draw() await self.draw()
async def on_nettotals(self, key, obj): async def on_nettotals(self, key, obj):
try: try:
@ -147,7 +162,7 @@ class HeaderView(object):
except KeyError: except KeyError:
pass pass
self.draw() await self.draw()
async def on_walletinfo(self, key, obj): async def on_walletinfo(self, key, obj):
try: try:
@ -158,9 +173,9 @@ class HeaderView(object):
except KeyError: except KeyError:
pass pass
self.draw() await self.draw()
async def on_window_resize(self, y, x): async def on_window_resize(self, y, x):
# At the moment we ignore the x size and limit to 100. # At the moment we ignore the x size and limit to 100.
self._window_size = (y, x) self._window_size = (y, x)
self.draw() await self.draw()

@ -9,6 +9,6 @@ VERSION_STRING = "bitcoind-ncurses v0.2.0-dev"
# "tx", "console", "net", "forks", # "tx", "console", "net", "forks",
# ] # ]
MODES = ["monitor", "peers", "block"] MODES = ["monitor", "peers", "block"]
DEFAULT_MODE = "monitor" # DEFAULT_MODE = "monitor"
MIN_WINDOW_SIZE = (10, 20) MIN_WINDOW_SIZE = (10, 20)

@ -10,13 +10,14 @@ import datetime
import rpc import rpc
import interface import interface
import modes import modes
import splash
import header import header
import footer import footer
import monitor import monitor
import peers import peers
import block import block
from macros import DEFAULT_MODE # from macros import DEFAULT_MODE
async def keypress_loop(window, callback, resize_callback): 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. # Unhandled key. Don't care.
pass pass
first = True # first = True
while True: while True:
# This is basically spinning which is really annoying. # This is basically spinning which is really annoying.
# TODO: find a way of having async blocking getch/getkey. # 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() key = window.getkey()
except Exception: except Exception:
# This is bonkers and I don't understand it. # This is bonkers and I don't understand it.
if first: # if first:
await callback(DEFAULT_MODE[0]) # hackery! # await callback(DEFAULT_MODE[0]) # hackery!
first = False # first = False
await asyncio.sleep(0.05) await asyncio.sleep(0.05)
continue continue
@ -78,13 +79,18 @@ def initialize():
parser.add_argument("--datadir", parser.add_argument("--datadir",
help="path to bitcoin datadir [~/.bitcoin/]", help="path to bitcoin datadir [~/.bitcoin/]",
default=os.path.expanduser("~/.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() args = parser.parse_args()
url = rpc.get_url_from_datadir(args.datadir) url = rpc.get_url_from_datadir(args.datadir)
auth = rpc.get_auth_from_datadir(args.datadir) auth = rpc.get_auth_from_datadir(args.datadir)
client = rpc.BitcoinRPCClient(url, auth) client = rpc.BitcoinRPCClient(url, auth)
return client return client, args.nosplash
def check_disablewallet(client): def check_disablewallet(client):
@ -103,11 +109,15 @@ def check_disablewallet(client):
return False return False
def create_tasks(client, window): def create_tasks(client, window, nosplash):
headerview = header.HeaderView() headerview = header.HeaderView()
footerview = footer.FooterView() 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) monitorview = monitor.MonitorView(client)
peerview = peers.PeersView() peerview = peers.PeersView()
@ -136,18 +146,14 @@ def create_tasks(client, window):
async def on_window_resize(y, x): async def on_window_resize(y, x):
interface.check_min_window_size(y, x) interface.check_min_window_size(y, x)
await splashview.on_window_resize(y, x)
await headerview.on_window_resize(y, x) await headerview.on_window_resize(y, x)
await footerview.on_window_resize(y, x) await footerview.on_window_resize(y, x)
await monitorview.on_window_resize(y, x) await monitorview.on_window_resize(y, x)
await peerview.on_window_resize(y, x) await peerview.on_window_resize(y, x)
await blockview.on_window_resize(y, x) await blockview.on_window_resize(y, x)
# Set the initial window sizes
ty, tx = window.getmaxyx() ty, tx = window.getmaxyx()
loop2 = asyncio.new_event_loop()
loop2.run_until_complete(on_window_resize(ty, tx))
loop2.close()
tasks = [ tasks = [
poll_client(client, "getbestblockhash", poll_client(client, "getbestblockhash",
on_bestblockhash, 1.0), on_bestblockhash, 1.0),
@ -171,7 +177,9 @@ def create_tasks(client, window):
poll_client(client, "uptime", poll_client(client, "uptime",
monitorview.on_uptime, 5.0, params=[10]), monitorview.on_uptime, 5.0, params=[10]),
tick(on_tick, 1.0), 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): if not check_disablewallet(client):
@ -183,13 +191,13 @@ def create_tasks(client, window):
def mainfn(): def mainfn():
client = initialize() client, nosplash = initialize()
try: try:
window = interface.init_curses() window = interface.init_curses()
tasks = create_tasks(client, window) tasks = create_tasks(client, window, nosplash)
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
t = asyncio.gather(*tasks) t = asyncio.gather(*tasks)

@ -6,11 +6,11 @@ from macros import MODES
class ModeHandler(object): class ModeHandler(object):
def __init__(self, base_callback): def __init__(self, base_callbacks):
self._mode = None self._mode = None
self._callbacks = {} # mode -> callback, one per mode. self._callbacks = {} # mode -> callback, one per mode.
self._base_callback = base_callback self._base_callbacks = base_callbacks
self._keypress_handlers = {} # mode -> keypress handler. self._keypress_handlers = {} # mode -> keypress handler.
@ -39,8 +39,9 @@ class ModeHandler(object):
if cb2 is not None: if cb2 is not None:
await cb2(newmode) await cb2(newmode)
# Base callback (generally FooterView) # Base callbacks (FooterView, HeaderView)
await self._base_callback(newmode) for bcb in self._base_callbacks:
await bcb(newmode)
async def set_mode(self, newmode): async def set_mode(self, newmode):
if self._mode == newmode: if self._mode == newmode:

@ -31,7 +31,7 @@ class MonitorView(object):
self._window_size = MIN_WINDOW_SIZE self._window_size = MIN_WINDOW_SIZE
def _draw(self): async def _draw(self):
# TODO: figure out window width etc. # TODO: figure out window width etc.
if self._pad is not None: if self._pad is not None:
@ -132,9 +132,9 @@ class MonitorView(object):
if self._uptime: if self._uptime:
self._pad.addstr(13, 1, "uptime: {}".format(datetime.timedelta(seconds=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 maxy, maxx = self._window_size
if maxy < 8 or maxx < 3: if maxy < 8 or maxx < 3:
return # Can't do it return # Can't do it
@ -143,7 +143,8 @@ class MonitorView(object):
async def draw(self): async def draw(self):
with await self._lock: with await self._lock:
self._draw() if self._visible:
await self._draw()
async def on_bestblockhash(self, key, obj): async def on_bestblockhash(self, key, obj):
try: try:
@ -166,7 +167,7 @@ class MonitorView(object):
j = await self._client.request("getrawtransaction", [j["result"]["tx"][0], 1]) j = await self._client.request("getrawtransaction", [j["result"]["tx"][0], 1])
self._bestcoinbase = j["result"] self._bestcoinbase = j["result"]
if draw and self._visible: if draw:
await self.draw() await self.draw()
async def on_mempoolinfo(self, key, obj): async def on_mempoolinfo(self, key, obj):
@ -175,8 +176,7 @@ class MonitorView(object):
except KeyError: except KeyError:
return return
if self._visible: await self.draw()
await self.draw()
async def on_estimatesmartfee(self, key, obj): async def on_estimatesmartfee(self, key, obj):
try: try:
@ -190,15 +190,13 @@ class MonitorView(object):
except KeyError: except KeyError:
self._estimatesmartfee = None self._estimatesmartfee = None
if self._visible: await self.draw()
await self.draw()
async def on_tick(self, dt): async def on_tick(self, dt):
with await self._lock: with await self._lock:
self._dt = dt self._dt = dt
if self._visible: await self.draw()
await self.draw()
async def on_uptime(self, key, obj): async def on_uptime(self, key, obj):
try: try:
@ -206,8 +204,7 @@ class MonitorView(object):
except KeyError: except KeyError:
return return
if self._visible: await self.draw()
await self.draw()
async def on_mode_change(self, newmode): async def on_mode_change(self, newmode):
if newmode != "monitor": if newmode != "monitor":
@ -220,5 +217,4 @@ class MonitorView(object):
async def on_window_resize(self, y, x): async def on_window_resize(self, y, x):
# At the moment we ignore the x size and limit to 100. # At the moment we ignore the x size and limit to 100.
self._window_size = (y, x) self._window_size = (y, x)
if self._visible: await self.draw()
await self.draw()

@ -18,7 +18,7 @@ class PeersView(object):
self._window_size = MIN_WINDOW_SIZE self._window_size = MIN_WINDOW_SIZE
def draw(self): async def _draw(self):
# TODO: figure out window width etc. # TODO: figure out window width etc.
if self._pad is not None: if self._pad is not None:
@ -88,9 +88,13 @@ class PeersView(object):
if 'synced_headers' in peer: if 'synced_headers' in peer:
self._pad.addstr(1+index-offset, 93, str(peer['synced_headers']).rjust(7) ) 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 maxy, maxx = self._window_size
if maxy < 8 or maxx < 3: if maxy < 8 or maxx < 3:
return # Can't do it return # Can't do it
@ -103,8 +107,7 @@ class PeersView(object):
except KeyError: except KeyError:
return return
if self._visible: await self.draw()
self.draw()
async def on_mode_change(self, newmode): async def on_mode_change(self, newmode):
if newmode != "peers": if newmode != "peers":
@ -112,10 +115,9 @@ class PeersView(object):
return return
self._visible = True self._visible = True
self.draw() await self.draw()
async def on_window_resize(self, y, x): async def on_window_resize(self, y, x):
# At the moment we ignore the x size and limit to 100. # At the moment we ignore the x size and limit to 100.
self._window_size = (y, x) self._window_size = (y, x)
if self._visible: await self.draw()
await self.draw()

@ -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)
Loading…
Cancel
Save