Linux PyQt tray application to control OctoPrint instances
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

octotray.py 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. #!/usr/bin/env python3
  2. # OctoTray Linux Qt System Tray OctoPrint client
  3. #
  4. # depends on:
  5. # - python-pyqt5
  6. #
  7. # see also:
  8. # https://doc.qt.io/qt-5/qtwidgets-widgets-imageviewer-example.html
  9. # https://stackoverflow.com/a/22618496
  10. import json
  11. import sys
  12. import os
  13. import time
  14. import urllib.parse
  15. import urllib.request
  16. from os import path
  17. from PyQt5 import QtWidgets, QtGui, QtCore, QtNetwork
  18. from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox, QWidget, QLabel, QVBoxLayout, QHBoxLayout, QDesktopWidget, QSizePolicy, QSlider, QLayout, QTableWidget, QTableWidgetItem, QPushButton
  19. from PyQt5.QtGui import QIcon, QPixmap, QImageReader, QDesktopServices
  20. from PyQt5.QtCore import QCoreApplication, QSettings, QUrl, QTimer, QSize, Qt, QSettings
  21. class SettingsWindow(QWidget):
  22. def __init__(self, parent, *args, **kwargs):
  23. super(SettingsWindow, self).__init__(*args, **kwargs)
  24. self.parent = parent
  25. self.setWindowTitle(parent.name + " Settings")
  26. self.setWindowIcon(parent.icon)
  27. box = QVBoxLayout()
  28. self.setLayout(box)
  29. buttons = QHBoxLayout()
  30. box.addLayout(buttons, 0)
  31. self.add = QPushButton("&Add Printer")
  32. self.add.clicked.connect(self.addPrinter)
  33. buttons.addWidget(self.add)
  34. self.remove = QPushButton("&Remove Printer")
  35. self.remove.clicked.connect(self.removePrinter)
  36. buttons.addWidget(self.remove)
  37. printers = self.parent.readSettings()
  38. self.rows = len(printers)
  39. self.table = QTableWidget(self.rows, 2)
  40. box.addWidget(self.table, 1)
  41. for i in range(0, self.rows):
  42. p = printers[i]
  43. for j in range(0, 2):
  44. item = QTableWidgetItem(p[j])
  45. self.table.setItem(i, j, item)
  46. buttons2 = QHBoxLayout()
  47. box.addLayout(buttons2, 0)
  48. self.up = QPushButton("Move &Up")
  49. self.up.clicked.connect(self.moveUp)
  50. buttons2.addWidget(self.up)
  51. self.down = QPushButton("Move &Down")
  52. self.down.clicked.connect(self.moveDown)
  53. buttons2.addWidget(self.down)
  54. self.table.setHorizontalHeaderLabels(["Hostname", "API Key"])
  55. self.table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
  56. self.table.resizeColumnsToContents()
  57. if self.rows <= 0:
  58. self.addPrinter()
  59. def tableToList(self):
  60. printers = []
  61. for i in range(0, self.rows):
  62. p = [self.table.item(i, 0).text(), self.table.item(i, 1).text()]
  63. printers.append(p)
  64. return printers
  65. def closeEvent(self, event):
  66. oldPrinters = [item[0:2] for item in self.parent.printers]
  67. newPrinters = self.tableToList()
  68. if oldPrinters != newPrinters:
  69. r = self.parent.showDialog(self.parent.name + " Settings Changed", "Do you want to save the new list of printers?", "This will restart the application!", True, False, False)
  70. if r == True:
  71. self.parent.writeSettings(newPrinters)
  72. self.parent.restartApp()
  73. self.parent.removeSettingsWindow()
  74. def addPrinter(self):
  75. self.rows += 1
  76. self.table.setRowCount(self.rows)
  77. self.table.setItem(self.rows - 1, 0, QTableWidgetItem("HOSTNAME"))
  78. self.table.setItem(self.rows - 1, 1, QTableWidgetItem("API_KEY"))
  79. self.table.resizeColumnsToContents()
  80. def removePrinter(self):
  81. r = self.table.currentRow()
  82. if (r >= 0) and (r < self.rows):
  83. self.rows -= 1
  84. self.table.removeRow(r)
  85. def moveUp(self):
  86. i = self.table.currentRow()
  87. if i <= 0:
  88. return
  89. host = self.table.item(i, 0).text()
  90. key = self.table.item(i, 1).text()
  91. self.table.item(i, 0).setText(self.table.item(i - 1, 0).text())
  92. self.table.item(i, 1).setText(self.table.item(i - 1, 1).text())
  93. self.table.item(i - 1, 0).setText(host)
  94. self.table.item(i - 1, 1).setText(key)
  95. def moveDown(self):
  96. i = self.table.currentRow()
  97. if i >= (self.rows - 1):
  98. return
  99. host = self.table.item(i, 0).text()
  100. key = self.table.item(i, 1).text()
  101. self.table.item(i, 0).setText(self.table.item(i + 1, 0).text())
  102. self.table.item(i, 1).setText(self.table.item(i + 1, 1).text())
  103. self.table.item(i + 1, 0).setText(host)
  104. self.table.item(i + 1, 1).setText(key)
  105. class AspectRatioPixmapLabel(QLabel):
  106. def __init__(self, *args, **kwargs):
  107. super(AspectRatioPixmapLabel, self).__init__(*args, **kwargs)
  108. self.setMinimumSize(1, 1)
  109. self.setScaledContents(False)
  110. self.pix = QPixmap(0, 0)
  111. def setPixmap(self, p):
  112. self.pix = p
  113. super(AspectRatioPixmapLabel, self).setPixmap(self.scaledPixmap())
  114. def heightForWidth(self, width):
  115. if self.pix.isNull():
  116. return self.height()
  117. else:
  118. return (self.pix.height() * width) / self.pix.width()
  119. def sizeHint(self):
  120. w = self.width()
  121. return QSize(int(w), int(self.heightForWidth(w)))
  122. def scaledPixmap(self):
  123. return self.pix.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
  124. def resizeEvent(self, e):
  125. if not self.pix.isNull():
  126. super(AspectRatioPixmapLabel, self).setPixmap(self.scaledPixmap())
  127. class CamWindow(QWidget):
  128. reloadDelayDefault = 1000 # in ms
  129. statusDelay = 5 * 1000 # in ms
  130. reloadOn = True
  131. sliderFactor = 100
  132. def __init__(self, parent, printer, *args, **kwargs):
  133. super(CamWindow, self).__init__(*args, **kwargs)
  134. self.app = parent.app
  135. self.manager = parent.manager
  136. self.manager.finished.connect(self.handleResponse)
  137. self.parent = parent
  138. self.printer = printer
  139. self.host = self.printer[0]
  140. self.url = "http://" + self.host + ":8080/?action=snapshot"
  141. self.setWindowTitle(parent.name + " Webcam Stream")
  142. self.setWindowIcon(parent.icon)
  143. box = QVBoxLayout()
  144. self.setLayout(box)
  145. label = QLabel(self.url)
  146. box.addWidget(label, 0)
  147. box.setAlignment(label, Qt.AlignHCenter)
  148. self.img = AspectRatioPixmapLabel()
  149. self.img.setPixmap(QPixmap(640, 480))
  150. box.addWidget(self.img, 1)
  151. slide = QHBoxLayout()
  152. box.addLayout(slide, 0)
  153. self.slider = QSlider(Qt.Horizontal)
  154. self.slider.setMinimum(int(0 / self.sliderFactor))
  155. self.slider.setMaximum(int(2000 / self.sliderFactor))
  156. self.slider.setTickInterval(int(100 / self.sliderFactor))
  157. self.slider.setPageStep(int(100 / self.sliderFactor))
  158. self.slider.setSingleStep(int(100 / self.sliderFactor))
  159. self.slider.setTickPosition(QSlider.TicksBelow)
  160. self.slider.setValue(int(self.reloadDelayDefault / self.sliderFactor))
  161. self.slider.valueChanged.connect(self.sliderChanged)
  162. slide.addWidget(self.slider, 1)
  163. self.slideLabel = QLabel(str(self.reloadDelayDefault) + "ms")
  164. slide.addWidget(self.slideLabel, 0)
  165. self.statusLabel = QLabel("Status: unavailable")
  166. box.addWidget(self.statusLabel, 0)
  167. box.setAlignment(label, Qt.AlignHCenter)
  168. self.loadImage()
  169. self.loadStatus()
  170. def getHost(self):
  171. return self.host
  172. def sliderChanged(self):
  173. self.slideLabel.setText(str(self.slider.value() * self.sliderFactor) + "ms")
  174. def closeEvent(self, event):
  175. self.reloadOn = False
  176. self.url = ""
  177. self.parent.removeWebcamWindow(self)
  178. def scheduleLoadImage(self):
  179. if self.reloadOn:
  180. QTimer.singleShot(self.slider.value() * self.sliderFactor, self.loadImage)
  181. def scheduleLoadStatus(self):
  182. if self.reloadOn:
  183. QTimer.singleShot(self.statusDelay, self.loadStatus)
  184. def loadImage(self):
  185. url = QUrl(self.url)
  186. request = QtNetwork.QNetworkRequest(url)
  187. self.manager.get(request)
  188. def loadStatus(self):
  189. s = "Status: "
  190. t = self.parent.getTemperatureString(self.host, self.printer[1])
  191. if len(t) > 0:
  192. s += t
  193. else:
  194. s += "Unknown"
  195. progress = self.parent.getProgress(self.host, self.printer[1])
  196. if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
  197. s += " - %.1f%%" % progress["completion"]
  198. s += " - runtime "
  199. s += time.strftime("%H:%M:%S", time.gmtime(progress["printTime"]))
  200. s += " - "
  201. s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
  202. self.statusLabel.setText(s)
  203. self.scheduleLoadStatus()
  204. def handleResponse(self, reply):
  205. if reply.url().url() == self.url:
  206. if reply.error() == QtNetwork.QNetworkReply.NoError:
  207. reader = QImageReader(reply)
  208. reader.setAutoTransform(True)
  209. image = reader.read()
  210. if image != None:
  211. if image.colorSpace().isValid():
  212. image.convertToColorSpace(QColorSpace.SRgb)
  213. self.img.setPixmap(QPixmap.fromImage(image))
  214. self.scheduleLoadImage()
  215. else:
  216. print("Error decoding image: " + reader.errorString())
  217. else:
  218. print("Error loading image: " + reply.errorString())
  219. class OctoTray():
  220. name = "OctoTray"
  221. vendor = "xythobuz"
  222. version = "0.3"
  223. iconName = "octotray_icon.png"
  224. iconPaths = [
  225. path.abspath(path.dirname(__file__)),
  226. "data",
  227. "/usr/share/pixmaps",
  228. ".",
  229. "..",
  230. "../data"
  231. ]
  232. networkTimeout = 2.0 # in s
  233. # list of lists, inner lists contain printer data:
  234. # 0=host 1=key (2=system-commands 3=menu 4+=actions)
  235. printers = []
  236. statesWithWarning = [
  237. "Printing", "Pausing", "Paused"
  238. ]
  239. camWindows = []
  240. settingsWindow = None
  241. def __init__(self, app):
  242. QCoreApplication.setApplicationName(self.name)
  243. self.app = app
  244. if not QSystemTrayIcon.isSystemTrayAvailable():
  245. self.showDialog("OctoTray Error", "System Tray is not available on this platform!", "", False, False, True)
  246. sys.exit(0)
  247. self.manager = QtNetwork.QNetworkAccessManager()
  248. self.menu = QMenu()
  249. self.printers = self.readSettings()
  250. unknownCount = 0
  251. for p in self.printers:
  252. method = self.getMethod(p[0], p[1])
  253. print("Printer " + p[0] + " has method " + method)
  254. if method == "unknown":
  255. unknownCount += 1
  256. action = QAction(p[0])
  257. action.setEnabled(False)
  258. p.append(action)
  259. self.menu.addAction(action)
  260. continue
  261. commands = self.getSystemCommands(p[0], p[1])
  262. p.append(commands)
  263. menu = QMenu(self.getName(p[0], p[1]))
  264. p.append(menu)
  265. self.menu.addMenu(menu)
  266. if method == "psucontrol":
  267. action = QAction("Turn On PSU")
  268. action.triggered.connect(lambda chk, x=p: self.printerOnAction(x))
  269. p.append(action)
  270. menu.addAction(action)
  271. action = QAction("Turn Off PSU")
  272. action.triggered.connect(lambda chk, x=p: self.printerOffAction(x))
  273. p.append(action)
  274. menu.addAction(action)
  275. for i in range(0, len(commands)):
  276. action = QAction(commands[i].title())
  277. action.triggered.connect(lambda chk, x=p, y=i: self.printerSystemCommandAction(x, y))
  278. p.append(action)
  279. menu.addAction(action)
  280. menu.addSeparator()
  281. action = QAction("Get Status")
  282. action.triggered.connect(lambda chk, x=p: self.printerStatusAction(x))
  283. p.append(action)
  284. menu.addAction(action)
  285. action = QAction("Show Webcam")
  286. action.triggered.connect(lambda chk, x=p: self.printerWebcamAction(x))
  287. p.append(action)
  288. menu.addAction(action)
  289. action = QAction("Open Web UI")
  290. action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
  291. p.append(action)
  292. menu.addAction(action)
  293. self.menu.addSeparator()
  294. self.settingsAction = QAction("&Settings")
  295. self.settingsAction.triggered.connect(self.showSettingsAction)
  296. self.menu.addAction(self.settingsAction)
  297. self.refreshAction = QAction("&Refresh")
  298. self.refreshAction.triggered.connect(self.restartApp)
  299. self.menu.addAction(self.refreshAction)
  300. self.quitAction = QAction("&Quit")
  301. self.quitAction.triggered.connect(self.exit)
  302. self.menu.addAction(self.quitAction)
  303. self.iconPathName = None
  304. for p in self.iconPaths:
  305. if os.path.isfile(path.join(p, self.iconName)):
  306. self.iconPathName = path.join(p, self.iconName)
  307. break
  308. if self.iconPathName == None:
  309. self.showDialog("OctoTray Error", "Icon file has not been found!", "", False, False, True)
  310. sys.exit(0)
  311. self.icon = QIcon()
  312. self.pic = QPixmap(32, 32)
  313. self.pic.load(self.iconPathName)
  314. self.icon = QIcon(self.pic)
  315. self.trayIcon = QSystemTrayIcon(self.icon)
  316. self.trayIcon.setToolTip(self.name + " " + self.version)
  317. self.trayIcon.setContextMenu(self.menu)
  318. self.trayIcon.setVisible(True)
  319. def readSettings(self):
  320. settings = QSettings(self.vendor, self.name)
  321. printers = []
  322. l = settings.beginReadArray("printers")
  323. for i in range(0, l):
  324. settings.setArrayIndex(i)
  325. p = []
  326. p.append(settings.value("host"))
  327. p.append(settings.value("key"))
  328. printers.append(p)
  329. settings.endArray()
  330. return printers
  331. def writeSettings(self, printers):
  332. settings = QSettings(self.vendor, self.name)
  333. settings.remove("printers")
  334. settings.beginWriteArray("printers")
  335. for i in range(0, len(printers)):
  336. p = printers[i]
  337. settings.setArrayIndex(i)
  338. settings.setValue("host", p[0])
  339. settings.setValue("key", p[1])
  340. settings.endArray()
  341. del settings
  342. def openBrowser(self, url):
  343. QDesktopServices.openUrl(QUrl("http://" + url))
  344. def showDialog(self, title, text1, text2 = "", question = False, warning = False, error = False):
  345. msg = QMessageBox()
  346. if error:
  347. msg.setIcon(QMessageBox.Critical)
  348. elif warning:
  349. msg.setIcon(QMessageBox.Warning)
  350. elif question:
  351. msg.setIcon(QMessageBox.Question)
  352. else:
  353. msg.setIcon(QMessageBox.Information)
  354. msg.setWindowTitle(title)
  355. msg.setText(text1)
  356. if text2 is not None:
  357. msg.setInformativeText(text2)
  358. if question:
  359. msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
  360. else:
  361. msg.setStandardButtons(QMessageBox.Ok)
  362. retval = msg.exec_()
  363. if retval == QMessageBox.Yes:
  364. return True
  365. else:
  366. return False
  367. def sendRequest(self, host, headers, path, content = None):
  368. url = "http://" + host + "/api/" + path
  369. if content == None:
  370. request = urllib.request.Request(url, None, headers)
  371. else:
  372. data = content.encode('ascii')
  373. request = urllib.request.Request(url, data, headers)
  374. try:
  375. with urllib.request.urlopen(request, None, self.networkTimeout) as response:
  376. text = response.read()
  377. return text
  378. except (urllib.error.URLError, urllib.error.HTTPError) as error:
  379. print("Error requesting URL \"" + url + "\": \"" + str(error) + "\"")
  380. return "error"
  381. except socket.timeout:
  382. print("Timeout waiting for response to \"" + url + "\"")
  383. return "timeout"
  384. def sendPostRequest(self, host, key, path, content):
  385. headers = {
  386. "Content-Type": "application/json",
  387. "X-Api-Key": key
  388. }
  389. return self.sendRequest(host, headers, path, content)
  390. def sendGetRequest(self, host, key, path):
  391. headers = {
  392. "X-Api-Key": key
  393. }
  394. return self.sendRequest(host, headers, path)
  395. def getTemperatureIsSafe(self, host, key):
  396. r = self.sendGetRequest(host, key, "printer")
  397. try:
  398. rd = json.loads(r)
  399. if "temperature" in rd:
  400. if ("tool0" in rd["temperature"]) and ("actual" in rd["temperature"]["tool0"]):
  401. if rd["temperature"]["tool0"]["actual"] > 50.0:
  402. return False
  403. if ("tool1" in rd["temperature"]) and ("actual" in rd["temperature"]["tool1"]):
  404. if rd["temperature"]["tool1"]["actual"] > 50.0:
  405. return False
  406. except json.JSONDecodeError:
  407. pass
  408. return True
  409. def getTemperatureString(self, host, key):
  410. r = self.sendGetRequest(host, key, "printer")
  411. s = ""
  412. try:
  413. rd = json.loads(r)
  414. if ("state" in rd) and ("text" in rd["state"]):
  415. s += rd["state"]["text"]
  416. if "temperature" in rd:
  417. s += " - "
  418. if "temperature" in rd:
  419. if "bed" in rd["temperature"]:
  420. if "actual" in rd["temperature"]["bed"]:
  421. s += "B"
  422. s += "%.1f" % rd["temperature"]["bed"]["actual"]
  423. if "target" in rd["temperature"]["bed"]:
  424. s += "/"
  425. s += "%.1f" % rd["temperature"]["bed"]["target"]
  426. s += " "
  427. if "tool0" in rd["temperature"]:
  428. if "actual" in rd["temperature"]["tool0"]:
  429. s += "T"
  430. s += "%.1f" % rd["temperature"]["tool0"]["actual"]
  431. if "target" in rd["temperature"]["tool0"]:
  432. s += "/"
  433. s += "%.1f" % rd["temperature"]["tool0"]["target"]
  434. s += " "
  435. if "tool1" in rd["temperature"]:
  436. if "actual" in rd["temperature"]["tool1"]:
  437. s += "T"
  438. s += "%.1f" % rd["temperature"]["tool1"]["actual"]
  439. if "target" in rd["temperature"]["tool1"]:
  440. s += "/"
  441. s += "%.1f" % rd["temperature"]["tool1"]["target"]
  442. s += " "
  443. except json.JSONDecodeError:
  444. pass
  445. return s.strip()
  446. def getState(self, host, key):
  447. r = self.sendGetRequest(host, key, "job")
  448. try:
  449. rd = json.loads(r)
  450. if "state" in rd:
  451. return rd["state"]
  452. except json.JSONDecodeError:
  453. pass
  454. return "Unknown"
  455. def getProgress(self, host, key):
  456. r = self.sendGetRequest(host, key, "job")
  457. try:
  458. rd = json.loads(r)
  459. if "progress" in rd:
  460. return rd["progress"]
  461. except json.JSONDecodeError:
  462. pass
  463. return "Unknown"
  464. def getName(self, host, key):
  465. r = self.sendGetRequest(host, key, "printerprofiles")
  466. try:
  467. rd = json.loads(r)
  468. if "profiles" in rd:
  469. p = next(iter(rd["profiles"]))
  470. if "name" in rd["profiles"][p]:
  471. return rd["profiles"][p]["name"]
  472. except json.JSONDecodeError:
  473. pass
  474. return host
  475. def getMethod(self, host, key):
  476. r = self.sendGetRequest(host, key, "plugin/psucontrol")
  477. if r == "timeout":
  478. return "unknown"
  479. try:
  480. rd = json.loads(r)
  481. if "isPSUOn" in rd:
  482. return "psucontrol"
  483. except json.JSONDecodeError:
  484. pass
  485. r = self.sendGetRequest(host, key, "system/commands/custom")
  486. if r == "timeout":
  487. return "unknown"
  488. try:
  489. rd = json.loads(r)
  490. for c in rd:
  491. if "action" in c:
  492. # we have some custom commands and no psucontrol
  493. # so lets try to use that instead of skipping
  494. # the printer completely with 'unknown'
  495. return "system"
  496. except json.JSONDecodeError:
  497. pass
  498. return "unknown"
  499. def getSystemCommands(self, host, key):
  500. l = []
  501. r = self.sendGetRequest(host, key, "system/commands/custom")
  502. try:
  503. rd = json.loads(r)
  504. if len(rd) > 0:
  505. print("system commands available for " + host + ":")
  506. for c in rd:
  507. if "action" in c:
  508. print(" - " + c["action"])
  509. l.append(c["action"])
  510. except json.JSONDecodeError:
  511. pass
  512. return l
  513. def setPSUControl(self, host, key, state):
  514. cmd = "turnPSUOff"
  515. if state:
  516. cmd = "turnPSUOn"
  517. return self.sendPostRequest(host, key, "plugin/psucontrol", '{ "command":"' + cmd + '" }')
  518. def setSystemCommand(self, host, key, cmd):
  519. cmd = urllib.parse.quote(cmd)
  520. return self.sendPostRequest(host, key, "system/commands/custom/" + cmd, '')
  521. def exit(self):
  522. QCoreApplication.quit()
  523. def printerSystemCommandAction(self, item, index):
  524. if "off" in item[2][index].lower():
  525. state = self.getState(item[0], item[1])
  526. if state in self.statesWithWarning:
  527. if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to run '" + item[2][index] + "'?", True, True) == False:
  528. return
  529. safe = self.getTemperatureIsSafe(item[0], item[1])
  530. if safe == False:
  531. if self.showDialog("OctoTray Warning", "The printer seems to still be hot!", "Do you really want to turn it off?", True, True) == False:
  532. return
  533. self.setSystemCommand(item[0], item[1], item[2][index])
  534. def printerOnAction(self, item):
  535. self.setPSUControl(item[0], item[1], True)
  536. def printerOffAction(self, item):
  537. state = self.getState(item[0], item[1])
  538. if state in self.statesWithWarning:
  539. if self.showDialog("OctoTray Warning", "The printer seems to be running currently!", "Do you really want to turn it off?", True, True) == False:
  540. return
  541. safe = self.getTemperatureIsSafe(item[0], item[1])
  542. if safe == False:
  543. if self.showDialog("OctoTray Warning", "The printer seems to still be hot!", "Do you really want to turn it off?", True, True) == False:
  544. return
  545. self.setPSUControl(item[0], item[1], False)
  546. def printerWebAction(self, item):
  547. self.openBrowser(item[0])
  548. def printerStatusAction(self, item):
  549. progress = self.getProgress(item[0], item[1])
  550. s = item[0] + "\n"
  551. warning = False
  552. if ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress) and (progress["completion"] != None) and (progress["printTime"] != None) and (progress["printTimeLeft"] != None):
  553. s += "%.1f%% Completion\n" % progress["completion"]
  554. s += "Printing since " + time.strftime("%H:%M:%S", time.gmtime(progress["printTime"])) + "\n"
  555. s += time.strftime("%H:%M:%S", time.gmtime(progress["printTimeLeft"])) + " left"
  556. elif ("completion" in progress) and ("printTime" in progress) and ("printTimeLeft" in progress):
  557. s += "No job is currently running"
  558. else:
  559. s += "Could not read printer status!"
  560. warning = True
  561. t = self.getTemperatureString(item[0], item[1])
  562. if len(t) > 0:
  563. s += "\n" + t
  564. self.showDialog("OctoTray Status", s, None, False, warning)
  565. def printerWebcamAction(self, item):
  566. for cw in self.camWindows:
  567. if cw.getHost() == item[0]:
  568. cw.show()
  569. cw.activateWindow()
  570. return
  571. window = CamWindow(self, item)
  572. self.camWindows.append(window)
  573. window.show()
  574. window.activateWindow()
  575. screenGeometry = QDesktopWidget().screenGeometry()
  576. x = (screenGeometry.width() - window.width()) / 2
  577. y = (screenGeometry.height() - window.height()) / 2
  578. x += screenGeometry.x()
  579. y += screenGeometry.y()
  580. window.setGeometry(int(x), int(y), int(window.width()), int(window.height()))
  581. def removeWebcamWindow(self, window):
  582. self.camWindows.remove(window)
  583. def showSettingsAction(self):
  584. if self.settingsWindow != None:
  585. self.settingsWindow.show()
  586. self.settingsWindow.activateWindow()
  587. return
  588. self.settingsWindow = SettingsWindow(self)
  589. self.settingsWindow.show()
  590. self.settingsWindow.activateWindow()
  591. screenGeometry = QDesktopWidget().screenGeometry()
  592. x = (screenGeometry.width() - self.settingsWindow.width()) / 2
  593. y = (screenGeometry.height() - self.settingsWindow.height()) / 2
  594. x += screenGeometry.x()
  595. y += screenGeometry.y()
  596. self.settingsWindow.setGeometry(int(x), int(y), int(self.settingsWindow.width()), int(self.settingsWindow.height()) + 50)
  597. def removeSettingsWindow(self):
  598. self.settingsWindow = None
  599. def restartApp(self):
  600. QCoreApplication.exit(42)
  601. def closeAll(self):
  602. for cw in self.camWindows:
  603. cw.close()
  604. if self.settingsWindow != None:
  605. self.settingsWindow.close()
  606. self.trayIcon.setVisible(False)
  607. if __name__ == "__main__":
  608. app = QtWidgets.QApplication(sys.argv)
  609. tray = OctoTray(app)
  610. rc = app.exec_()
  611. while rc == 42:
  612. tray.closeAll()
  613. tray = OctoTray(app)
  614. rc = app.exec_()
  615. sys.exit(rc)