threading for tray icon application - python

I want to have a tray icon to inform me whether or not my COM port is plugged-in. It should change every 5 seconds according to the state of the COM port. I also want the ability to kill the program using the contextual menu of the tray icon. I figured out how to have the refreshing or the menu, but I don't know how to have both.
import sys
import glob
import serial
import time
from PyQt4 import QtGui, QtCore
import sys
import threading
from multiprocessing import Process, Queue
#script needing python 3.4 , pyserial (via pip) and pyqt4 (via .exe available online)
def serial_ports():
if sys.platform.startswith('win'):
ports = ['COM' + str(i + 1) for i in range(256)]
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except (OSError, serial.SerialException):
pass
return result
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
changeicon = menu.addAction("Update")
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
exitAction.triggered.connect(QtGui.qApp.quit)
changeicon.triggered.connect(self.updateIcon)
def updateIcon(self):
resultats = serial_ports()
icone = "red.ico"
for resultat in resultats:
if "COM3" in resultat:
icone = "green.ico"
break
self.setIcon(QtGui.QIcon(icone))
#update the icon (its color) according to the content of "resultat"
#missing code; purpose : wait 5 seconds while having the contextual menu of the tray icon still available
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon("red.ico"), w)
#always starts with red icon
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Found out using QTimer thanks to figs, seems repetitive and didn't understand everything but it works :
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
changeicon = menu.addAction("Update")
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
exitAction.triggered.connect(QtGui.qApp.quit)
changeicon.triggered.connect(self.updateIcon)
self.updateIcon()
def updateIcon(self):
try:
timer = QtCore.QTimer()
timer.timeout.connect(self.updateIcon)
timer.start(5000)
resultats = serial_ports()
icone = "red.ico"
for resultat in resultats:
if "COM3" in resultat:
icone = "green.ico"
break
self.setIcon(QtGui.QIcon(icone))
finally:
QtCore.QTimer.singleShot(5000,self.updateIcon)

Related

pyqt5 Embedded Terminal placed outside the new tab

I tried to enhanced this code The code embeds a terminal into a pyqt tab and sends command remotely with a button. I tried to make it so that a new tab with another embedded terminal instance is added when pressing a button. I successfully implemented this but the embedded terminal in the new tab isn't placed inside.
This is the code.
import time
from gi.repository import Wnck, Gdk
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
import uuid
gi.require_version('Wnck', '3.0')
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.sess_count = 0
self.embed('xterm')
def embed(self, command, *args):
self.name_session = uuid.uuid4().hex
print ("SESSION {0} ID: {1}".format(self.sess_count,self.name_session))
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
started, procId = QtCore.QProcess.startDetached(
"xterm", ["-e", "tmux", "new", "-s", self.name_session], "."
)
if not started:
QtWidgets.QMessageBox.critical(
self, 'Command "{}" not started!'.format(command), "Eh")
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# do a bit of sleep, else window is not really found
time.sleep(0.1)
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
print(attempts, w.get_pid(), procId, w.get_pid() == procId)
if w.get_pid() == procId:
#self.window = QtGui.QWindow.fromWinId(w.get_xid())
proc.setParent(self)
win32w = QtGui.QWindow.fromWinId(w.get_xid())
win32w.setFlags(QtCore.Qt.FramelessWindowHint)
widg = QtWidgets.QWidget.createWindowContainer(win32w)
self.addTab(widg, command)
#self.insertTab(self.sess_count, widg, command)
#widg.setFocusPolicy(QtCore.Qt.StrongFocus)
#self.currentIndex(self.sess_count)
self.resize(500, 400) # set initial size of window
# self.setFocus()
# self.update()
return
attempts += 1
QtWidgets.QMessageBox.critical(
self, 'Window not found', 'Process started but window not found')
def stop(self):
QtCore.QProcess.execute(
"tmux", ["kill-session", "-t", self.name_session])
def send_command(self, command):
QtCore.QProcess.execute(
"tmux", ["send-keys", "-t", self.name_session, command, "Enter"]
)
def add_terminal(self):
self.sess_count +=1
self.embed('xterm')
#self.addTab(EmbTerminal(),"")
#self.currentIndex(self.sess_count)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ifconfig_btn = QtWidgets.QPushButton("ifconfig")
self.ping_btn = QtWidgets.QPushButton("ping")
self.add_term_btn = QtWidgets.QPushButton("add terminal")
self.terminal = Container()
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QGridLayout(central_widget)
lay.addWidget(self.ifconfig_btn, 0, 0)
lay.addWidget(self.ping_btn, 0, 1)
lay.addWidget(self.add_term_btn, 1, 0, 1, 2)
lay.addWidget(self.terminal, 2, 0, 2, 2)
self.resize(640, 480)
self.ifconfig_btn.clicked.connect(self.launch_ifconfig)
self.ping_btn.clicked.connect(self.launch_ping)
self.add_term_btn.clicked.connect(self.terminal.add_terminal)
def launch_ifconfig(self):
self.terminal.send_command("ifconfig")
def launch_ping(self):
self.terminal.send_command("ping 8.8.8.8")
def closeEvent(self, event):
self.terminal.stop()
super().closeEvent(event)
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Also, I found a workaround to fix this, Video Link on the workaround, but as you can see, it involves interacting the GUI, not by code. I want to fix this via code.
When clicking the Add Terminal Button, the embedded terminal should be inside the new tab.

