Linux PyQt tray application to control OctoPrint instances
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

octotray 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env python3
  2. # OctoTray Linux Qt System Tray OctoPrint client
  3. # depends on:
  4. # - python-pyqt5
  5. # - curl
  6. # - xdg-open
  7. import json
  8. import subprocess
  9. import sys
  10. import os
  11. import threading
  12. import time
  13. from PyQt5 import QtWidgets, QtGui, QtCore
  14. from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu, QMessageBox
  15. from PyQt5.QtGui import QIcon, QPixmap
  16. from PyQt5.QtCore import QCoreApplication, QSettings
  17. class OctoTray():
  18. name = "OctoTray"
  19. vendor = "xythobuz"
  20. version = "0.1"
  21. iconPath = "/usr/share/pixmaps/"
  22. iconName = "octotray_icon.png"
  23. # 0=host, 1=key
  24. # (2=method, 3=menu, 4=on-action, 5=off-action, 6=web-action)
  25. printers = [
  26. [ "PRINTER_HOST_HERE", "PRINTER_API_KEY_HERE" ]
  27. ]
  28. statesWithWarning = [
  29. "Printing", "Pausing", "Paused"
  30. ]
  31. def __init__(self):
  32. app = QtWidgets.QApplication(sys.argv)
  33. QCoreApplication.setApplicationName(self.name)
  34. if not QSystemTrayIcon.isSystemTrayAvailable():
  35. print("System Tray is not available on this platform!")
  36. sys.exit(0)
  37. self.menu = QMenu()
  38. for p in self.printers:
  39. method = self.getMethod(p[0], p[1])
  40. print("Printer " + p[0] + " has method " + method)
  41. p.append(method)
  42. if method == "unknown":
  43. continue
  44. menu = QMenu(self.getName(p[0], p[1]))
  45. p.append(menu)
  46. self.menu.addMenu(menu)
  47. action = QAction("Turn on")
  48. action.triggered.connect(lambda chk, x=p: self.printerOnAction(x))
  49. p.append(action)
  50. menu.addAction(action)
  51. action = QAction("Turn off")
  52. action.triggered.connect(lambda chk, x=p: self.printerOffAction(x))
  53. p.append(action)
  54. menu.addAction(action)
  55. action = QAction("Open Web UI")
  56. action.triggered.connect(lambda chk, x=p: self.printerWebAction(x))
  57. p.append(action)
  58. menu.addAction(action)
  59. self.quitAction = QAction("&Quit")
  60. self.quitAction.triggered.connect(self.exit)
  61. self.menu.addAction(self.quitAction)
  62. iconPathName = ""
  63. if os.path.isfile(self.iconName):
  64. iconPathName = self.iconName
  65. elif os.path.isfile(self.iconPath + self.iconName):
  66. iconPathName = self.iconPath + self.iconName
  67. else:
  68. print("no icon found")
  69. icon = QIcon()
  70. if iconPathName != "":
  71. pic = QPixmap(32, 32)
  72. pic.load(iconPathName)
  73. icon = QIcon(pic)
  74. trayIcon = QSystemTrayIcon(icon)
  75. trayIcon.setToolTip(self.name + " " + self.version)
  76. trayIcon.setContextMenu(self.menu)
  77. trayIcon.setVisible(True)
  78. sys.exit(app.exec_())
  79. def openBrowser(self, url):
  80. os.system("xdg-open http://" + url)
  81. def sendRequest(self, host, headers, path, content = None):
  82. cmdline = 'curl -s'
  83. for h in headers:
  84. cmdline += " -H \"" + h + "\""
  85. if content == None:
  86. cmdline += " -X GET"
  87. else:
  88. cmdline += " -X POST"
  89. cmdline += " -d '" + content + "'"
  90. cmdline += " http://" + host + "/api/" + path
  91. r = subprocess.run(cmdline, shell=True, capture_output=True, timeout=10, text=True)
  92. return r.stdout
  93. def sendPostRequest(self, host, key, path, content):
  94. headers = [ "Content-Type: application/json",
  95. "X-Api-Key: " + key ]
  96. return self.sendRequest(host, headers, path, content)
  97. def sendGetRequest(self, host, key, path):
  98. headers = [ "X-Api-Key: " + key ]
  99. return self.sendRequest(host, headers, path)
  100. def getStatus(self, host, key):
  101. r = self.sendGetRequest(host, key, "job")
  102. try:
  103. rd = json.loads(r)
  104. if "state" in rd:
  105. return rd["state"]
  106. except json.JSONDecodeError:
  107. pass
  108. return "Unknown"
  109. def getName(self, host, key):
  110. r = self.sendGetRequest(host, key, "printerprofiles")
  111. try:
  112. rd = json.loads(r)
  113. if "profiles" in rd:
  114. p = next(iter(rd["profiles"]))
  115. if "name" in rd["profiles"][p]:
  116. return rd["profiles"][p]["name"]
  117. except json.JSONDecodeError:
  118. pass
  119. return host
  120. def getMethod(self, host, key):
  121. r = self.sendGetRequest(host, key, "plugin/psucontrol")
  122. try:
  123. rd = json.loads(r)
  124. if "isPSUOn" in rd:
  125. return "psucontrol"
  126. except json.JSONDecodeError:
  127. pass
  128. r = self.sendGetRequest(host, key, "system/commands/custom")
  129. try:
  130. rd = json.loads(r)
  131. for c in rd:
  132. if "action" in c:
  133. if (c["action"] == "all off") or (c["action"] == "all on"):
  134. return "system"
  135. except json.JSONDecodeError:
  136. pass
  137. return "unknown"
  138. def setPSUControl(self, host, key, state):
  139. cmd = "turnPSUOff"
  140. if state:
  141. cmd = "turnPSUOn"
  142. return self.sendPostRequest(host, key, "plugin/psucontrol", '{ "command":"' + cmd + '" }')
  143. def setSystemCommand(self, host, key, state):
  144. cmd = "all%20off"
  145. if state:
  146. cmd = "all%20on"
  147. return self.sendPostRequest(host, key, "system/commands/custom/" + cmd, '')
  148. def setPrinter(self, host, key, method, state):
  149. if method == "psucontrol":
  150. return self.setPSUControl(host, key, state)
  151. elif method == "system":
  152. return self.setSystemCommand(host, key, state)
  153. else:
  154. return "error"
  155. def exit(self):
  156. QCoreApplication.quit()
  157. def printerOnAction(self, item):
  158. self.setPrinter(item[0], item[1], item[2], True)
  159. def printerOffAction(self, item):
  160. state = self.getStatus(item[0], item[1])
  161. if state in self.statesWithWarning:
  162. if self.showdialog() == True:
  163. self.setPrinter(item[0], item[1], item[2], False)
  164. else:
  165. self.setPrinter(item[0], item[1], item[2], False)
  166. def printerWebAction(self, item):
  167. self.openBrowser(item[0])
  168. def showdialog(self):
  169. msg = QMessageBox()
  170. msg.setIcon(QMessageBox.Warning)
  171. msg.setWindowTitle("OctoTray Warning")
  172. msg.setText("The printer seems to be running currently!")
  173. msg.setInformativeText("Do you really want to turn it off?")
  174. msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
  175. retval = msg.exec_()
  176. if retval == QMessageBox.Yes:
  177. return True
  178. return False
  179. if __name__ == "__main__":
  180. tray = OctoTray()