Linux PyQt tray application to control OctoPrint instances
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

OctoTray.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #!/usr/bin/env python3
  2. # OctoTray Linux Qt System Tray OctoPrint client
  3. #
  4. # OctoTray.py
  5. #
  6. # Main application logic.
  7. import sys
  8. from os import path
  9. from PyQt5 import QtNetwork
  10. from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QDesktopWidget
  11. from PyQt5.QtGui import QIcon, QPixmap, QDesktopServices, QCursor
  12. from PyQt5.QtCore import QCoreApplication, QSettings, QUrl
  13. from CamWindow import CamWindow
  14. from SettingsWindow import SettingsWindow
  15. from MainWindow import MainWindow
  16. from APIOctoprint import APIOctoprint
  17. class Printer(object):
  18. # field 'api' for actual I/O
  19. # field 'host' and 'key' for credentials
  20. pass
  21. class OctoTray():
  22. name = "OctoTray"
  23. vendor = "xythobuz"
  24. version = "0.5"
  25. iconName = "octotray_icon.png"
  26. iconPaths = [
  27. path.abspath(path.dirname(__file__)),
  28. "data",
  29. "/usr/share/pixmaps",
  30. ".",
  31. "..",
  32. "../data"
  33. ]
  34. networkTimeout = 2.0 # in s
  35. # list of Printer objects
  36. printers = []
  37. camWindows = []
  38. settingsWindow = None
  39. # default, can be overridden in config
  40. jogMoveSpeed = 10 * 60 # in mm/min
  41. jogMoveLength = 10 # in mm
  42. def __init__(self, app, inSysTray):
  43. QCoreApplication.setApplicationName(self.name)
  44. self.app = app
  45. self.inSysTray = inSysTray
  46. self.manager = QtNetwork.QNetworkAccessManager()
  47. self.menu = QMenu()
  48. self.printers = self.readSettings()
  49. unknownCount = 0
  50. for p in self.printers:
  51. p.api = APIOctoprint(self, p.host, p.key)
  52. p.menus = []
  53. commands = p.api.getAvailableCommands()
  54. # don't populate menu when no methods are available
  55. if len(commands) == 0:
  56. unknownCount += 1
  57. action = QAction(p.host)
  58. action.setEnabled(False)
  59. p.menus.append(action)
  60. self.menu.addAction(action)
  61. continue
  62. # top level menu for this printer
  63. menu = QMenu(p.api.getName())
  64. p.menus.append(menu)
  65. self.menu.addMenu(menu)
  66. # create action for all available commands
  67. for cmd in commands:
  68. name, func = cmd
  69. action = QAction(name)
  70. action.triggered.connect(lambda chk, p=p, n=name, f=func: p.api.f(n))
  71. p.menus.append(action)
  72. menu.addAction(action)
  73. if (p.tempTool != None) or (p.tempBed != None):
  74. menu.addSeparator()
  75. if p.tempTool != None:
  76. action = QAction("Preheat Tool")
  77. action.triggered.connect(lambda chk, p=p: p.api.printerHeatTool(p.tempTool))
  78. p.menus.append(action)
  79. menu.addAction(action)
  80. if p.tempBed != None:
  81. action = QAction("Preheat Bed")
  82. action.triggered.connect(lambda chk, p=p: p.api.printerHeatBed(p.tempBed))
  83. p.menus.append(action)
  84. menu.addAction(action)
  85. if (p.tempTool != None) or (p.tempBed != None):
  86. action = QAction("Cooldown")
  87. action.triggered.connect(lambda chk, p=p: p.api.printerCooldown())
  88. p.menus.append(action)
  89. menu.addAction(action)
  90. menu.addSeparator()
  91. fileMenu = QMenu("Recent Files")
  92. p.menus.append(fileMenu)
  93. menu.addMenu(fileMenu)
  94. files = p.api.getRecentFiles(10)
  95. for f in files:
  96. fileName, filePath = f
  97. action = QAction(fileName)
  98. action.triggered.connect(lambda chk, p=p, f=filePath: p.api.printFile(f))
  99. p.menus.append(action)
  100. fileMenu.addAction(action)
  101. action = QAction("Get Status")
  102. action.triggered.connect(lambda chk, p=p: p.api.statusDialog())
  103. p.menus.append(action)
  104. menu.addAction(action)
  105. action = QAction("Show Webcam")
  106. action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x))
  107. p.menus.append(action)
  108. menu.addAction(action)
  109. action = QAction("Open Web UI")
  110. action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
  111. p.menus.append(action)
  112. menu.addAction(action)
  113. self.menu.addSeparator()
  114. self.settingsAction = QAction("&Settings")
  115. self.settingsAction.triggered.connect(self.showSettingsAction)
  116. self.menu.addAction(self.settingsAction)
  117. self.refreshAction = QAction("&Refresh")
  118. self.refreshAction.triggered.connect(self.restartApp)
  119. self.menu.addAction(self.refreshAction)
  120. self.quitAction = QAction("&Quit")
  121. self.quitAction.triggered.connect(self.exit)
  122. self.menu.addAction(self.quitAction)
  123. self.iconPathName = None
  124. for p in self.iconPaths:
  125. if path.isfile(path.join(p, self.iconName)):
  126. self.iconPathName = path.join(p, self.iconName)
  127. break
  128. if self.iconPathName == None:
  129. self.showDialog("OctoTray Error", "Icon file has not been found!", "", False, False, True)
  130. sys.exit(0)
  131. self.icon = QIcon()
  132. self.pic = QPixmap(32, 32)
  133. self.pic.load(self.iconPathName)
  134. self.icon = QIcon(self.pic)
  135. if self.inSysTray:
  136. self.trayIcon = QSystemTrayIcon(self.icon)
  137. self.trayIcon.setToolTip(self.name + " " + self.version)
  138. self.trayIcon.setContextMenu(self.menu)
  139. self.trayIcon.activated.connect(self.showHide)
  140. self.trayIcon.setVisible(True)
  141. else:
  142. self.mainWindow = MainWindow(self)
  143. self.mainWindow.show()
  144. self.mainWindow.activateWindow()
  145. screenGeometry = QDesktopWidget().screenGeometry()
  146. x = (screenGeometry.width() - self.mainWindow.width()) / 2
  147. y = (screenGeometry.height() - self.mainWindow.height()) / 2
  148. x += screenGeometry.x()
  149. y += screenGeometry.y()
  150. self.mainWindow.setGeometry(int(x), int(y), int(self.mainWindow.width()), int(self.mainWindow.height()))
  151. def showHide(self, activationReason):
  152. if activationReason == QSystemTrayIcon.Trigger:
  153. self.menu.popup(QCursor.pos())
  154. elif activationReason == QSystemTrayIcon.MiddleClick:
  155. if len(self.printers) > 0:
  156. self.printerWebcamAction(self.printers[0])
  157. def readSettings(self):
  158. settings = QSettings(self.vendor, self.name)
  159. js = settings.value("jog_speed")
  160. if js != None:
  161. self.jogMoveSpeed = int(js)
  162. jl = settings.value("jog_length")
  163. if jl != None:
  164. self.jogMoveLength = int(jl)
  165. printers = []
  166. l = settings.beginReadArray("printers")
  167. for i in range(0, l):
  168. settings.setArrayIndex(i)
  169. p = Printer()
  170. p.host = settings.value("host")
  171. p.key = settings.value("key")
  172. p.tempTool = settings.value("tool_preheat")
  173. p.tempBed = settings.value("bed_preheat")
  174. printers.append(p)
  175. settings.endArray()
  176. return printers
  177. def writeSettings(self, printers):
  178. settings = QSettings(self.vendor, self.name)
  179. settings.setValue("jog_speed", self.jogMoveSpeed)
  180. settings.setValue("jog_length", self.jogMoveLength)
  181. settings.remove("printers")
  182. settings.beginWriteArray("printers")
  183. for i in range(0, len(printers)):
  184. p = printers[i]
  185. settings.setArrayIndex(i)
  186. settings.setValue("host", p.host)
  187. settings.setValue("key", p.key)
  188. settings.setValue("tool_preheat", p.tempTool)
  189. settings.setValue("bed_preheat", p.tempBed)
  190. settings.endArray()
  191. del settings
  192. def openBrowser(self, url):
  193. QDesktopServices.openUrl(QUrl("http://" + url))
  194. def showDialog(self, title, text1, text2 = "", question = False, warning = False, error = False):
  195. msg = QMessageBox()
  196. if error:
  197. msg.setIcon(QMessageBox.Critical)
  198. elif warning:
  199. msg.setIcon(QMessageBox.Warning)
  200. elif question:
  201. msg.setIcon(QMessageBox.Question)
  202. else:
  203. msg.setIcon(QMessageBox.Information)
  204. msg.setWindowTitle(title)
  205. msg.setText(text1)
  206. if text2 is not None:
  207. msg.setInformativeText(text2)
  208. if question:
  209. msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
  210. else:
  211. msg.setStandardButtons(QMessageBox.Ok)
  212. retval = msg.exec_()
  213. if retval == QMessageBox.Yes:
  214. return True
  215. else:
  216. return False
  217. def exit(self):
  218. QCoreApplication.quit()
  219. def printerWebAction(self, item):
  220. self.openBrowser(item.host)
  221. def printerWebcamAction(self, item):
  222. for cw in self.camWindows:
  223. if cw.getHost() == item.host:
  224. cw.show()
  225. cw.activateWindow()
  226. return
  227. window = CamWindow(self, item)
  228. self.camWindows.append(window)
  229. window.show()
  230. window.activateWindow()
  231. screenGeometry = QDesktopWidget().screenGeometry()
  232. x = (screenGeometry.width() - window.width()) / 2
  233. y = (screenGeometry.height() - window.height()) / 2
  234. x += screenGeometry.x()
  235. y += screenGeometry.y()
  236. window.setGeometry(int(x), int(y), int(window.width()), int(window.height()))
  237. def removeWebcamWindow(self, window):
  238. self.camWindows.remove(window)
  239. def showSettingsAction(self):
  240. if self.settingsWindow != None:
  241. self.settingsWindow.show()
  242. self.settingsWindow.activateWindow()
  243. return
  244. self.settingsWindow = SettingsWindow(self)
  245. self.settingsWindow.show()
  246. self.settingsWindow.activateWindow()
  247. screenGeometry = QDesktopWidget().screenGeometry()
  248. x = (screenGeometry.width() - self.settingsWindow.width()) / 2
  249. y = (screenGeometry.height() - self.settingsWindow.height()) / 2
  250. x += screenGeometry.x()
  251. y += screenGeometry.y()
  252. self.settingsWindow.setGeometry(int(x), int(y), int(self.settingsWindow.width()), int(self.settingsWindow.height()) + 50)
  253. def removeSettingsWindow(self):
  254. self.settingsWindow = None
  255. def restartApp(self):
  256. QCoreApplication.exit(42)
  257. def closeAll(self):
  258. for cw in self.camWindows:
  259. cw.close()
  260. if self.settingsWindow != None:
  261. self.settingsWindow.close()
  262. if self.inSysTray:
  263. self.trayIcon.setVisible(False)
  264. else:
  265. self.mainWindow.setVisible(False)