diff --git a/config.py b/config.py index 6279892..05cd852 100644 --- a/config.py +++ b/config.py @@ -11,6 +11,10 @@ config = { "PRINTER_USB_ID": "", "PRINTER_INTERFACE": 0, "PRINTER_ENDPOINT": 0x01, + "WEBDAV_USER": "", + "WEBDAV_PASSWORD": "", + "WEBDAV_SERVER": "", + "CALENDAR_PATH": "", } diff --git a/gui.py b/gui.py deleted file mode 100644 index c65411a..0000000 --- a/gui.py +++ /dev/null @@ -1,183 +0,0 @@ -# Python imports -import os.path -import sys - -# Gtk and Gobject -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gio - -# Internationalization support -import gettext -import locale - -# Printing library import -import smallprint - - -if os.path.exists('locale/po') and os.path.exists('res'): - # We're in the development tree - DIR = "locale/po/" - RESOURCES = "res/" - -else: - DIR = "po" - RESOURCES = "res/" - -# Internationalization code, we'll cross that bridge when we get there -# APP = "smallprint" -# -# -# locale.setlocale(locale.LC_ALL, '') -# -# gettext.bindtextdomain(APP, DIR) -# locale.bindtextdomain(APP, DIR) -# -# -# gettext.textdomain(APP) -# _ = gettext.gettext -# - - -class MainWindowMethods(Gtk.Application): - """ - Main Application object with the main window signals - """ - def __init__(self): - Gtk.Application.__init__(self, application_id="apps.gnome.smallprint", - flags=Gio.ApplicationFlags.FLAGS_NONE) - - self.connect("activate", self.on_app_start) - - def on_app_start(self, data=None): - """ - Loads the MainWindow widgets, shows it up and starts the main loop - """ - self.builder = Gtk.Builder() - # self.builder.set_translation_domain(APP) - self.builder.add_from_file(os.path.join(RESOURCES, 'smallprint.gtkbuilder')) - self.builder.connect_signals(self) - - # We get the window and show it - self.window = self.builder.get_object("MainWindow") - self.add_window(self.window) - self.window.show_all() - - def onOpenMenuClicked(self, event): - """ - User has pressed Open in the menu - """ - # Create the FileChooser Dialog - chooser = Gtk.FileChooserDialog(_("Open JSON text file"), self.window, - Gtk.FileChooserAction.OPEN, - (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) - - # Launch it - response = chooser.run() - - # If we choose a file, we load it - if response == Gtk.ResponseType.OK: - filename = chooser.get_filename() - label = self.builder.get_object("StatusLabel") - - # We update the statusbar accordingly to what has happened - if self.logicObj.loadjson(filename): - - label.set_text(filename) - treeStore = self.builder.get_object("treestore1") - treeStore.clear() - self.logicObj.loadTree(treeStore) - - else: - - label.set_text(_("No JSON loaded.")) - - # We finish the dialog - chooser.destroy() - - def onCopyJSONClicked(self, widget): - """ - User asked to paste a JSON code - """ - # Show up the TextWindow - textWindow = self.builder.get_object("TextWindow") - textWindow.show_all() - - def onCopyJSONDelete(self, widget, event): - """ - We've been told to close the CopyJSON Window - """ - textWindow = self.builder.get_object("TextWindow") - textWindow.hide() - - return True - - def onCopyJSONDestroy(self, widget): - """ - We've been tasked with removing the CopyJSON window - """ - textWindow = self.builder.get_object("TextWindow") - textWindow.hide() - - return True - - def onCopyJSONAcceptClicked(self, widget): - """ - The input is finished and accepted. - """ - textView = self.builder.get_object("textview1") - jsonBuffer = textView.get_buffer() - jsonText = jsonBuffer.get_text(jsonBuffer.get_start_iter(), - jsonBuffer.get_end_iter(), True) - - textWindow = self.builder.get_object("TextWindow") - textWindow.hide() - treestore = self.builder.get_object("treestore1") - treestore.clear() - - if self.logicObj.loadJSONText(jsonText): - - status_label = self.builder.get_object("StatusLabel") - status_label.set_text(_("Loaded from the clipboard.")) - self.logicObj.loadTree(treestore) - - def onCopyJSONCancelClicked(self, widget): - """ - The user changed its mind and pressed cancel - """ - textWindow = self.builder.get_object("TextWindow") - textWindow.hide() - - def onExitMenuClicked(self, widget): - """ - Menu option Exit has been clicked - """ - self.quit() - - def onAboutMenuActivate(self, widget): - """ - About option clicked - """ - about_dialog = self.builder.get_object("AboutDialog") - about_dialog.run() - about_dialog.hide() - - def onMainWindowDelete(self, widget, event): - """ - Our MainWindow has been deleted or closed - """ - pass - - def onAboutDialogClose(self, widget, event=None): - pass - - def onAboutDialogDeleteEvent(self, widget, event=None): - pass - - -# Main procedure -if __name__ == "__main__": - - mainWindow = MainWindowMethods() - mainWindow.run(None) diff --git a/requirements.txt b/requirements.txt index 8228dfc..acef573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,6 @@ ignition-gemini==0.1.7 importlib-metadata==1.7.0 Pillow==7.2.0 pycairo==1.19.1 -PyGObject==3.38.0 pyserial==3.4 python-escpos==2.2.0 pyusb==1.0.2 diff --git a/smallprint.py b/smallprint.py index ac3d283..e6a0a63 100644 --- a/smallprint.py +++ b/smallprint.py @@ -99,11 +99,19 @@ def print_weather(printer, city=None): printer.text(f"{description}\n\n") icon_code = weather['weather'][0]['icon'] - if not os.path.exists(f"icons/weather/{icon_code}.png"): - icon_path = "icons/weather/any.png" + dir_path = os.path.dirname(os.path.realpath(__file__)) # Get this file's current directory + + if not os.path.exists(os.path.join(dir_path, "icons/weather")): + dir_path = "/usr/share/smallprint/icons/weather" else: - icon_path = f"icons/weather/{icon_code}.png" + dir_path = os.path.join(dir_path, "icons/weather") + + if not os.path.exists(os.path.join(dir_path, f"{icon_code}.png")): + icon_path = os.path.join(dir_path, "any.png") + + else: + icon_path = os.path.join(dir_path, f"{icon_code}.png") # TODO: The image impl should be an option printer.image(icon_path, @@ -223,7 +231,7 @@ def print_rss(printer, link, news=3): """ reset_defaults(printer) feed = feedparser.parse(link) - title = feed["channel"]["title"] + title = feed["channel"].get("title", link)[:16] printer.set(align="center", text_type="BU", @@ -241,14 +249,17 @@ def _print_rss_item(item): title = item["title"] date = item["published"] text = clear_html(item["summary"]) + if len(text) > 255: + # Limit the text output in certain "summaries" + text = text[:252] + "...\n" link = item["link"] - printer.set(align="left", + printer.set(align="center", text_type="BU", smooth=True) printer.text(f"{title}\n") reset_defaults(printer) - printer.set(align="right", + printer.set(align="left", font="b") printer.text(f"{date}\n\n") printer.text(text) @@ -324,6 +335,44 @@ def print_image(printer, image): os.remove("temp.png") +def printing_script(printer): + """ + Gets the configuration and parses its script + """ + script = config.get("SCRIPT") + + if not script: + logger.error("No script in config file") + return + + for command in script: + key = list(command)[0] + value = command[key] + if key == 'WEATHER': + print_weather(printer, city=value) + + if key == 'RSS': + print_rss(printer, value) + + if key == 'GEMINI': + print_gemini(printer, value) + + if key == 'FILE': + print_file(printer, value) + + if key == 'IMAGE': + print_image(printer, value) + + if key == 'TEXT': + print_text(printer, value) + + printer.set(align="center", + text_type="NORMAL", + font="b", + smooth=True) + printer.text("-----\n") + + def clear_html(text): cleanr = re.compile('<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});') cleantext = re.sub(cleanr, '', text) @@ -355,25 +404,39 @@ if __name__ == "__main__": generate_qr = args.genqr + ops_called = 0 + if args.text: + ops_called += 1 print_text(printer, args.text[0]) printer.text("\n\n") if args.file: + ops_called += 1 print_file(printer, args.file) printer.text("\n\n") if args.weather: - print_weather(printer) + ops_called += 1 + print_weather(printer, args.weather) printer.text("\n\n") if args.gemini: + ops_called += 1 print_gemini(printer, args.gemini) printer.text("\n\n") if args.news: + ops_called += 1 print_rss(printer, args.news) if args.image: + ops_called += 1 print_image(printer, args.image[0]) printer.text("\n\n") + + # In case we get called (almost) empty + if ops_called == 0: + # Try to parse a config script + printing_script(printer) + printer.text("\n\n") diff --git a/webdav.py b/webdav.py new file mode 100644 index 0000000..538e71c --- /dev/null +++ b/webdav.py @@ -0,0 +1,57 @@ +import os +import os.path +import tempfile +from icalendar import Calendar, Event +from webdav3.client import Client as WDClient + + +class Client: + def __init__(self, config): + """ + Initializes a client + """ + options = { + 'webdav_hostname': config["WEBDAV_SERVER"], + 'webdav_login': config["WEBDAV_USER"], + 'webdav_password': config["WEBDAV_PASSWORD"], + } + self.calendar_path = config["CALENDAR_PATH"] + self.client = WDClient(options) + + def retrieve_calendars_list(self): + """ + Retrieves the calendar list + """ + return self.client.list(self.path) + + def read_calendar(self, pathname): + """ + Given a filename, tries to read the calendar event + """ + f, temp_path = tempfile.mkstemp() + rpath = os.path.join(self.calendar_path, pathname) + self.client.download_sync(remote_path=rpath, + local_path=temp_path) + # Read the temporary data, close the file, remove it + data = f.read() + f.close() + os.remove(temp_path) + # Now with the data read, we can process it + return self.parse_calendar(data) + + def parse_calendar(self, data): + """ + Parses ICS calendar data received + """ + calendar = Calendar.from_ical(data) + for component in calendar.walk(): + if component.name == 'VEVENT': + pass + + + + def get_todays_events(self): + """ + Get todays events + """ + pass