StatusIcon with threaded loop: loop does not loop - multithreading

I have created a simple status icon.
Its purpose is to check the existence of files in a given time interval.
The status icon shows, and the thread is started.
However, the loop does not loop: it enters the loop, but on event.wait it halts, and never continues.
I've tried to replace with time.sleep(5), but that had the same result.
I've searched anywhere, but I couldn't find an answer.
What am I missing?
#! /usr/bin/env python3
#-*- coding: utf-8 -*-
from gi.repository import Gtk
import threading
class CheckStatus(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.event = threading.Event()
def run(self):
cnt = 0
while not self.event.is_set():
cnt += 1
print((">>> check %d" % cnt)) # This shows just once
self.event.wait(5)
print(">>> done")
def stop(self):
self.event.set()
class MyStatusIcon(object):
def __init__(self):
self.statusIcon = Gtk.StatusIcon()
self.statusIcon.set_from_stock(Gtk.STOCK_ABOUT)
# Build status icon menu
menu = Gtk.Menu()
menuQuit = Gtk.MenuItem("Quit")
menuQuit.connect('activate', self.quit_tray)
menu.append(menuQuit)
self.statusIcon.connect('popup-menu', self.popup_menu, menu)
# Start thread
self.check = CheckStatus()
self.check.start()
def popup_menu(self, widget, button, time, data):
data.show_all()
data.popup(None, None, None, None, button, time)
def quit_tray(self, widget):
# Stop the thread and quit
self.check.stop()
self.check.join()
Gtk.main_quit()
if __name__ == '__main__':
# Create an instance of our GTK application
try:
MyStatusIcon()
Gtk.main()
except:
pass
[EDIT 1]
I've replaced the while loop with a counter loop (while cnt < 10), although this didn't change the behavior, it showed the printed counter every 5 seconds as soon as I quit the status icon.
It seems that the thread is waiting for an event from the status icon.
[EDIT 2]
I needed to look at this from a different angle, and wondered if GObject.timeout_add could solve this issue...well, it did, but I don't know if it's the most elegant way to do so.
#! /usr/bin/env python3
#-*- coding: utf-8 -*-
from gi.repository import Gtk, GObject
class MyStatusIcon(object):
def __init__(self):
self.quit = False
self.counter = 0
self.service = GObject.timeout_add(5000, self.checkStatus)
self.statusIcon = Gtk.StatusIcon()
self.statusIcon.set_from_stock(Gtk.STOCK_ABOUT)
# Build status icon menu
menu = Gtk.Menu()
menuQuit = Gtk.MenuItem("Quit")
menuQuit.connect('activate', self.quit_tray)
menu.append(menuQuit)
self.statusIcon.connect('popup-menu', self.popup_menu, menu)
def checkStatus(self):
self.counter += 1
if not self.quit:
print((">>> check %d" % self.counter))
return True
print("Done")
return False
def popup_menu(self, widget, button, time, data):
data.show_all()
data.popup(None, None, None, None, button, time)
def quit_tray(self, widget):
# Stop the thread and quit
self.quit = True
Gtk.main_quit()
if __name__ == '__main__':
# Create an instance of our GTK application
try:
MyStatusIcon()
Gtk.main()
except:
pass

Related

Python kivy and multiprocessing bug

I am stuck on writing a very simple kivy gui with 2 buttons.
button_1 launches the countdown in multiprocessing. It works.
button_2 is supposed to end the countdown in multiprocessing. That does not work...
Can anyone please point out what I am doing wrong and why?
Thank you kindly in advance.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import multiprocessing
import time
class MyApp(App):
list_of_objects_in_class = []
def build(self):
layout = BoxLayout()
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
#staticmethod
def actual_countdown_function(*args):
print('I am launching the countdown!')
for count in range(5, 0, -1):
print(count)
time.sleep(1)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
MyApp.list_of_objects_in_class.append(multiprocessing.Process(target=MyApp.actual_countdown_function, args=()))
MyApp.list_of_objects_in_class[0].start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
try:
MyApp.list_of_objects_in_class[0].terminate()
MyApp.list_of_objects_in_class.clear()
except:
pass
if __name__ == "__main__":
MyApp().run()
I tested to see if .terminate() works on my system, it works. The script below terminates successfully but the script above does NOT...
import multiprocessing
import time
def print_one():
time.sleep(3)
print('This does NOT get printed because proc.terminate() works on my Linux Mint OS')
def print_two():
print('This gets printed')
process_to_terminate = multiprocessing.Process(target=print_one, args=())
process_to_terminate.start()
process_to_keep_and_NOT_terminate = multiprocessing.Process(target=print_two, args=())
process_to_keep_and_NOT_terminate.start()
process_to_terminate.terminate() # Works and kills print_one function
It's better to use threads indeed to run such a small tasks.
Anyway, I think, the original problem could be solved by setting SIGTERM handler in a child process, because it seems to be ignoring SIGTERM by default:
def actual_countdown_function(self, *args):
+ signal.signal(signal.SIGTERM, lambda *_, **__: sys.exit(0))
print('I am launching the countdown!')
But, TBH, if you will really need a way to manage child process, I strongly recommend to use Locks/Conditions/Queues instead. They have almost the same API like all the threading primitives, but built to interact over pipes/shared-memory/sockets/etc.
after many different attempts, I was able to solve my own problem by using threading instead of multiprocessing.
Here is the code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import threading
import time
class MyApp(App):
my_var = True
list_of_objects_in_class = []
def build(self):
layout = BoxLayout(size =(300, 300))
button_1 = Button(text='launch_countdown', on_press=self.launch_countdown)
button_2 = Button(text='end_the_countdown', on_press=self.end_the_countdown, height = "100", width = 140)
layout.add_widget(button_1)
layout.add_widget(button_2)
return layout
def actual_countdown_function(self, *args):
print('I am launching the countdown!')
x = 5
while x != 0:
new_var = self.my_var
if new_var == False:
break
time.sleep(1)
print(x, new_var)
x -=1
del(new_var)
print('Countdown Finished!!!') # When I press end_the_countdown button, this is NOT supposed to be printed.
def launch_countdown(self, *args):
t1 = threading.Thread(target=self.actual_countdown_function)
t1.start()
def end_the_countdown(self, *args):
print('I am terminating the countdown early!')
self.my_var = not self.my_var
print(self.my_var)
self.list_of_objects_in_class.clear()
if __name__ == "__main__":
MyApp().run()

wxPython threading - GUI freezes while doing an animation in GLCanvas

What is the proper way of refreshing a GLCanvas (with heavy 3D geometries) without freezing the GUI?
I'm trying to use a set of GUI elements (buttons, checkboxes) to control a GLCanvas which is refreshed to display a 3D animation. My problem is that the GUI elements are not responsive when the animation loop is on.
I've tried to launch the animation loop in three different ways:
Option 1: within a thread
Option 2: using wx.lib.delayedresult (most likely similar to thread)
Option 3: calling Refresh after the onDraw event
It seems I can get the Option 1 to work, but I need to introduce a sleep delay with time.sleep(xxx) in between the call to the Refresh of the canvas. Otherwise the GUI remains poorly responsive: resizing the window, will end up "graying" the GUI elements, clicking on the checkboxes will trigger events but not the "checking" of the box, the "mouseover" effect over buttons does not work, etc.
I enclose a small working example. In my actual application, I animate a 3D geometry using moderngl, and I noticed that the "sleep" time required varies depending on how heavy the geometry is (as opposed to this example which is extremely lightweight and works with a delay as small as 0.00001s). I'm wondering what I'm missing. Thanks!
import wx
from wx import glcanvas
import wx.lib.delayedresult as delayedresult
from OpenGL.GL import *
import OpenGL.GL.shaders
import time
import numpy as np
from threading import Thread
# --- Option 1: Thread
class TestThread(Thread):
def __init__(self, parent, canvas):
Thread.__init__(self)
self.parent=parent
self.canvas=canvas
self.start() # start the thread
def run(self):
print('Thread running... ')
while self.canvas.animate:
#time.sleep(0.01) # <<<<<<<<<<<< This line needed
self.canvas.Refresh()
print('Tread done ')
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(400,400))
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.init = False
self.animate = False
self.refreshAfter = False
self.t=0
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
wx.PaintDC(self)
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
glEnable(GL_DEPTH_TEST)
def OnDraw(self):
""" Called at every frame"""
glClearColor(0.1, 0.0, np.mod(self.t,1), 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self.animate:
self.t+=0.0005 # increment time
# ---- Option 3
if self.refreshAfter:
self.Refresh() # Trigger next frame
self.SwapBuffers()
# --- Option 2: delayed results
def onAnimDelayedEnd(self, thread):
""" Consumer """
print('Delayed result done')
jobID = thread.getJobID()
result = thread.get()
def onAnimDelayedStart(self):
print('Delayed result running... ')
while self.animate:
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
# GUI
self.rot_btn = wx.Button(self, -1, label="Toggle animation", pos=(430, 10))
self.cbDo = wx.CheckBox (self, -1, label="Do something", pos=(430 ,100))
self.radDo = wx.RadioButton(self, -1, label="Do something", pos=(430,140))
self.radDo2= wx.RadioButton(self, -1, label="Do something", pos=(430,180))
# Binding
self.rot_btn.Bind(wx.EVT_BUTTON, self.toggleAnim)
self.radDo.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.radDo2.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.cbDo.Bind(wx.EVT_CHECKBOX , self.doSomething)
def toggleAnim(self, event):
if not self.canvas.animate:
self.canvas.animate = True
# --- Option 1: thread
TestThread(self, self.canvas)
# --- Option 2: delayed result
#delayedresult.startWorker(self.canvas.onAnimDelayedEnd, self.canvas.onAnimDelayedStart, jobID=1)
# --- Option 3: refreshloop
#self.canvas.refreshAfter=True
#self.canvas.Refresh() # set the canvas into an "infinite" refresh loop
else:
self.canvas.animate = False
def doSomething(self, event):
print('Do something')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="My wx frame", size=(600,400))
self.Bind(wx.EVT_CLOSE, self.on_close)
self.panel = MyPanel(self)
def on_close(self, event):
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame().Show()
app.MainLoop()