PyQt Messagebox is crashing

I'm working on a file transfer application including its server. When I try to send a file between two clients, I want to ensure that the receiving client will get a message box like "x user wants to send a file. Do you accept?". I accomplished this so far but when I clicked the "Yes" button for testing purposes, the Yes button disappear and the receiving client collapses. When I tried to view the error on console, I saw that it's "QObject::setParent: Cannot set parent, new parent is in a different thread" on the receiving client. I searched the error on the site but couldn't make sense out of the solutions. Could you explain to me how I can solve this?
Code:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import socket
import json
import base64
from threading import Thread
from time import sleep
username = "admin"
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("144.122.86.204",5000)) #Fill here later
s.send(username.encode("utf-8"))
app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle("File Sharing")
window.setGeometry(0,0,500,350)
contactlist= QListWidget(window)
contactlist.setGeometry(5,5,100,250)
def add():
user, ok = QInputDialog.getText(window, "Add a contact","Enter a
username:")
if ok:
contactlist.addItem(str(user))
def send():
target_user=contactlist.currentItem().text()
name = QFileDialog.getOpenFileName(window, 'Open File')
file = open(name[0], 'rb')
base64_bytes = base64.b64encode(file.read())
base64_string = base64_bytes.decode('utf-8')
data= {"FRM": username, "TO": target_user, "DATA": base64_string}
encoded_data = json.dumps(data).encode()
s.sendall(encoded_data)
def receive():
while True:
data = s.recv(1024)
if data:
if 'wants to send you a file. Do you accept that?' in
data.decode('utf-8'):
choice = QMessageBox.question(window, 'FileTransfer',
data.decode('utf-8'),QMessageBox. Yes|QMessageBox. No)
if choice == QMessageBox.Yes:
s.send("CONFIRMED".encode('utf-8'))
t = Thread(target=receive)
t.start()
add_contact = QPushButton("Add a Contact", window)
add_contact.setGeometry(5,270,100,50)
add_contact.clicked.connect(add)
send_file = QPushButton("Send a File",window)
send_file.setGeometry(110,270,200,50)
send_file.clicked.connect(send)
window.show()
sys.exit(app.exec())
The problem in your case is that QMessageBox is being created in another thread which Qt prohibits, and its parent window lives in the main thread what Qt also forbids. The general approach in this case is to send the information of the secondary thread to the main thread through signals, events, metaobjects, etc. that are thread-safe and in the main thread is to create the GUI (in your case QMessageBox).
In this case it can be complicated so instead of using the above it is best to use the QtNetwork module of Qt that will send you the information received through signals making the while loop unnecessary and therefore the use of threads.
import json
import base64
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.initUi()
self.m_username = "admin"
self.m_socket = QtNetwork.QTcpSocket(self)
self.m_socket.connected.connect(self.onConnected)
self.m_socket.readyRead.connect(self.onReadyRead)
self.m_socket.connectToHost("144.122.86.204", 5000)
#QtCore.pyqtSlot()
def onConnected(self):
username = "admin"
self.m_socket.write(self.m_username.encode("utf-8"))
self.m_socket.flush()
#QtCore.pyqtSlot()
def onReadyRead(self):
data = self.m_socket.readAll().data()
text = data.decode("utf-8")
if "wants to send you a file. Do you accept that?" in text:
choice = QtWidgets.QMessageBox.question(
self,
"FileTransfer",
text,
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
)
if choice == QtWidgets.QMessageBox.Yes:
self.m_socket.write("CONFIRMED".encode("utf-8"))
#QtCore.pyqtSlot()
def add_contant(self):
user, ok = QtWidgets.QInputDialog.getText(
self, "Add a contact", "Enter a username:"
)
if ok:
self.m_listwidget.addItem(user)
#QtCore.pyqtSlot()
def send_file(self):
if self.m_socket.state() != QtNetwork.QAbstractSocket.ConnectedState:
print("Socket not connected")
return
item = self.m_listwidget.currentItem()
if item is None:
print("Not current item")
return
target_user = item.text()
filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
with open(filename, "rb") as file:
base64_bytes = base64.b64encode(file.read())
base64_string = base64_bytes.decode("utf-8")
data = {"FRM": self.m_username, "TO": target_user, "DATA": base64_string}
encoded_data = json.dumps(data).encode()
self.m_socket.write(encoded_data)
self.m_socket.flush()
def initUi(self):
self.m_listwidget = QtWidgets.QListWidget()
self.m_listwidget.setFixedWidth(100)
self.m_add_button = QtWidgets.QPushButton("Add a Contact")
self.m_add_button.clicked.connect(self.add_contant)
self.m_add_button.setFixedSize(100, 50)
self.m_send_file = QtWidgets.QPushButton("Send a File")
self.m_send_file.clicked.connect(self.send_file)
self.m_send_file.setFixedSize(200, 50)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(self.m_listwidget)
hlay.addStretch()
hlay2 = QtWidgets.QHBoxLayout()
hlay2.addWidget(self.m_add_button)
hlay2.addWidget(self.m_send_file)
hlay2.addStretch()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addLayout(hlay)
vlay.addLayout(hlay2)
self.resize(500, 350)
def closeEvent(self, event):
if self.m_socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
self.m_socket.disconnectFromHost()
super(Widget, self).closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec())

