I'm having trouble figuring out how to receive the WM_ENDSESSION window manager message in Tkinter, that is supposed to be sent to the top-level window during system shutdown. I am aware of everything that comes with trying to run extra code during shutdown, however in my case it'll only be a simple file flush down to disk and close, so that my program has a chance to save it's state.
My simple test code:
import tkinter as tk
def on_close(*args):
print("User closed the window")
# I want this to run during shutdown
def on_shutdown(*args):
print("User is shutting down their PC")
root = tk.Tk()
root.protocol("WM_DELETE_WINDOW", on_close)
root.mainloop()
A code example, or at least a pointer towards which functions or methods to use, would be appreciated.
I would suggest using a message spying too like "Spy++" (spyxx.exe) to check which messages are actually sent to your window.
In some places it is suggested that wireshark can also do this, but I have not found concrete evidence for that.
Tk gives your program "a chance to save it's state" with the aptly named WM_SAVE_YOURSELF protocol. It is hooked up to WM_QUERYENDSESSION on Windows. The return value seems to be ignored so you cannot prevent WM_ENDSESSION from being dispatched, and therefore it shouldn't be used for long blocking work, so it really depends on how much you have to write if things will get weird or interrupted.
Hook up your callback with root.protocol("WM_SAVE_YOURSELF", on_close) and let me know if that works!
As you, I found out (with respect for the issue) tk.Tk().protocol is pretty useless and your desired behavior is a wish since 1999. In the link is a patch you could try out.
I think the easiest way to go for you will be SetConsoleCtrlHandler:
This function provides a similar notification for console application
and services that WM_QUERYENDSESSION provides for graphical
applications with a message pump. You could also use this function
from a graphical application, but there is no guarantee it would
arrive before the notification from WM_QUERYENDSESSION.
You could find an exampel on StackOverflow or go with Pywin32. The disadvantage over the message approach is that you can't delay the shutdown further more. But the User would have this option anyway, after the timeout of 5 seconds is expired and you would have with SetConsoleHandler the same amount of time.
A word of caution, flushing a bigger amount of data is explicitly discouraged:
Avoid disk flushes, for example, those initiated through calling
FlushFileBuffers API. Flushing causes the disk stack to delete its
caches and is supposed to force the hard drive to write out data in
its RAM buffers. Typically, this operation is very costly and does not
guarantee data consistency since the hard drives often ignore the
request.
...
It is recommended that applications save their data and state
frequently; for example, automatically save data between save
operations
In addition there is an example with tkinter and pywin32 that handles WM_ENDSESSION, if you intrested.
After many struggles of trying to find information online and failing to get my code to work, this is what eventually ended up working for me in the end. It's based on the https://stackoverflow.com/a/58622778/13629335 answer, suggested by #furas and #Thingamabobs. I've added helpful comments explaining what each section does. Feel free to adjust it up to your own needs.
import sys
import time
import ctypes
import logging
import tkinter as tk
from tkinter import ttk
import win32con
import win32gui
import win32api
# NOTE: this is used in the root.mainloop() replacement at the bottom.
exit_requested = False
def quit():
global exit_requested
exit_requested = True
# Received when the user tries to close the window via X in the top right corner.
def close_window(*args, **kwargs):
logging.info("WM_CLOSE received")
quit()
# 0 tells Windows the message has been handled
return 0
# Received when the system is shutting down.
def end_session(*args, **kwargs):
logging.info("WM_ENDSESSION received")
quit()
# Returning immediately lets Windows proceed with the shutdown.
# You can run some shutdown code here, but there's a 5 seconds maximum timeout,
# before your application is killed by Windows.
return 0
# Received when the system is about to shutdown, but the user can
# cancel this action. Return 0 to tell the system to wait until
# the application exits first. No timeout.
def query_end_session(*args, **kwargs):
logging.info("WM_QUERYENDSESSION received")
quit()
# 1 means you're ready to exit, and you'll receive a WM_ENDSESSION immediately afterwards.
# 0 tells Windows to wait before proceeding with the shutdown.
return 0
# Simple logging setup to catch all logging messages into a file.
file_handler = logging.FileHandler("shutdown_test.log", encoding="utf8")
file_handler.setFormatter(logging.Formatter("%(asctime)s: %(message)s"))
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_logger.addHandler(file_handler)
logging.info("starting shutdown test")
# Start of your application code
root = tk.Tk()
root.title("Shutdown test")
main_frame = ttk.Frame(root, padding=20)
main_frame.grid(column=0, row=0)
ttk.Label(
main_frame, text="Shutdown test in progress...", padding=50
).grid(column=0, row=0, sticky="nsew")
# End of your application code
# This is crucial - a root.update() after all application setup is done,
# is very needed here, otherwise Tk won't properly set itself up internally,
# leading to not being able to catch any messages later.
root.update()
# NOTE: These two lines below can be used for basic message handling instead.
# Return value from WM_SAVE_YOURSELF is ignored, so you're expected to
# finish all of the closing sequence before returning. Note that Windows will wait
# for you up to 5 seconds, and then proceed with the shutdown anyway.
# root.protocol("WM_DELETE_WINDOW", close_window)
# root.protocol("WM_SAVE_YOURSELF", query_end_session)
root_handle = int(root.wm_frame(), 16)
message_map = {
win32con.WM_CLOSE: close_window,
win32con.WM_ENDSESSION: end_session,
win32con.WM_QUERYENDSESSION: query_end_session,
}
def wnd_proc(hwnd, msg, w_param, l_param):
"""
This function serves as a message processor for all messages sent to your
application by Windows.
"""
if msg == win32con.WM_DESTROY:
win32api.SetWindowLong(root_handle, win32con.GWL_WNDPROC, old_wnd_proc)
if msg in message_map:
return message_map[msg](w_param, l_param)
return win32gui.CallWindowProc(old_wnd_proc, hwnd, msg, w_param, l_param)
# This hooks up the wnd_proc function as the message processor for the root window.
old_wnd_proc = win32gui.SetWindowLong(root_handle, win32con.GWL_WNDPROC, wnd_proc)
if old_wnd_proc == 0:
raise NameError("wndProc override failed!")
# This works together with WM_QUERYENDSESSION to provide feedback to the user
# in terms of what's preventing the shutdown from proceeding.
# NOTE: It's sort-of optional. If you don't include it, Windows will use
# a generic message instead. However, your application can fail to receive
# a WM_QUERYENDSESSION if it's window is minimized (via iconify/withdraw)
# when the message happens - if you also need to be able to handle that case,
# then you'll need it.
retval = ctypes.windll.user32.ShutdownBlockReasonCreate(
root_handle, ctypes.c_wchar_p("I'm still saving data!")
)
if retval == 0:
raise NameError("shutdownBlockReasonCreate failed!")
# NOTE: this replaces root.mainloop() to allow for a loop exit
# without closing any windows - root.quit() apparently does so.
while not exit_requested:
root.update()
time.sleep(0.05)
# Your shutdown sequence goes here.
logging.info("shutdown start")
time.sleep(10)
logging.info("shutdown finished")
root.destroy()
sys.exit(0)
Related
I am making a python program which takes a lot's of user input. And If user by mistake close the program all the data stored in variables are gone. So, I want to make something like warning message like "Do you want to close the program" appear when ever user click on close button or do shortcut like alt+F4 to close program. I just want it for making my program much better. Cuz, If some one had input many data and he by mistake close
the program and all the progress for him and my program as lost. So to overcome this I want to to know to make something like warning message appear whenever user try to close the program.
This can be done with the signal library. This is a built in library that can asynchronously handle user events in Python. You can read more in the Python Documentation.
Here is a simple example that I typically use to remind myself. (As a note, this was inspired by this SO post from eight years ago):
import signal
import sys
def yay():
x = 0
while True:
x += 1
print(x)
def terminate_catcher(signum, frame):
#Restoring sigint in case user hits Ctrl+C for real to avoid loop
signal.signal(signal.SIGINT, actual_sigint)
#Some conditional
if input("Are you sure? (Y/N)") == "Y":
sys.exit(1)
#Restore the signal catcher if the user decides not to cancel
signal.signal(signal.SIGINT, terminate_catcher)
if __name__ == '__main__':
# store the original SIGINT handler
actual_sigint = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGINT, terminate_catcher)
yay()
Essentially, the main function registers an event handler that is called whenever the SIGINT interrupt is called. Whenever the function runs, it restores the handler back to the original SIGINT handler in case the user hits ^C one more time while the exception is being handled so we can avoid an infinite loop. Whenever the function is triggered, it just asks the user if they want to leave and uses system.exit(1) if they say yes. If the user decides to stay, it restores the handler for future events.
I have started network programming using Python and am working on a basic peer-to-peer chat client-server application. I got it working for console, but am facing problem while developing a GUI.
This is the code for my client script. It is sending data to the server but is unable to receive /display the data sent from server, I am at a loss. Please show the error in my code and the solution.
from socket import *
from tkinter import *
host="127.0.0.1"
port=1420
buffer=1024
server=(host,port)
clientsock=socket(AF_INET,SOCK_STREAM)
clientsock.connect(server)
class ipbcc(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.grid()
self.create()
self.connect()
def write(self,event):
msg=self.e.get()
clientsock.send(msg.encode())
def create(self):
self.pic=PhotoImage(file="logo.gif")
self.label=Label(self,image=self.pic)
self.label.grid(column=0)
self.wall=Text(self,width=70,height=20,wrap=WORD)
self.wall.grid(row = 0, column = 1, columnspan = 2, sticky = W)
self.e=Entry(self,width=50)
self.e.grid(row = 1, column = 1, sticky = W)
self.e.bind('<Return>',self.write)
def add(self,data):
self.wall.insert(END,data)
def connect(self):
def xloop():
while 1:
data=clientsock.recv(buffer).decode()
print(data)
self.add(data)
root=Tk()
root.title("IPBCC v0.1")
app=ipbcc(root)
root.mainloop()
PS: Python Version 3.3 and there is no problem in the server script.
Your connect function defines a function called xloop, but it doesn't call that function, or return it, or store it somewhere for anyone else to call it. You need to call that function for it to do anything.
Of course if you just call it directly inline, it will run forever, meaning you never get back to the event loop, and the UI freezes up and stops responding to the user.
There are two options for this: threading, or polling.
The obvious way to do this is with a background thread. The basic idea is very simple:
def connect(self):
def xloop():
while 1:
data=clientsock.recv(buffer).decode()
print(data)
self.add(data)
self.t = threading.Thread(target=xloop)
self.t.start()
However, there are two problems with this.
First, there's no way to stop the background thread. When you try to exit the program, it will wait for the background thread to stop—which means it will wait forever.
There's an easy solution to that one: if you make it a "daemon thread", it will be summarily killed when the main program exits. This is obviously no good for threads that are doing work that could be corrupted if interrupted in the middle, but in your case that doesn't seem to be a problem. So, just change one line:
self.t = threading.Thread(target=xloop, daemon=True)
Second, that self.add method needs to modify a Tkinter widget. You can't do that from a background thread. Depending on your platform, it may fail silently, raise an exception, or even crash—or, worse, it may work 99% of the time and fail 1%.
So, you need some way to send a message to the main thread, asking it to do the widget modification for you. This is a bit complicated, but Tkinter and Threads explains how to do it.
Alternatively, you could use mtTkinter, which intercepts Tkinter calls in background threads and passes them to the main thread automatically, so you don't have to worry about it.
The other option is to change the blocking xloop function into a nonblocking function that polls for data. The problem is that you want to wait on Tkinter GUI events, but you also want to wait on the socket.
If you could integrate the socket into the main event loop, that would be easy: a new message coming in would be handled just like any other event. Some of the more powerful GUI frameworks like Qt give you ways to do this, but Tkinter does not. A reactor framework like Twisted can tie itself into Tkinter and add it for you (or at least fake nicely). But if you want to stick with your basic design, you have to do it yourself.
So, there are two options:
Give Tkinter full control. Ask it to call your function every, say, 1/20th of a second, and in the function do a non-blocking check. Or maybe loop around non-blocking checks until there's nothing left to read.
Give the socket control. Ask Tkinter to call your function every time it gets a chance, and block for 1/20th of a second checking for data before returning to Tkinter.
Of course 1/20th of a second may not be the right length—for many applications, no answer is really correct. Anyway, here's a simple example:
def poll_socket(self):
r, w, x = select.select([clientsock], [], [], 0)
if r:
data=clientsock.recv(buffer).decode()
print(data)
self.add(data)
self.after(50, self.poll_socket)
def connect(self):
self.after(50, self.poll_socket)
You define xloop, however you never actually call it as far as I can see.
I would suggest you look into using threads - the threading module in the standard library would be one way to go. Then, in your code you will be able to create a thread running the xloop function, without stopping the rest of your code. Alternatively, you could remove the loop from xloop (or indeed just put the code in the function into the connect function) and call it periodically, using widget.after(milliseconds, a_function)
I'd also like to mention that from amodule import * is considered bad practice (although tkinter is one of the exceptions to this rule).
It might help to follow the flow. The "app=ipbcc(root)" step would call "self.connect()" and that has a "def xloop():" that has the step "data=clientsock.recv". But, then somebody needs to invoke xloop(). Who does that? Btw, why do have a function inside a method?
Also, I don't see anybody invoking the "clientsock.send(msg.encode())" via the write() method. I am not familiar with the Tinker part (and what the mainloop() does), so can you please check if there are callers to send() and the recv() call.
Below is a function I am using. When I call the command the spawn a putty session, it completely freezes up my gui program until I close the putty session. Is there any way to get around this, so that it calls the to the command, and then just moves along? (there is more I am passing to the command, but I have removed it to clean it up.
def SpawnSessionrmt(self,event):
if "SSH" in self.protormt:
subprocess.call('C:/bin/putty.exe ',shell=True)
elif "RDP" in self.protormt:
subprocess.call('C:/bin/rdp.exe)
else:
print "Not that you will see this...but that isn't a valid protocol"
The problem is that, as the docs say, call will:
Run the command described by args. Wait for command to complete, then return the returncode attribute.
If you don't want to wait for the command to complete, don't use call. Just create a Popen instance (and, ideally, wait for it later on, maybe at exit time, or in a background thread).
For example:
def SpawnSessionrmt(self,event):
if "SSH" in self.protormt:
self.children.append(subprocess.Popen('C:/bin/putty.exe ',shell=True))
elif "RDP" in self.protormt:
self.children.append(subprocess.Popen('C:/bin/rdp.exe'))
else:
print "Not that you will see this...but that isn't a valid protocol"
def WaitForChildren(self):
for child in self.children:
child.wait()
For a GUI app, I think the simplest thing to do might be to just put the subprocess.call into a background thread. That way, you can update the GUI when the actual work is done.
Unfortunately, each GUI framework has a different way of doing that—some let you do GUI stuff from any thread, some have a run_on_main_thread function, some let you post events to the main thread to be picked up by its event loop, some require you to build your own inter-thread communication system, etc. And you didn't tell us which GUI framework you're using.
So, here's an example with one framework I picked at random, wx:
def SpawnSessionrmt(self,event):
if "SSH" in self.protormt:
cmd = 'C:/bin/putty.exe'
elif "RDP" in self.protormt:
cmd = 'C:/bin/rdp.exe'
else:
print "Not that you will see this...but that isn't a valid protocol"
return
def background_function():
result = subprocess.call(cmd)
event = wx.CommandEvent(MY_UPDATE_ID)
event.SetInt(result)
self.GetEventHandler().AddPendingEvent(event)
t = threading.Thread(target=background_function)
t.daemon = True
t.start()
(PS, I hate my random number generator, because I hate wx… but at least it didn't pick Tkinter, which would have forced me to write my own cross-thread communication around a Queue or Condition…)
Edit: The main part of this question before this revision/update was how to terminate a QThread. That has been solved, the question is being revised to How to kill a requests rest object that is in progress.
http://docs.python-requests.org/en/v0.10.4/user/advanced/#asynchronous-requests
It appears using an Asynchronous Request still blocks - the user can't cancel the post while its in progress.
Basically this is the functionality that is needed:
When the user presses Stop Uploading, the uploading must stop instantly, I can stop the thread using stop() however it is only checked if it should stop once the loop has looped over again.
So basically, it should be possible to use an asynchronous request that would let me check if it should be cancelled during the request, however, I don't know how.
Any suggestions? The previous part of the post is still relevant so it's below.
Please note that the initial question of how to terminate a QThread has been solved, and as such the code below isn't too important, it's just for context, the thing I need help with now is what I just described.
I've been writing a program, it's a photo uploader, I've made a thread
in which I upload the files. I can't figure out how to exit the
thread. I've tried suggestions i've read from here:
1) I've tried a bool flag, wrapping it both around the method and the
for statement that does the work.
2) I've use a 'with' and then tried to set an exception.
I want to be able to cancel uploading, preferably quickly. I've read a
lot that it's always recommended to "clean up" the thread before
terminating it, I honestly don't know what 'clean it up' means.
But, I think I should be able to just kill the thread - since all it's
doing is sending the binary data of an image to the TUMBLR api. It
shouldn't matter if the request is cancelled early, because It will
just cancel the upload in the api too.
Anyway, here is my thread:
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
global w
w = WorkThread
def __del__(slf):
self.wait()
def run(self):
url = 'https://www.tumblr.com/api/write'
files = os.listdir(directory)
for file in files:
file_path = os.path.join(directory + '\\' + file)
file_path = str(file_path)
if file_path[-3:] in ['bmp', 'gif', 'jpg', 'png', 'thm', 'tif', 'yuv']:
self.emit(QtCore.SIGNAL('update(QString)'), "Uploading %s ..." % file_path)
print smart_str(password)
data = {'email': email, 'password': password, 'type': 'photo'}
with open(file_path, 'rb') as photo:
r = requests.post(url, data=data, files={'data': photo})
print r.content
self.emit(QtCore.SIGNAL('update(QString)'), 'All finished! Go check out your stuff on tumblr :)\n')
return
This is how im calling it.
def upload(self):
self.doneBox.clear()
self.workThread = WorkThread()
self.connect( self.workThread, QtCore.SIGNAL("update(QString)"), self.startUploading )
self.workThread.start()
Can anyone suggest in a way that I can terminate the thread quickly
and quietly? OR if you think that's not good, a way to stop it safely.
HOWEVER, If I do not kill it instantly, and it goes through the for
loop again in the run() method, It will upload the photo that it was
uploading WHEN the user presses "Stop Uploading". I
wouldn't want it to do that, I'd prefer to have it stop uploading the
current photo the second the user presses "Stop Uploading".
Thanks.
I'm not sure what you are doing with that global w; w = WorkThread but it seems pointless since you aren't doing anything with it.
In your __init__() you want a flag:
def __init__(self):
...
self._isRunning = False
In your run() method you just check for this flag and exit when needed:
def run(self):
self._isRunning = True
while True:
if not self._isRunning:
# clean up or close anything here
return
# do rest of work
And maybe add a simple convenience method:
def stop(self):
self._isRunning = False
self.wait()
When documentation refers to cleaning up they are talking about closing down resources that you may have opened, undoing things that might be partially started, or anything else that you might want to signal or handle before just killing the thread object. In the case of your uploader, you want to check if the thread should exit before you start each upload process. This will give you the chance to stop the thread and have it exit before it continues with another bit of work.
Try terminating it:
self.workThread.terminate()
Just be careful while doing this. Since you are performing a network operation, you can't stop it, but usually you do this:
# Logic...
if self.running:
# Continue logic...
You set self.running from outside of the thread.
I am collecting usage stats for my applications which include how much each session lasts. However, I can't seem to be able to save this information because None Of the signals I tried yet actually succeeds to call my report_session function.
This are the signals I have already tried:
lastWindowClosed()
aboutToQuit()
destroyed()
Either these signals never get emitted or the application does not live long enough after that to run anything else. Here is my main:
app = QtGui.QApplication(sys.argv)
ui = MainWindow()
ui.app = app
QtCore.QObject.connect(ui, QtCore.SIGNAL("destroyed()"), ui.report_session)
ui.show()
logger.info('Started!')
splash.finish(ui)
sys.exit(app.exec_())
The method that Mark Byers posted will run after the main widget has been closed, meaning that its controls will no longer be available.
If you need to work with any values from controls on your form, you will want to capture the close event and do your work there:
class MainWidget(QtGui.QWidget):
#...
def closeEvent(self, event):
print "closing PyQtTest"
self.SaveSettings()
# report_session()
Also, see the Message Box example in the ZetCode tutorial First programs in PyQt4 toolkit (near the end of the page). This shows how to accept or cancel the close request.
Put the code between app.exec_ and sys.exit:
ret = app.exec_()
# Your code that must run when the application closes goes here
sys.exit(ret)
To ensure that a Python function gets called at process termination, in general (with or without Qt involved;-), you can use the atexit module of the standard Python library:
import atexit
def whatever(): ...
atexit.register(whatever)
Out of prudence I would recommend against using a bound method instead of a function for this purpose -- it "should" work, but the destruction-phase of a process is always somewhat delicate, and the simpler you keep it, the better.
atexit won't trigger for a sufficiently-hard crash of a process, of course (e.g., if the process is killed with a kill -9, then by definition it's not given a chance to run any termination code) -- the OS sees to that;-). If you need to handle any crash no matter how hard you must do so from a separate "watchdog" process, a substantially subtler issue.
Found this answer which involves overloading closeEvent().
it worked perfectly for me.