Browse Source

Initial commit

Thomas Buck 3 years ago
commit
8be7d4c3ee
6 changed files with 270 additions and 0 deletions
  1. 3
    0
      .gitignore
  2. 23
    0
      PKGBUILD
  3. 15
    0
      README.md
  4. 10
    0
      de.xythobuz.octotray.desktop
  5. 219
    0
      octotray
  6. BIN
      octotray_icon.png

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
1
+OctoTray-*-any.pkg.tar
2
+pkg
3
+src

+ 23
- 0
PKGBUILD View File

@@ -0,0 +1,23 @@
1
+# Maintainer: Thomas Buck <thomas@xythobuz.de>
2
+pkgname=OctoTray
3
+pkgver=0.1
4
+pkgrel=1
5
+pkgdesc="Control OctoPrint instances from system tray"
6
+arch=('any')
7
+license=('unknown')
8
+depends=('python-pyqt5')
9
+source=("octotray"
10
+        "octotray_icon.png"
11
+        "de.xythobuz.octotray.desktop")
12
+md5sums=(SKIP
13
+         SKIP
14
+         SKIP)
15
+
16
+package() {
17
+	mkdir -p "$pkgdir/usr/bin"
18
+	cp octotray "$pkgdir/usr/bin/octotray"
19
+	mkdir -p "$pkgdir/usr/share/pixmaps"
20
+	cp octotray_icon.png "$pkgdir/usr/share/pixmaps/octotray_icon.png"
21
+	mkdir -p "$pkgdir/usr/share/applications"
22
+	cp de.xythobuz.octotray.desktop "$pkgdir/usr/share/applications/de.xythobuz.octotray.desktop"
23
+}

+ 15
- 0
README.md View File

@@ -0,0 +1,15 @@
1
+# OctoTray Linux Qt client
2
+
3
+Simple Python Qt Linux client for OctoPrint. Install on Arch Linux like this:
4
+
5
+    makepkg
6
+    sudo pacman -U octotray-0.1-1-any.pkg.tar.xz
7
+
8
+Or on all other linux distros:
9
+
10
+	sudo cp octotray /usr/bin/octotray
11
+	sudo cp octotray_icon.png /usr/share/pixmaps/octotray_icon.png
12
+	sudo cp de.xythobuz.octotray.desktop /usr/share/applications/de.xythobuz.octotray.desktop
13
+
14
+Then run it from your desktop environment menu or even add it to the autostart there.
15
+

+ 10
- 0
de.xythobuz.octotray.desktop View File

@@ -0,0 +1,10 @@
1
+[Desktop Entry]
2
+Type=Application
3
+Version=1.0
4
+Name=OctoTray
5
+Comment=Control OctoPrint instances from system tray
6
+Path=/usr/bin
7
+Exec=octotray
8
+Icon=/usr/share/pixmaps/octotray_icon.png
9
+Terminal=false
10
+Categories=Utility;

+ 219
- 0
octotray View File

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

BIN
octotray_icon.png View File


Loading…
Cancel
Save