PyQT GUI Freeze with Infinite Loop in QThread

I'm new to PyQT and QThread. My PyQT program has a button which trigger a continuous writing/reading of the serial port every second.
Problem: When the button is clicked and the looping starts, the GUI freezes up. Am I using QThread wrongly?
import sys
from PyQt4.QtGui import *
from PyQt4 import QtCore
import serial
import time
from TemperatureReader import TemperatureReader # my module to talk to serial hardware
class Screen(QMainWindow):
def __init__(self):
super(Screen, self).__init__()
self.initTemperatureReader()
self.initUI()
def initTemperatureReader(self):
ser = serial.Serial(port='COM9', baudrate=115200, timeout=5)
self.temperatureReader = TemperatureReader(ser)
def initUI(self):
startReadingTCsBtn = QPushButton('Start Reading')
startReadingTCsBtn.clicked.connect(self.startReadingTCsThread)
startReadingTCsBtn.show()
directControlBoxLayout = QVBoxLayout()
directControlBoxLayout.addWidget(startReadingTCsBtn)
self.mainFrame = QWidget()
mainLayout = QVBoxLayout()
mainLayout.addWidget(directControlGroupBox)
self.mainFrame.setLayout(mainLayout)
self.setCentralWidget(self.mainFrame)
self.setGeometry(300,300,400,150)
self.show()
def startReadingTCsThread(self):
self.tcReaderThread = TCReaderThread(self.temperatureReader)
self.tcReaderThread.temperatures.connect(self.onTemperatureDataReady)
self.tcReaderThread.start()
def onTemperatureDataReady(self, data):
print data
class TCReaderThread(QtCore.QThread):
temperatures = QtCore.pyqtSignal(object)
def __init__(self, temperatureReader):
QtCore.QThread.__init__(self)
self.temperatureReader = temperatureReader
def run(self):
while True:
resultString = self.temperatureReader.getTemperature() # returns a strng
self.temperatures.emit(resultString)
time.sleep(1)
def main():
app = QApplication(sys.argv)
screen = Screen()
app.exec_()
if __name__ == '__main__':
main()

