I have a simple chat client that I was attempting to get working with Tkinter as the interface. My problem is that when running the mainloop with .after for the chat input/output, the window freezes and blocks until another message is received.
class Client(Frame):
def __init__(self, **kwargs):
Frame.__init__(self, Tk())
self.pack()
self.lb = Listbox(self, width=100, height=30)
self.lb.pack()
self.show_data = self.lb.after(1000, self.chat_handle)
self.entry = Entry(self)
self.entry.bind('<Return>', self.input_handle)
self.entry.pack(side=BOTTOM, fill=X)
def input_handle(self, event):
msg = self.entry.get()
self.entry.delete(0, 'end')
new_msg = 'privmsg %s :' % self.channel + msg + '\r\n'
self.client.sendall(new_msg)
self.lb.insert(END, self.nick + ' | ' + msg)
def chat_handle(self):
try:
self.data = self.client.recvfrom(1024)
except socket.error:
self.lb.insert(END, "Bad Connection!")
return
if self.data and len(self.data[0]) > 0:
self.lb.insert(END, self.data[0])
elif self.data and len(self.data[0]) == 0:
self.lb.insert(END, "Connection Dropped!")
return
self.show_data = self.lb.after(1000, self.chat_handle)
This block of code is shortened but, shows the relavent parts involved. The Entry widget will become unresponsive for extended periods while .after is called and won't respond until a message is received.
When the Entry widget becomes responsive again, the entry field has all the data that was typed in but, I won't see the changes during the "frozen" time. The same goes for the Listbox widget.
If anyone could shed some light on why this is exactly or point out if I'm miss using a method here, it would be greatly appreciated.
EDIT: after some more research, its looking like the socket data is blocking whenever its called and window is getting frozen during this time.
after executes the callback function after the given time; however, this method also runs in the main thread. So if there is an operation that takes more time than usual (in this case, the blocking recvfrom), the GUI will be unresponsive until the complete callback is executed.
To solve this, a common recipe is to spawn a new thread and communicate it with your Tkinter code with a synchronized object like a Queue. Thus, you put the data in the queue when you receive it from the socket, and then check periodically in the main thread inside the after callback.
This is a question whose answer can be adapted to use the same approach: Tkinter: How to use threads to preventing main event loop from "freezing"
I learned about using select to make system calls to check if a socket file is ready to be read.
How to set timeout on python's socket recv method?
class Client(Frame):
def __init__(self, **kwargs):
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.connect(("host", port))
self.client.setblocking(0)
Frame.__init__(self, Tk())
self.pack()
self.lb = Listbox(self, width=100, height=30)
self.lb.pack()
self.show_data = self.lb.after(1000, self.chat_handle)
self.entry = Entry(self)
self.entry.bind('<Return>', self.input_handle)
self.entry.pack(side=BOTTOM, fill=X)
def input_handle(self, event):
msg = self.entry.get()
self.entry.delete(0, 'end')
new_msg = 'privmsg %s :' % self.channel + msg + '\r\n'
self.client.sendall(new_msg)
self.lb.insert(END, self.nick + ' | ' + msg)
def chat_handle(self):
socket_data = select.select([self.client], [], [], 0.3) # set timeout on last arg
if socket_data[0]:
try:
self.data = self.client.recvfrom(1024)
except socket.error:
self.lb.insert(END, "Bad Connection!")
return
if self.data and len(self.data[0]) > 0:
self.lb.insert(END, self.data[0])
elif self.data and len(self.data[0]) == 0:
self.lb.insert(END, "Connection Dropped!")
return
self.show_data = self.lb.after(1000, self.chat_hand
Related
Basically, what I have is a GUI with some QLineEdits, a "search button" and a table. You hit the button and a class called DataGrabber searches a database for data, processes them, returns a list with dictionaries with which the table is filled, accordingly. These searches can take a while and I need to keep my GUI responsive. Also, I want the statusbar message to change as long as the search is going on (something like "Searching." -> "Searching.." -> "Searching...", the functionality is not rly important here, it's just about understanding how I can handle this properly).
I started with threading everything and created a queue between the thread that handles the search and the function that handles the statusbar, to know when the search is done. But that seems really goofy. Especially since Qt provides all kind of tools, like QThread and Signals. But I'm rly lost right now. What would be the best way to handle the responsiveness when having such a time-consuming action like a database search? And what would be the best way to tell the main/child thread that the search is finished?
Here is a reduced version of what I have right now:
class GUI(Ui_MainWindow, InitGlobals):
def __init__(dialog):
...
self.start_button_3.clicked.connect(\
lambda: self.start_search(self.result_tab_3))
...
def start_search():
...
search_paras = [3,
self.name_box_3.text(),
self.project_combo_3.currentText(),
self.voltage_box.text(),
self.volume_box.text()]
queue = Queue()
thr = Thread(target=self.search_thread, args=(queue, search_paras,))
thr.start()
data_lst = statusbar_load(queue, self.statusbar, option="loader")
thr.join()
self.statusbar.showMessage("Search completed...")
for dic in data_lst:
self.write_to_table(dic, tab)
def search_thread(self, queue, args):
grabber = DataGrabber(self.db_config)
...
if args[0] == 3:
queue.put(grabber.alpha_search(args[1], args[2],
args[3], args[4]))
queue.task_done()
def statusbar_load(queue, statusbar_obj, option="spinner"):
data = None
i = 0
while data is None:
try:
data = queue.get(timeout=0.1)
except Empty:
if option == "spinner":
status = ["-", "\\", "|", "/"]
statusbar_obj.showMessage("Searching [" + status[i%4] + "]")
....
i = i + 1
return data
This can be handled with signals. You can use signals to send the results to the GUI and to update the GUI of the progress.
Here is a quick example of the implementation with a progress bar and status label. These can be included in a status bar if you would like:
class GUITest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout()
self.button = QtWidgets.QPushButton('Run')
self.button.clicked.connect(self.run)
self.result_box = QtWidgets.QTextBrowser()
self.label = QtWidgets.QLabel()
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
layout.addWidget(self.button)
layout.addWidget(self.result_box)
layout.addWidget(self.label)
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def run(self):
self.progress_bar.setVisible(True)
self.label.setText('Searching...')
self.thread = QtCore.QThread()
self.data_grabber = DataGrabber()
self.data_grabber.moveToThread(self.thread)
self.data_grabber.update_progress.connect(self.update_progress_bar)
self.data_grabber.results.connect(self.display_results)
self.data_grabber.finished.connect(self.complete)
self.data_grabber.finished.connect(self.thread.quit)
self.data_grabber.finished.connect(self.data_grabber.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.started.connect(self.data_grabber.run)
self.thread.start()
def update_progress_bar(self):
self.progress_bar.setValue(self.progress_bar.value() + 1)
def complete(self):
self.label.setText('Complete')
self.progress_bar.setVisible(False)
def display_results(self, results):
for key, value in results.items():
self.result_box.append('%s: %s' % (key, value))
class DataGrabber(QtCore.QObject):
finished = QtCore.pyqtSignal()
update_progress = QtCore.pyqtSignal()
results = QtCore.pyqtSignal(dict) # set the type of object you are sending
def __init__(self):
super().__init__()
self.count = 0
def run(self):
# search database here and emit update_progress when appropriate
while self.count <= 100:
self.update_progress.emit()
self.count += 1
time.sleep(0.02)
self.send_results() # when done, send the results
self.finished.emit()
def send_results(self):
results = {'one': 'Result One', 'two': 'Result Two', 'three': 'Result Three'}
self.results.emit(results)
For a few weeks I'm stuck with, how to open a serial COM port from a thread,
to be able to write/read it from another thread. In my example, when I write to the port from another thread, there is access denied.
When I open the port from Main GUI, it works fine, I can write it from the thread, but I need to give a user possibility to choose the COM number.
This is my code, if anybody could take a look and help, it would be great...
class Connect(QThread):
connectResult = QtCore.pyqtSignal(str)
position1 = QtCore.pyqtSignal(str)
actuPosResult = QtCore.pyqtSignal(str)
def __init__(self, myvar, parent=None):
QThread.__init__(self, parent)
self.myvar = str(myvar) # value from spinbox
def run(self):
self.pserial = serial.Serial()
try:
COMnumber= self.myvar
self.pserial = serial.Serial('COM'+COMnumber, 115200,timeout=None)
r='COM '+COMnumber+' connected.'
self.pserial.write("c".encode('ascii')+"\n".encode('ascii'))
incomingByte = self.pserial.read()
decodedByte = incomingByte.decode("utf-8")
if decodedByte == ('c'):
r='Atmega On-Line'
self.connectResult.emit(r)
pos1='---'
self.position1.emit(pos1)
else :
r=' No answer from Atmega.'
self.connectResult.emit(r)
def stop(self):
self.terminate()
class ReadingEncoder(QThread):
actuPosResult = QtCore.pyqtSignal(str)
def __init__(self, mojazmienna, parent=None):
QThread.__init__(self, parent)
self.mojazmienna = str(mojazmienna)
def run(self):
Try:
self.pserial = serial.Serial()
self.pserial = serial.Serial('COM3', 115200,timeout=1)
self.pserial.write("p".encode('ascii')+"\n".encode('ascii'))
incomingByte = self.pserial.read()
decodedByte = incomingByte.decode("utf-8")
actualPos = ''
if decodedByte == ('a'):
while decodedByte != ('\n'):
incomingByte = self.pserial.read()
decodedByte = incomingByte.decode("utf-8")
actualPos = actualPos + decodedByte
pos= actualPos.rstrip('\n')# pozycja w formacie string
print(pos)
self.actuPosResult.emit(pos)
except (EOFError, OSError, IOError, ValueError, RuntimeError, BrokenPipeError, InterruptedError, TimeoutError):
print('Thread readingEncoder error')
self.pserial.close()
You just open the serial port and start the thread.
import atexit
class SerialPort(QThread):
connectResult = QtCore.pyqtSignal(str)
position1 = QtCore.pyqtSignal(str)
actuPosResult = QtCore.pyqtSignal(str)
def __init__(self, port=None, baud=115200, timeout=1):
super().__init__()
self.ser = serial.Serial()
self.ser.port = port
self.ser.baudrate = baud
self.ser.timeout = timeout
self.running = False
atexit.register(self.ser.close) # Make sure the serial port closes when you quit the program.
def set_port(port_num):
self.ser.port = "COM"+str(port_num)
def start(self, *args, **kwargs):
self.running = True
self.ser.open()
super().start()
self.ser.write("c\n".encode("ascii"))
def run(self):
while self.running:
try:
incomingByte = self.ser.read()
decodedByte = incomingByte.decode("utf-8")
if decodedByte == ('c'):
r='Atmega On-Line'
self.connectResult.emit(r)
pos1='---'
self.position1.emit(pos1)
else:
r=' No answer from Atmega.'
self.connectResult.emit(r)
except:
pass
# time.sleep(0.01) # You may want to sleep or use readline
You use this class by having a button call the start method.
serial = SerialPort("COM3")
btn = QtGui.QPushButton("Connect")
btn.clicked.connect(serial.start)
You can typically only open a serial port once unless you know that you have both ends available to you.
The serial port exists on the main thread and exists the entire time. You don't need to continuously open and close the serial port. All of the reading happens in the thread. To write just call serial.write(b"c\n"). You shouldn't need the writing to happen in the thread.
To have the user select the com port just use a QLineEdit.
myserial = QtGui.QLineEdit("3")
myserial.textChanged.connect(serial.set_port)
You're trying to open the port multiple times.
Instead of doing that, you have a couple of alternatives;
Create a Serial object once, and pass it to the thread that has to work with it.
Provide the name of the serial port to use to the the thread that has to work with it, and let it open the port.
In both cases make sure that your thread closes the port properly when it exits!
I'm using this forum for some time, but this is the first time that I ask a question, since I haven't been able to find a good way around my difficulties, and because I hope this question will be useful to other people too.
I am implementing a simple notification board, i.e. a window where messages coming from a socket connection are displayed. This board prints in red the last message received and in blue the old ones, up to ten. When a message sent by the client is 'Q', the connection terminates and the notification board is destroyed.
I am using Tkinter, threading and sockets, but the behaviour is not smooth (it takes a while to refresh the notification board). I can think of a few problems: the thread handling the connection is not closed; the update of the window is performed by destroying and recreating the toplevel. Unfortunately I can't understand if these issues are the source of the problem.
Here is the code for the client, a very simple one:
#!/usr/bin/env python
import socket
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
while True:
message = raw_input('Enter your command (Q=quit): ')
s.send(message)
reply = s.recv(1024)
if reply=='Q':
print 'Request to disconnect was received.'
break
else :
print reply
s.close()
And here is the server. The server has implemented a class that handles the notification board characteristics, a thread for the socket connection and finally the main part with the mainloop().
#!/usr/bin/env python
import socket
import threading
from Tkinter import *
from datetime import datetime
### Class definition
class NoticationsBoard() :
def __init__(self, title):
self.messages = []
self.toplevel = None
self.title = title
self.backgroundColor = 'black'
self.textColor = 'blue'
self.textColorFirst = 'red'
self.boardSize = '200x250+0+0'
self.listsize = 10
def createBoard(self):
self.toplevel = Toplevel()
self.toplevel.title(self.title)
self.toplevel.configure(background='black')
self.toplevel.geometry(self.boardSize)
def publish(self, message):
self.addToList(message)
self.displayList()
def addToList(self, msg):
if len(self.messages) == self.listsize:
self.messages.pop(0)
timestamp = datetime.utcnow().strftime('%H:%M:%S')
newMessage = (msg, timestamp)
self.messages.append(newMessage)
def displayList(self):
# Destroy and create the window (is it really necessary?)
if self.toplevel is not None :
self.toplevel.destroy()
self.createBoard()
# create labels for all the messages in the list
index = 1
for m, t in self.messages :
color = self.textColor
if index == len(self.messages) :
color = self.textColorFirst
label = Label(self.toplevel, text=m, height=0, width=100, fg=color, anchor=W)
label.grid(row=0,column=1)
label.configure(background=self.backgroundColor)
label.pack(side='bottom')
index = index +1
####### Run
def receiveMessages(newsboard) :
print '===== Inside receiveMessages ======'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
try:
s.bind((HOST, PORT))
except socket.error , msg:
print 'Bind failed. Error code: ' + str(msg[0]) + 'Error message: ' + msg[1]
sys.exit()
print 'Socket bind complete'
s.listen(1)
print 'Socket now listening on port', PORT
# Accept the connection once
(conn, addr) = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
stored_data = ''
while True:
# RECEIVE DATA
data = conn.recv(1024)
# PROCESS DATA
if data == 'Q' :
print 'Client wants to disconnect.'
reply = 'Q'
conn.send(reply)
break
else :
print data
newsboard.publish(data)
reply = 'Message received:' + data
conn.send(reply)
print 'Close connection.'
conn.close()
board.destroy()
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
app = Tk()
app.title("GUI main")
board = NoticationsBoard('Notifications')
t = threading.Thread(target=receiveMessages, args = (board,))
t.start()
app.update() # Not sure what it does and if it is necessary
app.mainloop()
I am using Python 2.7.5.
Finally, but this is something minor, I was trying to display the timestamp of each message on the left of the message itself, in a different color. It seems to me that it is not possible to have text of different colours on the same label, so I had created other labels in the for loop with the timestamps. I tried to display the timestamp and message labels one next to the others using .grid(column=0) and .grid(column=1), but they were not one next to the other but one below the other, and I haven't figured out why.
As you understood, I am not a skilled programmer, and definitely a newbie with Python...
Thanks in advance to whom will give me some advice, and I hope this question will be useful to many people.
Ok, it seems like I found a solution by taking parts of other people's questions, suggestions, and code. There are few differences in the appearance maybe.
In the GUI, the most notable one is that I preload all the Labels and then I just modify the text.
In the threading part, well, that's completely changed. Please see below.
#!/usr/local/bin/python
try:
import Tkinter
except ImportError:
import tkinter as Tkinter
import time
import threading
import random
import Queue
import socket
import sys
from datetime import datetime
class GuiPart:
def __init__(self, master, queue):
self.queue = queue
# GUI stuff
self.labelArray = []
self.messages = []
# Set up the GUI
self.master = master
self.backgroundColor = 'black'
self.listsize = 10
master.config(bg=self.backgroundColor)
self.board = Tkinter.LabelFrame(self.master, text='Notification Board', bg='Black', fg='Yellow', labelanchor='n', width=170)
self.initLabels()
self.board.pack()
def initLabels(self) :
self.textColorTime = 'cyan'
self.textColorMessage = 'orange'
colorTime = 'blue'
colorMessage = 'red'
for i in range(0,self.listsize):
la = Tkinter.Label(self.board, height=0, width=10, bg=self.backgroundColor, fg=colorTime, anchor=Tkinter.W)
lb = Tkinter.Label(self.board, height=0, width=160, bg=self.backgroundColor, fg=colorMessage)
la.grid(row=i,column=0, sticky=Tkinter.W)
lb.grid(row=i,column=1, sticky=Tkinter.W)
self.labelArray.append((la, lb))
colorTime = self.textColorTime
colorMessage = self.textColorMessage
self.initList()
self.displayList()
def initList(self):
for i in range(0, self.listsize):
t = ''
m = ''
self.messages.append((t,m))
def processIncoming(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.processMessage(msg)
except Queue.Empty:
pass
def processMessage(self, message):
timestamp = datetime.utcnow().strftime('%H:%M:%S')
self.publish(timestamp, message)
def publish(self, msg1, msg2):
self.addToList(msg1, msg2)
self.displayList()
def addToList(self, msg1, msg2):
if len(self.messages) == self.listsize:
self.messages.pop(0)
if (msg1 == None):
msg1 = datetime.utcnow().strftime('%H:%M:%S')
newMessage = (msg1, msg2)
self.messages.append(newMessage)
def displayList(self):
index = self.listsize -1
for t, m in self.messages :
la, lb = self.labelArray[index]
la.config(text=t)
lb.config(text=m)
index = index -1
def destroy(self):
self.master.destroy()
class ThreadedClient:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = Queue.Queue()
# Define connection parameters
self.conn = None
self.bindError = False
# Set up the GUI part
self.gui = GuiPart(master, self.queue)
# Set up the thread to do asynchronous I/O
self.running = True
self.commThread = threading.Thread(target=self.workerThreadReceive)
self.commThread.daemon = True
self.commThread.start()
# Start the periodic call in the GUI to check if the queue contains anything
self.periodicCall()
def periodicCall(self):
if not self.running:
self.killApplication()
else :
self.gui.processIncoming()
self.master.after(100, self.periodicCall)
def workerThreadReceive(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try :
s.bind((HOST, PORT))
except socket.error as msg :
print 'Bind failed. Error code: ' + str(msg[0]) + ' Error message: ' + str(msg[1])
self.running = False
self.bindError = True
return
s.listen(1)
(self.conn, self.addr) = s.accept()
while self.running :
data = self.conn.recv(1024)
if data == 'Q' :
self.conn.sendall('Q')
self.running = False
else :
self.queue.put(data)
reply = 'ACK'
self.conn.sendall(reply)
if self.conn is not None:
self.conn.close()
def killApplication(self):
self.running = False
if (self.conn is None) and (not self.bindError) :
sfake = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sfake.connect((HOST,PORT))
sfake.sendall('Q')
sfake.close()
self.gui.destroy()
sys.exit()
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
root = Tkinter.Tk()
client = ThreadedClient(root)
root.protocol("WM_DELETE_WINDOW", client.killApplication)
root.mainloop()
The client is the same as in the question.
I am not sure my code is the most elegant one (ok, let's say it... it is not!), but it seems to do the job. Nevertheless, I would like to get your feedback about it, because I am sure that I have overlooked many issues and that things could have done in an easier way. :)
I am trying to connect raw_input() to a Tkinter gui, by using Queus and Threading. Here is my code:
import Tkinter
import time
import threading
import Queue
class UI(Tkinter.Frame):
def __init__(self, master=None, queue=None):
self.queue = queue
Tkinter.Frame.__init__(self, master)
self.master.title("what does console say")
self.master.minsize(40, 30)
swd = self.master.winfo_screenwidth()
fwd = 320
fht = 240
self.master.geometry('{0:d}x{1:d}+{2:d}+{3:d}'.format(
fwd, fht, swd - fwd, 0))
self.message = Tkinter.StringVar()
mesgbox = Tkinter.Label(master, textvariable=self.message)
mesgbox.pack(fill=Tkinter.BOTH, expand=1)
self.pack()
self.processqueue()
def processqueue(self):
try:
message = '\n'.join([self.message.get(),
self.queue.get_nowait()])
except Queue.Empty:
pass
else:
self.message.set(message)
self.master.after(100, self.processqueue)
class ThreadedTask(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.daemon = 1
self.queue = queue
def run(self):
i = 0
while True:
i += 1
message = raw_input('put some words:')
self.queue.put("[Message] {0:s}".format(message))
queue = Queue.Queue()
gui = UI(queue=queue)
job = ThreadedTask(queue)
job.start()
gui.mainloop()
However, I get the following error:
error in background error handler:
out of stack space (infinite loop?)
while executing
"::tcl::Bgerror {out of stack space (infinite loop?)} {-code 1 -level 0 -errorcode NONE -errorinfo {out of stack space (infinite loop?)
("after" sc..."
Could anyone help me with it? Thank you in advance!
Another thing is that, this code works if I do not use the raw_input(), but some machine generated text, i.e.:
def run(self):
i = 0
while True:
i += 1
time.sleep(0.5)
self.queue.put("[Message] {0:d}".format(i))
Could anyone explain why?
After a deep searching, I found this post: here
I am not sure if this is exactly the problem, but it inspired me, in the way that I reformed my code, by putting the gui mainloop() in a second thread.
In this way, it works like a charm, although I still don't exactly why there is such behaviour.
I'm trying to change the state of a Gtk status icon from a thread as specified in MailThread.run() below, but I don't know how to reach the status icon object from the method in order to change set_visible to either True or False.
Basically I would like to know what to write in place of "# set status icon visible off/on".
#!/usr/bin/env python
import gtk, sys, pynotify, imaplib, time, threading
from email import parser
class Mail:
def check_mail(self):
obj = imaplib.IMAP4_SSL('imap.gmail.com','993')
acc = 'email'
pwrd = 'pass'
obj.login(acc, pwrd)
obj.select()
num = str(len(obj.search(None,'UnSeen')[1][0].split()))
return acc, num
class MailThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
gtk.gdk.threads_init()
def run(self):
while True:
print "hello"
mail = Mail()
num = mail.check_mail()[1]
if num < 1:
# set status icon visible off
else:
# set status icon visible on
time.sleep(60)
class StatusIcon:
# activate callback
def activate( self, widget, data=None):
mail = Mail()
acc, num = mail.check_mail()
pynotify.init("myapp")
n = pynotify.Notification(acc, "You have " + num + " unread e-mails.", "emblem-mail")
n.show()
# Show_Hide callback
def show_hide(self, widget,response_id, data= None):
if response_id == gtk.RESPONSE_YES:
widget.hide()
else:
widget.hide()
# destroyer callback
def destroyer(self, widget,response_id, data= None):
if response_id == gtk.RESPONSE_OK:
gtk.main_quit()
else:
widget.hide()
# popup callback
def popup(self, button, widget, data=None):
dialog = gtk.MessageDialog(
parent = None,
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
type = gtk.MESSAGE_INFO,
buttons = gtk.BUTTONS_OK_CANCEL,
message_format = "Do you want to close e-mail notifications?")
dialog.set_title('Exit')
dialog.connect('response', self.destroyer)
dialog.show()
def __init__(self):
# create a new Status Icon
self.staticon = gtk.StatusIcon()
self.staticon.set_from_icon_name("emblem-mail")
self.staticon.connect("activate", self.activate)
self.staticon.connect("popup_menu", self.popup)
self.staticon.set_visible(True)
# starting thread
thread = MailThread()
thread.setDaemon(True)
thread.start()
# invoking the main()
gtk.main()
if __name__ == "__main__":
# status icon
statusicon = StatusIcon()
You can accept the status icon in the thread's __init__():
class MailThread(threading.Thread):
def __init__(self, status_icon = None):
threading.Thread.__init__(self)
gtk.gdk.threads_init()
self.status_icon = status_icon
And then you can use it in run().
Additionally, you need to do all the GUI work from the main thread. The main thread has a queue maintained by GTK you can use to tell it to go do some GUI work. This is how it works:
def run(self):
# <...>
if num < 1:
gobject.idle_add(self.set_status_icon, False)
else:
gobject.idle_add(self.set_status_icon, True)
# <...>
def set_status_icon(self, state = False):
# code that changes icon state goes here
pass
idle_add basically means "add that to the queue and do it when you have some free time".