diff --git a/docs/dev.md b/docs/dev.md index 75b7ea0..534b398 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -26,7 +26,7 @@ usage: ./scripts/create_sibling.sh [OPTIONS] `GLib-ERROR **: 20:50:46.361: getauxval () failed: No such file or directory` -- Affected DEB & Versions: QEMU <= 2.11 +- Affected DEB & Versions: QEMU <= 2.11 - Fix: Upgrade QEMU to >= 3.1 - Bug Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923289 @@ -55,7 +55,6 @@ If you changed the `voice.py`- File, the translations need an update. Do it like Now you can use the `preview.py`-script to preview the changes: ```shell -./scripts/preview.py --lang it --display ws2 --port 8080 & -./scripts/preview.py --lang it --display inky --port 8081 & -# Now open http://localhost:8080 and http://localhost:8081 +./scripts/preview.py --lang it --display ws1 ws2 inky --output preview.png +# Now open preview.png ``` diff --git a/scripts/preview.py b/scripts/preview.py index 2b45933..1d29119 100755 --- a/scripts/preview.py +++ b/scripts/preview.py @@ -14,64 +14,26 @@ sys.path.insert(0, '../sdcard/rootfs/root/pwnagotchi/scripts/')) from pwnagotchi.ui.display import Display, VideoHandler - +from PIL import Image class CustomDisplay(Display): + def __init__(self, config, state): + self.last_image = None + super(CustomDisplay, self).__init__(config, state) + def _http_serve(self): - if self._video_address is not None: - self._httpd = HTTPServer((self._video_address, self._video_port), - CustomVideoHandler) - logging.info("ui available at http://%s:%d/" % (self._video_address, - self._video_port)) - self._httpd.serve_forever() - else: - logging.info("could not get ip of usb0, video server not starting") + # do nothing + pass def _on_view_rendered(self, img): - CustomVideoHandler.render(img) + self.last_image = img - if self._enabled: - self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation)) - if self._render_cb is not None: - self._render_cb() - - -class CustomVideoHandler(VideoHandler): - - @staticmethod - def render(img): - with CustomVideoHandler._lock: - try: - img.save("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), format='PNG') - except BaseException: - logging.exception("could not write preview") - - def do_GET(self): - if self.path == '/': - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - try: - self.wfile.write( - bytes( - self._index % - ('localhost', 1000), "utf8")) - except BaseException: - pass - - elif self.path.startswith('/ui'): - with self._lock: - self.send_response(200) - self.send_header('Content-type', 'image/png') - self.end_headers() - try: - with open("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), 'rb') as fp: - shutil.copyfileobj(fp, self.wfile) - except BaseException: - logging.exception("could not open preview") - else: - self.send_response(404) + def get_image(self): + """ + Return the saved image + """ + return self.last_image class DummyPeer: @@ -79,21 +41,43 @@ class DummyPeer: def name(): return "beta" +def append_images(images, horizontal=True, xmargin=0, ymargin=0): + w, h = zip(*(i.size for i in images)) + + if horizontal: + t_w = sum(w) + t_h = max(h) + else: + t_w = max(w) + t_h = sum(h) + + result = Image.new('RGB', (t_w, t_h)) + + x_offset = 0 + y_offset = 0 + + for im in images: + result.paste(im, (x_offset,y_offset)) + if horizontal: + x_offset += im.size[0] + xmargin + else: + y_offset += im.size[1] + ymargin + + return result def main(): parser = argparse.ArgumentParser(description="This program emulates\ the pwnagotchi display") - parser.add_argument('--display', help="Which display to use.", + parser.add_argument('--displays', help="Which displays to use.", nargs="+", default="waveshare_2") - parser.add_argument('--port', help="Which port to use", - default=8080) - parser.add_argument('--sleep', type=int, help="Time between emotions", - default=2) parser.add_argument('--lang', help="Language to use", default="en") + parser.add_argument('--output', help="Path to output image (PNG)", default="preview.png") + parser.add_argument('--xmargin', type=int, default=5) + parser.add_argument('--ymargin', type=int, default=5) args = parser.parse_args() - CONFIG = yaml.load(''' + config_template = ''' main: lang: {lang} ui: @@ -107,65 +91,81 @@ def main(): video: enabled: true address: "0.0.0.0" - port: {port} - '''.format(display=args.display, - port=args.port, - lang=args.lang)) + port: 8080 + ''' - DISPLAY = CustomDisplay(config=CONFIG, state={'name': '%s>' % 'preview'}) + list_of_displays = list() + for display_type in args.displays: + config = yaml.safe_load(config_template.format(display=display_type, + lang=args.lang)) + display = CustomDisplay(config=config, state={'name': f"{display_type}>"}) + list_of_displays.append(display) - while True: - DISPLAY.on_starting() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_ai_ready() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_normal() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_new_peer(DummyPeer()) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_lost_peer(DummyPeer()) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_free_channel('6') - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.wait(args.sleep) - DISPLAY.update() - DISPLAY.on_bored() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_sad() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_motivated(1) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_demotivated(-1) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_excited() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'}) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_miss('test') - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_lonely() - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_handshakes(1) - DISPLAY.update() - time.sleep(args.sleep) - DISPLAY.on_rebooting() - DISPLAY.update() - time.sleep(args.sleep) + columns = list() + + for display in list_of_displays: + emotions = list() + # Starting + display.on_starting() + display.update() + emotions.append(display.get_image()) + display.on_ai_ready() + display.update() + emotions.append(display.get_image()) + display.on_normal() + display.update() + emotions.append(display.get_image()) + display.on_new_peer(DummyPeer()) + display.update() + emotions.append(display.get_image()) + display.on_lost_peer(DummyPeer()) + display.update() + emotions.append(display.get_image()) + display.on_free_channel('6') + display.update() + emotions.append(display.get_image()) + display.wait(2) + display.update() + emotions.append(display.get_image()) + display.on_bored() + display.update() + emotions.append(display.get_image()) + display.on_sad() + display.update() + emotions.append(display.get_image()) + display.on_motivated(1) + display.update() + emotions.append(display.get_image()) + display.on_demotivated(-1) + display.update() + emotions.append(display.get_image()) + display.on_excited() + display.update() + emotions.append(display.get_image()) + display.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'}) + display.update() + emotions.append(display.get_image()) + display.on_miss('test') + display.update() + emotions.append(display.get_image()) + display.on_lonely() + display.update() + emotions.append(display.get_image()) + display.on_handshakes(1) + display.update() + emotions.append(display.get_image()) + display.on_rebooting() + display.update() + emotions.append(display.get_image()) + + # append them all together (vertical) + columns.append(append_images(emotions, horizontal=False, xmargin=args.xmargin, ymargin=args.ymargin)) + + + # append columns side by side + final_image = append_images(columns, horizontal=True, xmargin=args.xmargin, ymargin=args.ymargin) + final_image.save(args.output, 'PNG') if __name__ == '__main__': SystemExit(main())