diff --git a/screenless/__init__.py b/screenless/__init__.py index b633544..a50a018 100644 --- a/screenless/__init__.py +++ b/screenless/__init__.py @@ -1,13 +1,13 @@ import sys -from . import officemgr +from . import mgmt def main(args=None): if args is None: args = sys.argv[1:] - print("starting Screenless Office...") - mgr = officemgr.OfficeManager() + print("initializing Screenless Office...") + mgr = mgmt.Management() - print("starting Office Manager...") + print("starting Management...") mgr.run() diff --git a/screenless/bureau/audio/audio.py b/screenless/bureau/audio/audio.py index 6d6574d..7b2cf42 100644 --- a/screenless/bureau/audio/audio.py +++ b/screenless/bureau/audio/audio.py @@ -1,4 +1,4 @@ -import subprocess +import vlc from bureau import Bureau, add_command @@ -17,7 +17,7 @@ class Audio(Bureau): Bureau.__init__(self) self.urldb = self.open_db("urldb") - subprocess.call(["mocp", "-S"]) + self.player = vlc.MediaPlayer() @add_command("p", "Play an album, track or a live stream.") def play(self, data): @@ -31,44 +31,58 @@ class Audio(Bureau): url = self.urldb.get(shortcode) self.log.debug(" playing url " + url) - subprocess.call(["mocp", "-c"]) - subprocess.call(["mocp", "-a", url]) - subprocess.call(["mocp", "-p"]) + self.player.set_mrl(url) + self.player.play() @add_command("stop", "Halt audio playback.") def stop(self): """ Stops all audio currently playing audio output. """ - subprocess.call(["mocp", "-P"]) + self.player.pause() @add_command("resu", "Resume playback.") def resume(self): """ Resume playback of paused audio. """ - subprocess.call(["mocp", "-U"]) + self.player.play() @add_command("next", "Play the next song.") def play_next(self): """ Skip to the next song in the playlist or album. """ - subprocess.call(["mocp", "-f"]) + #subprocess.call(["mocp", "-f"]) + # TODO + pass @add_command("prev", "Play the previous song.") def play_prev(self): """ Skip to the previous song in the playlist or album. """ - subprocess.call(["mocp", "-r"]) + #subprocess.call(["mocp", "-r"]) + # TODO + pass @add_command("nowp", "Now Playing") def now_playing(self): """ Prints the currently playing song or stream on the small printer. """ - out = subprocess.check_output(["mocp", "-i"]).decode("utf-8") + #out = subprocess.check_output(["mocp", "-i"]).decode("utf-8") + # TODO: sort out how to do this with + out = "Now Playing: " + out += self.player.get_media().get_meta(vlc.Meta.Title) + "\n" + nowplaying = self.player.get_media().get_meta(vlc.Meta.NowPlaying) + if nowplaying == "": + out += "by " + self.player.get_media().get_meta(vlc.Meta.Title) + "\n" + out += "from the album '" + self.player.get_media().get_meta(vlc.Meta.Album) \ + + "'\n" + else: + out += nowplaying + "\n" + self.log.debug("info output:" + out) self.print_small(out) diff --git a/screenless/bureau/bureau.py b/screenless/bureau/bureau.py index 7324bb8..f4a6533 100644 --- a/screenless/bureau/bureau.py +++ b/screenless/bureau/bureau.py @@ -7,9 +7,9 @@ import os.path import random import string import subprocess -import sys import tempfile import textwrap +#import traceback import threading import lmdb @@ -60,6 +60,19 @@ def add_api(apistr, name=""): return decorator +#def log_traceback(func): +# """ this is a decorator that catches tracebacks for logging""" +# def wrapper(*args): +# my_bureau = args[0] +# try: +# func(*args) +# except Exception as e: +# my_bureau.log.error("CRASH TRACE: {0}".format(my_bureau.name), +# exc_info=e) +# raise +# return wrapper + + class LogPrinter(logging.Handler): """ LogPrinter prints logs on a receipt printer for screenless debugging. @@ -151,11 +164,6 @@ class Bureau(object): self.api = {} modpath = os.path.dirname(__file__) - #slimerjs = os.path.join(modpath, "..", "lib", "slimerjs", "slimerjs") - #renderer = os.path.join(modpath, "..", "slimerjs", "rasterize.js") - #self.slimerjs = os.path.abspath(slimerjs) - #self.html2pdf = self.slimerjs + " --headless " + \ - # os.path.abspath(renderer) + " " mypath = inspect.getfile(self.__class__) self.mdir = os.path.dirname(mypath) @@ -181,7 +189,6 @@ class Bureau(object): log_format = logging.Formatter('LOG ${levelname} $name: $message', style='$') log_printer.setFormatter(log_format) self.log.addHandler(log_printer) - sys.excepthook = self._log_exception # setup a dir to store files and data self.datadir = os.path.join(basepath, self.prefix) @@ -200,9 +207,9 @@ class Bureau(object): self.log.debug("commands: ") self.log.debug(str(self.commands)) - def _log_exception(typ, value, tb): - self.log.error("CRASH TRACE: {0}".format(str(value)), exc_info=(typ, value, tb)) - sys.__excepthook__(typ, value, tb) +# def _log_exception(typ, value, tb): +# self.log.error("CRASH TRACE: {0}".format(str(value)), exc_info=(typ, value, tb)) +# sys.__excepthook__(typ, value, tb) def open_db(self, name): """ @@ -361,6 +368,16 @@ class Bureau(object): print(("hi! testing. " + self.name + " bureau seems to work!")) return "seems to work." + def _run_io(self): + """ + wrapper for run_io so that we can catch threaded exceptions and log + """ + try: + self.run_io() + except Exception as err: + self.log.exception("%s CRASHED with %s\n", self.name, err) + raise + def run_io(self): """process hardware or timed input @@ -375,6 +392,16 @@ class Bureau(object): pass def run(self): + """ + wrapper running the main loop and logging all exceptions + """ + try: + self._run() + except Exception as err: + self.log.exception("%s CRASHED with %s\n", self.name, err) + raise + + def _run(self): """ main loop for processing messages @@ -382,7 +409,7 @@ class Bureau(object): """ # start the hardware input handler - io_handler = threading.Thread(target=self.run_io) + io_handler = threading.Thread(target=self._run_io) io_handler.start() # register commands and api methods @@ -398,10 +425,6 @@ class Bureau(object): msg = self._recv.recv_string(flags=zmq.NOBLOCK) else: continue - # msg = self._recv.recv_string(flags=zmq.NOBLOCK) - #except zmq.ZMQError: - # time.sleep(0.05) # don't waste CPU - # continue try: self.log.debug("got message:" + msg) dot = msg.find(".") @@ -411,7 +434,6 @@ class Bureau(object): self.log.debug("dot at %d", dot) # TODO: maybe trim off the trailing "." for convenience data = msg[dot + 1:] - # data = str(data) # force to be a string else: data = None self.log.debug("data: " + str(data)) diff --git a/screenless/bureau/publicrelations/publicrelations.py b/screenless/bureau/publicrelations/publicrelations.py index 7d3f55d..98b5836 100644 --- a/screenless/bureau/publicrelations/publicrelations.py +++ b/screenless/bureau/publicrelations/publicrelations.py @@ -292,6 +292,8 @@ class PublicRelations(Bureau): prn.codepage = "cp437" # TODO: add fancier formatting i.e. inverted text for username/handle + # TODO: clean this up to use the built in formatting from escpos lib + # and make a print_status function to use for notifications too toots = self.masto.timeline(limit=count) out = "" for t in toots: @@ -324,8 +326,20 @@ class PublicRelations(Bureau): tw_shortcode = self.short_tweet_id(str(t["id"])) prn.barcode("PRmad." + tw_shortcode, "CODE128", function_type="B") - prn.text("\r\n\r\n") + notifications = self.masto.notifications() + if len(notifications) > 0: + prn.set(text_type="B") + prn.text("NOTIFICATIONS:\r\n") + prn.set(text_type="NORMAL") + for note in notifications: + username = t.account.display_name.encode("cp437", "ignore") + \ + b" (" + t.account.acct.encode("cp437", "ignore") + b")" + prn.text(note["type"] + " " + str(note["created_at"]) + " from ") + prn._raw(username) + prn.text(":\r\n" + str(note.keys()) + "\r\n") + + prn.text("\r\n\r\n") prn.cut() diff --git a/screenless/bureau/sales/sales.py b/screenless/bureau/sales/sales.py index dea4538..91ff3da 100644 --- a/screenless/bureau/sales/sales.py +++ b/screenless/bureau/sales/sales.py @@ -22,7 +22,7 @@ class Sales(Bureau): pass # we've already got a db folder @add_command("p", "Play Media") - def print_fortune(self, data): + def play_media(self, data): """ Shows media on a connected projector or plays audio. """ diff --git a/screenless/bureau/typing.py b/screenless/bureau/typing.py index 566d5f2..d6b9c73 100644 --- a/screenless/bureau/typing.py +++ b/screenless/bureau/typing.py @@ -67,6 +67,7 @@ class TypingPool(Bureau): def run_io(self): val = "" upper = False + ctrl = False #TODO: this is crap, needs to be multi-threaded and have one # such loop for each active device self.active_devices[0].grab() @@ -74,7 +75,7 @@ class TypingPool(Bureau): for ev in self.active_devices[0].read_loop(): if ev.type == evdev.ecodes.EV_KEY: data = evdev.categorize(ev) - if data.keystate == 1: + if data.keystate == 1: # key-down if data.scancode == 28: print("sending barcode:", val) self.send(val[0:2], val[2:]) @@ -82,15 +83,27 @@ class TypingPool(Bureau): else: try: new_key = KEYS[data.scancode] - if new_key == "LSHFT" or new_key == "RSHFT": + if ctrl and (new_key == "j"): + self.log.debug("ignoring line-feed") + elif new_key == "LSHFT" or new_key == "RSHFT": upper = True + elif new_key == "LCTRL" or new_key == "RCTRL": + ctrl = True else: if upper: new_key = new_key.upper() - upper = False val += new_key except KeyError: print("Error invalid keycode:", data.scancode) + if data.keystate == 0: # key-up for mod-keys + try: + new_key = KEYS[data.scancode] + if new_key == "LSHFT" or new_key == "RSHFT": + upper = False + if new_key == "LCTRL" or new_key == "RCTRL": + ctrl = False + except KeyError: + print("Error invalid keycode:", data.scancode) def main():