a serial port thread in pyQT5

The following is my code. I am trying to create a serial port thread in my pyQt5 GUI. I checked many examples. This is very helpful one Background thread with QThread in PyQt
I think I made a successful connection but I am confused about the signature.
SelectedItem is the selected port. The GUI is still freezing because of while loop.
I need some help. Thanks!
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# vim:fileencoding=utf-8
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from TTP_Monitor_GUI import Ui_MainWindow
import serial
import serial.tools.list_ports
import binascii
import glob
class SerialPort(QObject):
def __init__(self, parent = None):
super(SerialPort, self).__init__(parent)
# Explicit signal
newParams = pyqtSignal(str)
#pyqtSlot(str)
def ReadSerialPort(self, port):
#initialization and open the port
with serial.Serial(port, 115200, timeout=1) as ser:
print ("Starting up")
while True:
readOut = 0 #chars waiting from laser range finder
#readOut = ser.readline().decode('ascii')
readOut = ser.read(268) # Reads # Bytes
r = binascii.hexlify(readOut).decode('ascii')
print(r)
self.newParams.emit(r)
#ser.flush() #flush the buffer
if ser.isOpen() == False:
print("Serial Port is Close")
class Main(QMainWindow):
# Explicit Signal
#ser = pyqtSignal(str)
def __init__(self, parent=None):
super(QMainWindow, self).__init__(parent)
#QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.btnRefresh.clicked.connect(self.Refresh)
self.ui.btnConnect.clicked.connect(self.Connect)
self.ListFunction(self.SerialPorts())
# Implicit Slot
def Connect(self):
Items = self.ui.listMain.selectedItems()
if len(Items) != 1:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("BE CAREFULL!")
msg.setText("SELECT ONLY 1 ITEM!...")
msg.exec_()
else:
SelectedItem = Items[0].text()
SelectedItem = SelectedItem.split(" ")[0]
if sys.platform.startswith('win') and SelectedItem[:3] != 'COM':
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("BE CAREFULL!")
msg.setText("THERE IS A MISTAKE!...")
msg.exec_()
return
# Read Selected Serial Port
self.serialThread = QThread()
self.ser = SerialPort()
self.ser.moveToThread(self.serialThread)
self.ser.newParams.connect(self.serialThread.quit)
#self.serialThread.started.connect(self.ser.ReadSerialPort(SelectedItem))
self.serialThread.started.connect(lambda port=SelectedItem: self.ser.ReadSerialPort(port))
self.serialThread.start()
def Refresh(self):
""" Refresh List Widget """
self.ui.listMain.clear()
if self.ui.radioSerialPorts.isChecked() == True:
self.ListFunction(self.SerialPorts())
elif self.ui.radioTCP.isChecked() == True:
pass
def ListFunction(self, items):
""" Lists items into List Widget """
if type(items) == list and type(items[0]) == str:
#qApp.processEvents() # See Changes on GUI
self.ui.listMain.addItems(items)
else:
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("BE CAREFULL!")
msg.setText("ITEMS ARE WRONG!...")
msg.exec_()
def SerialPorts(self):
""" Lists serial port names """
device = []
description = []
result = []
for port in serial.tools.list_ports.comports():
device.append(port.device)
description.append(port.description)
result.append(port.device + ' - ' + port.description)
return result
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
myapp = Main()
myapp.show()
sys.exit(app.exec_())
provide delay function time.sleep(1) at thread function ReadSerialPort(self, port) inside the while loop äfter the function self.newParams.emit(r).
pyqt UI does not gets sufficient time to execute. so provide delay. it will work.