Python and QsystemTray application

I have a code which runs on KDE system very well.
On Unity (Ubuntu 16.04 LTS) this code produces unexpected result.
Result on Unity:
wrong result on Unity
Bus the same code produces good result on KDE system:
Link to a good result
Question: Why the same code does not work on Unity?
And a code:
import sys
from PyQt4 import QtGui
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
edit = QtGui.QLineEdit(parent)
edit.setText("Tekstas kuris turi būti atsiradęs čia")
hl = QtGui.QVBoxLayout(parent)
hl.addWidget(QtGui.QLabel("Testuojame"))
hl.addWidget(edit)
w = QtGui.QWidget(parent)
w.setLayout(hl)
wa = QtGui.QWidgetAction(parent)
wa.setDefaultWidget(w)
menu.addAction(wa)
exitAction = menu.addAction("Blabla")
exitAction = menu.addAction("Blabla 2")
self.setContextMenu(menu)
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon("icons/close.png"), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I see the same behaviour on KDE 5.26.0 (Qt 5.6.1, Ubuntu 16.10), the defaultWidget is not shown in the context menu, only the (empty) iconText of the QWidgetAction is displayed. The way the context menu is shown is ultimately controlled by the tray (which is specific to the used desktop environment).
To get this to work consitently you can show your menu as a popup menu on activation instead of in the context menu. The difference is that it isn't activated on right click but on left click and that it may look different from the native trays context menu.
Your example would then look like this:
import sys
from PyQt4 import QtGui
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = menu = QtGui.QMenu()
edit = QtGui.QLineEdit()
edit.setText("Tekstas kuris turi būti atsiradęs čia")
w = QtGui.QWidget()
hl = QtGui.QVBoxLayout()
w.setLayout(hl)
hl.addWidget(QtGui.QLabel("Testuojame"))
hl.addWidget(edit)
wa = QtGui.QWidgetAction(menu)
wa.setDefaultWidget(w)
menu.addAction(wa)
exitAction = menu.addAction("Blabla")
exitAction = menu.addAction("Blabla 2")
self.activated.connect(self.showPopupMenu)
def showPopupMenu(self, reason):
if reason == QtGui.QSystemTrayIcon.Trigger:
self.menu.popup(QtGui.QCursor.pos())
def main():
app = QtGui.QApplication(sys.argv)
trayIcon = SystemTrayIcon(QtGui.QIcon("icons/close.png"))
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

How to add tabs is Qtabwidget in a new process?

I want to add a tab in a new process on button click event and terminate it after sometime.
On Button click a new process is created but tabs are not created.
Please help me regrading this issue!!!
Here is what is tried:
from PyQt4 import Qt, QtCore, QtGui
import sys
import multiprocessing
class createProc(multiprocessing.Process):
def __init__(self,mnWndObj):
super(createProc,self).__init__()
self.Obj = mnWndObj
def run(self):
print "Process is being created!!!"
tab = QtGui.QWidget()
self.Obj.tabwnd.addTab(tab,"tab")
class MainWnd(QtGui.QWidget):
def __init__(self,parent=None):
super(MainWnd,self).__init__(parent)
self.layout = QtGui.QVBoxLayout()
self.tabwnd = QtGui.QTabWidget()
self.webwnd = Qt.QWebView()
self.webwnd.load(QtCore.QUrl("https://www.google.co.in/"))
self.webwnd.show()
self.btn = QtGui.QPushButton("Create Process")
self.layout.addWidget(self.btn)
self.layout.addWidget(self.tabwnd)
self.layout.addWidget(self.webwnd)
self.setLayout(self.layout)
self.btn.clicked.connect(self.crProc)
def crProc(self):
p = createProc(self)
p.start()
print "Process Name",p.name
if __name__=="__main__":
app = Qt.QApplication(sys.argv)
m = MainWnd()
m.show()
app.exec_()

Categories

Resources