sbeam/src/SuperBeam/tui.py

160 lines
4.8 KiB
Python

import asyncio
import urwid
import weakref
import struct
import base64
import pyqrcode
import socket
from datetime import datetime
loop = asyncio.get_event_loop()
def unhandled(key):
if key == 'ctrl c':
raise urwid.ExitMainLoop
def get_primary_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception as exc:
print(exc)
IP = '127.0.0.1'
finally:
s.close()
return IP
def get_qrcode():
primary_ip = get_primary_ip()
octets = [1]
octets.extend([int(_) for _ in primary_ip.split('.')[::-1]])
url = 'http://superbe.am/q?' + base64.b64encode(
struct.pack('BBBBB', *octets)
).decode('utf-8')
return pyqrcode.create(url)
class ClientModel(object):
def __init__(self):
self.clients = ['127.0.0.1', '192.168.178.34']
def get_clients(self):
return self.clients
class SuperBeamView(urwid.WidgetWrap):
palette = [
('body', 'black', 'light gray', 'standout'),
('header', 'white', 'dark red', 'bold'),
('screen edge', 'light blue', 'dark cyan'),
('main shadow', 'dark gray', 'black'),
('line', 'black', 'light gray', 'standout'),
('bg background', 'light gray', 'black'),
('bg 1', 'black', 'dark blue', 'standout'),
('bg 1 smooth', 'dark blue', 'black'),
('bg 2', 'black', 'dark cyan', 'standout'),
('bg 2 smooth', 'dark cyan', 'black'),
('button normal', 'light gray', 'dark blue', 'standout'),
('button select', 'white', 'dark green'),
('line', 'black', 'light gray', 'standout'),
('pg normal', 'white', 'black', 'standout'),
('pg complete', 'white', 'dark magenta'),
('pg smooth', 'dark magenta', 'black'),
('qr code', 'black', 'white'),
]
def __init__(self, controller):
self.controller = controller
self.client_model = ClientModel()
urwid.WidgetWrap.__init__(self, self.main_window())
def main_shadow(self, w):
bg = urwid.AttrWrap(urwid.SolidFill(u"\u2592"), 'screen edge')
shadow = urwid.AttrWrap(urwid.SolidFill(u" "), 'main shadow')
bg = urwid.Overlay(shadow, bg,
('fixed left', 3), ('fixed right', 1),
('fixed top', 2), ('fixed bottom', 1))
w = urwid.Overlay(w, bg,
('fixed left', 2), ('fixed right', 3),
('fixed top', 1), ('fixed bottom', 2))
return w
def update_clock(self, widget_ref):
widget = widget_ref()
if not widget:
return
widget.set_text(datetime.now().isoformat())
loop.call_later(1, self.update_clock, widget_ref)
def qrcode(self):
qrcode = get_qrcode().terminal()
qrcode = qrcode.replace('\x1b[0m\x1b[49m ', '██')
qrcode = qrcode.replace('\x1b[0m\x1b[7m ', ' ')
qrcode = qrcode.replace('\x1b[0m', '')
qrcode = qrcode.replace('\x1b[7m', '')
widgets = []
for row in qrcode.split('\n'):
widgets.append(urwid.AttrWrap(urwid.Text(row), 'qr code'))
w = urwid.Pile(widgets)
return urwid.Filler(urwid.AttrWrap(w, 'body'))
def client_list(self):
clients = []
for client in self.client_model.get_clients():
clients.append(urwid.Filler(urwid.AttrWrap(urwid.Text(client),
'line')))
return urwid.Pile([(1, _) for _ in clients])
def main_window(self):
header = urwid.AttrWrap(urwid.Text('SuperBeam Tui'), 'header')
clock = urwid.Text('')
self.update_clock(weakref.ref(clock))
footer = urwid.AttrWrap(clock, 'header')
body = urwid.Pile([(44, self.qrcode()), self.client_list()])
window = urwid.Frame(body, header, footer)
return self.main_shadow(window)
class SuperBeamController(object):
def __init__(self):
self.model = ClientModel()
self.view = SuperBeamView(self)
def run(self):
# self.loop = urwid.MainLoop(self.view, self.view.palette)
self.loop = urwid.MainLoop(
self.view, self.view.palette,
event_loop=urwid.AsyncioEventLoop(loop=loop),
unhandled_input=unhandled
)
self.loop.run()
def run():
screen = urwid.raw_display.Screen()
size = screen.get_cols_rows()
if size[0] < 87 or size[1] < 58:
raise Exception('Not enough screen space to render QR code.')
else:
SuperBeamController().run()
if __name__ == '__main__':
run()