From 9ef00a5c6a2cdd6f20a664d982bcbd5f15cc6916 Mon Sep 17 00:00:00 2001 From: Brandon Lewis Date: Thu, 5 May 2022 18:25:14 -0700 Subject: [PATCH] Add support for 'streaming mode' which reloads graph from stdin --- tests/streaming.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++ xdot/__main__.py | 30 ++++++++++++++++++++++++ xdot/ui/window.py | 14 +++++++----- 3 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 tests/streaming.sh diff --git a/tests/streaming.sh b/tests/streaming.sh new file mode 100644 index 0000000..7fb180e --- /dev/null +++ b/tests/streaming.sh @@ -0,0 +1,57 @@ +while true; do + + cat < B; +} + +EOF + printf '\0' + sleep 1 + + cat < B; + B -> C; +} + +EOF + printf '\0' + sleep 1 + + cat < B; + B -> C; + B -> D; +} + +EOF + printf '\0' + sleep 1 + + cat < B; + B -> C; + B -> D; + C -> E; +} + +EOF + printf '\0' + sleep 1 + + cat < B; + B -> C; + B -> D; + C -> E; + D -> E; +} + +EOF + printf '\0' + sleep 1 +done diff --git a/xdot/__main__.py b/xdot/__main__.py index d561f49..cdf8c9a 100755 --- a/xdot/__main__.py +++ b/xdot/__main__.py @@ -18,6 +18,7 @@ import argparse import sys +from threading import Thread from .ui.window import DotWindow, Gtk @@ -61,6 +62,10 @@ def main(): '--hide-toolbar', action='store_true', dest='hide_toolbar', help='Hides the toolbar on start.') + parser.add_argument( + '--streaming-mode', + action='store_true', dest='streaming_mode', + help='Updates canvas when a new graph is read from stdin') options = parser.parse_args() inputfile = options.inputfile @@ -89,7 +94,32 @@ def main(): import signal signal.signal(signal.SIGINT, signal.SIG_DFL) + def read(): + istream = sys.stdin.detach() + buf = bytearray() + first = True + # if there's a more efficient way to do this than to scan + # stdin character by character, I'm all for it. This just gets + # us off the ground. + # + # what we're doing is looking for the null byte which + while True: + byte = istream.read(1) + if not byte: + pass # Gtk.main_quit() + elif byte == b'\0': + win.set_dotcode(bytes(buf), preserve_viewport=not first) + buf = bytearray() + first = False + else: + buf += byte + + if options.streaming_mode: + reader = Thread(target=read, daemon=True) + reader.start() + Gtk.main() + if __name__ == '__main__': main() diff --git a/xdot/ui/window.py b/xdot/ui/window.py index 0a620d4..9311eb1 100644 --- a/xdot/ui/window.py +++ b/xdot/ui/window.py @@ -706,15 +706,17 @@ def textentry_activate(self, widget, entry): def set_filter(self, filter): self.dotwidget.set_filter(filter) - def set_dotcode(self, dotcode, filename=None): - if self.dotwidget.set_dotcode(dotcode, filename): + def set_dotcode(self, dotcode, filename=None, preserve_viewport=False): + if self.dotwidget.set_dotcode(dotcode, filename, center=not preserve_viewport): self.update_title(filename) - self.dotwidget.zoom_to_fit() + if not preserve_viewport: + self.dotwidget.zoom_to_fit() - def set_xdotcode(self, xdotcode, filename=None): - if self.dotwidget.set_xdotcode(xdotcode): + def set_xdotcode(self, xdotcode, filename=None, preserve_viewport=False): + if self.dotwidget.set_xdotcode(xdotcode, center=not preserve_viewport): self.update_title(filename) - self.dotwidget.zoom_to_fit() + if not preserve_viewport: + self.dotwidget.zoom_to_fit() def update_title(self, filename=None): if filename is None: