#!/usr/bin/env python3 # OctoTray Linux Qt System Tray OctoPrint client # # OctoTray.py # # Main application logic. import sys from os import path from PyQt5 import QtNetwork from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QDesktopWidget from PyQt5.QtGui import QIcon, QPixmap, QDesktopServices, QCursor from PyQt5.QtCore import QCoreApplication, QSettings, QUrl from CamWindow import CamWindow from SettingsWindow import SettingsWindow from MainWindow import MainWindow from APIOctoprint import APIOctoprint class Printer(object): # field 'api' for actual I/O # field 'host' and 'key' for credentials pass class OctoTray(): name = "OctoTray" vendor = "xythobuz" version = "0.5" iconName = "octotray_icon.png" iconPaths = [ path.abspath(path.dirname(__file__)), "data", "/usr/share/pixmaps", ".", "..", "../data" ] networkTimeout = 2.0 # in s # list of Printer objects printers = [] camWindows = [] settingsWindow = None # default, can be overridden in config jogMoveSpeed = 10 * 60 # in mm/min jogMoveLength = 10 # in mm def __init__(self, app, inSysTray): QCoreApplication.setApplicationName(self.name) self.app = app self.inSysTray = inSysTray self.manager = QtNetwork.QNetworkAccessManager() self.menu = QMenu() self.printers = self.readSettings() unknownCount = 0 for p in self.printers: p.api = APIOctoprint(self, p.host, p.key) p.menus = [] commands = p.api.getAvailableCommands() # don't populate menu when no methods are available if len(commands) == 0: unknownCount += 1 action = QAction(p.host) action.setEnabled(False) p.menus.append(action) self.menu.addAction(action) continue # top level menu for this printer menu = QMenu(p.api.getName()) p.menus.append(menu) self.menu.addMenu(menu) # create action for all available commands for cmd in commands: name, func = cmd action = QAction(name) action.triggered.connect(lambda chk, p=p, n=name, f=func: p.api.f(n)) p.menus.append(action) menu.addAction(action) if (p.tempTool != None) or (p.tempBed != None): menu.addSeparator() if p.tempTool != None: action = QAction("Preheat Tool") action.triggered.connect(lambda chk, p=p: p.api.printerHeatTool(p.tempTool)) p.menus.append(action) menu.addAction(action) if p.tempBed != None: action = QAction("Preheat Bed") action.triggered.connect(lambda chk, p=p: p.api.printerHeatBed(p.tempBed)) p.menus.append(action) menu.addAction(action) if (p.tempTool != None) or (p.tempBed != None): action = QAction("Cooldown") action.triggered.connect(lambda chk, p=p: p.api.printerCooldown()) p.menus.append(action) menu.addAction(action) menu.addSeparator() fileMenu = QMenu("Recent Files") p.menus.append(fileMenu) menu.addMenu(fileMenu) files = p.api.getRecentFiles(10) for f in files: fileName, filePath = f action = QAction(fileName) action.triggered.connect(lambda chk, p=p, f=filePath: p.api.printFile(f)) p.menus.append(action) fileMenu.addAction(action) action = QAction("Get Status") action.triggered.connect(lambda chk, p=p: p.api.statusDialog()) p.menus.append(action) menu.addAction(action) action = QAction("Show Webcam") action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x)) p.menus.append(action) menu.addAction(action) action = QAction("Open Web UI") action.triggered.connect(lambda chk, x=p: self.printerWebAction(x)) p.menus.append(action) menu.addAction(action) self.menu.addSeparator() self.settingsAction = QAction("&Settings") self.settingsAction.triggered.connect(self.showSettingsAction) self.menu.addAction(self.settingsAction) self.refreshAction = QAction("&Refresh") self.refreshAction.triggered.connect(self.restartApp) self.menu.addAction(self.refreshAction) self.quitAction = QAction("&Quit") self.quitAction.triggered.connect(self.exit) self.menu.addAction(self.quitAction) self.iconPathName = None for p in self.iconPaths: if path.isfile(path.join(p, self.iconName)): self.iconPathName = path.join(p, self.iconName) break if self.iconPathName == None: self.showDialog("OctoTray Error", "Icon file has not been found!", "", False, False, True) sys.exit(0) self.icon = QIcon() self.pic = QPixmap(32, 32) self.pic.load(self.iconPathName) self.icon = QIcon(self.pic) if self.inSysTray: self.trayIcon = QSystemTrayIcon(self.icon) self.trayIcon.setToolTip(self.name + " " + self.version) self.trayIcon.setContextMenu(self.menu) self.trayIcon.activated.connect(self.showHide) self.trayIcon.setVisible(True) else: self.mainWindow = MainWindow(self) self.mainWindow.show() self.mainWindow.activateWindow() screenGeometry = QDesktopWidget().screenGeometry() x = (screenGeometry.width() - self.mainWindow.width()) / 2 y = (screenGeometry.height() - self.mainWindow.height()) / 2 x += screenGeometry.x() y += screenGeometry.y() self.mainWindow.setGeometry(int(x), int(y), int(self.mainWindow.width()), int(self.mainWindow.height())) def showHide(self, activationReason): if activationReason == QSystemTrayIcon.Trigger: self.menu.popup(QCursor.pos()) elif activationReason == QSystemTrayIcon.MiddleClick: if len(self.printers) > 0: self.printerWebcamAction(self.printers[0]) def readSettings(self): settings = QSettings(self.vendor, self.name) js = settings.value("jog_speed") if js != None: self.jogMoveSpeed = int(js) jl = settings.value("jog_length") if jl != None: self.jogMoveLength = int(jl) printers = [] l = settings.beginReadArray("printers") for i in range(0, l): settings.setArrayIndex(i) p = Printer() p.host = settings.value("host") p.key = settings.value("key") p.tempTool = settings.value("tool_preheat") p.tempBed = settings.value("bed_preheat") printers.append(p) settings.endArray() return printers def writeSettings(self, printers): settings = QSettings(self.vendor, self.name) settings.setValue("jog_speed", self.jogMoveSpeed) settings.setValue("jog_length", self.jogMoveLength) settings.remove("printers") settings.beginWriteArray("printers") for i in range(0, len(printers)): p = printers[i] settings.setArrayIndex(i) settings.setValue("host", p.host) settings.setValue("key", p.key) settings.setValue("tool_preheat", p.tempTool) settings.setValue("bed_preheat", p.tempBed) settings.endArray() del settings def openBrowser(self, url): QDesktopServices.openUrl(QUrl("http://" + url)) def showDialog(self, title, text1, text2 = "", question = False, warning = False, error = False): msg = QMessageBox() if error: msg.setIcon(QMessageBox.Critical) elif warning: msg.setIcon(QMessageBox.Warning) elif question: msg.setIcon(QMessageBox.Question) else: msg.setIcon(QMessageBox.Information) msg.setWindowTitle(title) msg.setText(text1) if text2 is not None: msg.setInformativeText(text2) if question: msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) else: msg.setStandardButtons(QMessageBox.Ok) retval = msg.exec_() if retval == QMessageBox.Yes: return True else: return False def exit(self): QCoreApplication.quit() def printerWebAction(self, item): self.openBrowser(item.host) def printerWebcamAction(self, item): for cw in self.camWindows: if cw.getHost() == item.host: cw.show() cw.activateWindow() return window = CamWindow(self, item) self.camWindows.append(window) window.show() window.activateWindow() screenGeometry = QDesktopWidget().screenGeometry() x = (screenGeometry.width() - window.width()) / 2 y = (screenGeometry.height() - window.height()) / 2 x += screenGeometry.x() y += screenGeometry.y() window.setGeometry(int(x), int(y), int(window.width()), int(window.height())) def removeWebcamWindow(self, window): self.camWindows.remove(window) def showSettingsAction(self): if self.settingsWindow != None: self.settingsWindow.show() self.settingsWindow.activateWindow() return self.settingsWindow = SettingsWindow(self) self.settingsWindow.show() self.settingsWindow.activateWindow() screenGeometry = QDesktopWidget().screenGeometry() x = (screenGeometry.width() - self.settingsWindow.width()) / 2 y = (screenGeometry.height() - self.settingsWindow.height()) / 2 x += screenGeometry.x() y += screenGeometry.y() self.settingsWindow.setGeometry(int(x), int(y), int(self.settingsWindow.width()), int(self.settingsWindow.height()) + 50) def removeSettingsWindow(self): self.settingsWindow = None def restartApp(self): QCoreApplication.exit(42) def closeAll(self): for cw in self.camWindows: cw.close() if self.settingsWindow != None: self.settingsWindow.close() if self.inSysTray: self.trayIcon.setVisible(False) else: self.mainWindow.setVisible(False)