Kivy countdown Label

I'm trying to perform a countdown with the Label.
By updating the label's text I believe it is possible.
My intention is to start the countdown after the user presses the start button
But it's not working!!!
Here is my code
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.app import App
import time
class WelcomeScreen(Widget):
def __init__(self):
super().__init__()
self.start = Button(text="Start Game", pos=(350, 250))
self.add_widget(self.start)
self.count = Label(text="", pos=(350, 250),font_size=90)
self.start.bind(on_press=self.start_game)
def start_game(self, obj):
num = 0
self.remove_widget(self.start)
self.add_widget(self.count)
for i in range(1, 4):
num += 1
self.count.text = str(num)
time.sleep(1)
class TestApp(App):
def build(self):
return WelcomeScreen()
TestApp().run()
I know this is a stupid problem but I cant't really figure it out how to solve it.
Please Help
Short anwser: Use the Clock
def start_game(self, obj):
num = 0
self.remove_widget(self.start)
self.add_widget(self.count)
def count_it(num):
if num == 4:
return
num += 1
self.count.text = str(num)
Clock.schedule_once(lambda dt: count_it(num), 1)
Clock.schedule_once(lambda dt: count_it(0), 0)
Longer:
When you are running python code the kivy event loop is waiting so nothing gets update in the screen, when you do time.sleep you stall the process...
The Clock schedule_* methods allow you to set callbacks while letting the event loop run when you are idle
You can also use a thread and call Clock.schedule_once just for the updates but for your use case the code above is simpler

Pause execution until button press

I have a QStackedWidget. In the logic (not the UI) I am trying to change pages and wait there until a button on that page is pressed (basically an OK/Cancel). I pass the UI to the function in the class.
Something like this:
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
while True:
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
do_something
window.stackedWidget.setCurrentIndex(3)
break
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)
break
for i in range(5):
#stuff
func1(window) #this is where I want to pause
#other stuff
Now I know that I can't break with the function like that or pass the window variable through connect, but I hope that makes my point clearly enough.
A simple way to do this is to process pending events inside the loop (so the UI remains responsive), and set/unset an internal flag to control starting and stopping of the loop.
The following demo script shows a basic implementation of this idea:
import time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.label = QtGui.QLabel(self)
layout.addWidget(self.label)
self.buttonStart = QtGui.QPushButton('Start', self)
self.buttonStart.clicked.connect(self.handleStart)
layout.addWidget(self.buttonStart)
self.buttonStop = QtGui.QPushButton('Stop', self)
self.buttonStop.clicked.connect(self.handleStop)
layout.addWidget(self.buttonStop)
self._running = False
def handleStart(self):
self.buttonStart.setDisabled(True)
self._running = True
while self._running:
self.label.setText(str(time.clock()))
QtGui.qApp.processEvents()
time.sleep(0.05)
self.buttonStart.setDisabled(False)
def handleStop(self):
self._running = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 200, 100)
window.show()
sys.exit(app.exec_())
Just remove while and break.
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
# do_something
window.stackedWidget.setCurrentIndex(3)
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)

Resources