I have a small GUI application that listens for network messages so a user can update some info and accept it. This is in a production factory environment and used for interacting with a specific piece of physical hardware (over serial in some cases). The workflow looks like this:
User is interacting with another program (5250 Green Screen)
They enter a certain keybinding that sends a UDP message to a Tkinter GUI
The Tkinter GUI does a deiconify()
User edits data, accepts (Enter) and it does an iconify()
My issue is that on windows XP, the GUI does not become active when I do the deiconify and conversely does not fall back to the prior window on iconify. I have tried some things I found in other questions such as:
Setting the Tk GUI as top.. self.wm_attributes("-topmost", 1)
Trying to set/force focus... self.focus_set() and self.focus_force()
Although the window is visible with the first, I can not seem to get it to be the active window so that the user can type in it without "clicking" on it to activate. The same is true for releasing the "focus" so that the active window becomes the one they were previously on (5250).
It seems like an issue that others also have had but I have not been able to find anything that works. Is there a programmatic way to get the window activated and release it when done?
Unfortunately, after a week there have been no answers and I was not able to find a direct way to do this with Tkinter. I did find a way to solve the problem though and it appears to work consistently. Here are the steps I took to make the screens activate:
Install pywin32.
Create a function that activates the tk app.
Create a function that activates the 5250.
Then each time I do a iconify/deiconify I also run the function to activate the appropriate screen. The code that activates the tk window looks like this:
def activate_self(self):
""" Activate this window. """
shell = win32com.client.Dispatch('WScript.Shell')
shell.AppActivate(str(self.title))
shell = None
The code that activates the caller is a little ugly since it has to guess the title but is the same basic concept.
Related
I am trying to programmatically interrupt the screensaver by moving the cursor like this:
win32api.SetCursorPos((random.choice(range(100)),random.choice(range(100))))
And it fails with the message:
pywintypes.error: (0, 'SetCursorPos', 'No error message is available')
This error only occurs if the screensaver is actively running.
The reason for this request is that the computer is ONLY used for inputting data through a bluetooth device (via a Python program). When the BT device sends data to the computer the screensaver is not interrupted (which means I cannot see the data the BT device sent). Thus, when the Python program receives data from the BT device it is also supposed to interrupt the screensaver.
I have seen several solution on how to prevent the screensaver from starting (which are not suitable solutions in my case), but none on how to interrupt a running screensaver. How can I do this, using Windows 10 and Python 3.10?
The Windows operating system has a hierarchy of objects. At the top of the hierarchy is the "Window Station". Just below that is the "Desktop" (not to be confused with the desktop folder, or even the desktop window showing the icons of that folder). You can read more about this concept in the documentation.
I mention this because ordinarily only one Desktop can receive and process user input at any given time. And, when a screen saver is activated by Windows due to a timeout, Windows creates a new Desktop to run the screen saver.
This means any application associated with any other Desktop, including your Python script, will be unable to send input to the new Desktop without some extra work. The nature of that work depends on a few factors. Assuming the simplest case, a screen saver that's created without the "On resume, display logon screen", and no other Window Station has been created by a remote connection or local user login, then you can ask Windows for the active Desktop, attach the Python script to that Desktop, move the mouse, and revert back to the previous Desktop so the rest of the script works as expected.
Thankfully, the code to do this is easier than the explanation:
import win32con, win32api, win32service
import random
# Get a handle to the current active Desktop
hdesk = win32service.OpenInputDesktop(0, False, win32con.MAXIMUM_ALLOWED);
# Get a handle to the Desktop this process is associated with
hdeskOld = win32service.GetThreadDesktop(win32api.GetCurrentThreadId())
# Set this process to handle messages and input on the active Desktop
hdesk.SetThreadDesktop()
# Move the mouse some random amount, most Screen Savers will react to this,
# close the window, which in turn causes Windows to destroy this Desktop
# Also, move the mouse a few times to avoid the edge case of moving
# it randomly to the location it was already at.
for _ in range(4):
win32api.SetCursorPos((random.randint(0, 100), random.randint(0, 100)))
# Revert back to the old desktop association so the rest of this script works
hdeskOld.SetThreadDesktop()
However, if the screen saver is running on a separate Window Station because "On resume, display logon screen" is selected, or another user is connected either via the physical Console or has connected remotely, then connecting to and attaching to the active Desktop will require elevation of the Python script, and even then, depending on other factors, it may require special permissions.
And while this might help your specific case, I will add the the core issue in the general case is perhaps more properly defined as asking "how do I notify the user of the state of something, without the screen saver blocking that notification?". The answer to that question isn't "cause the screen saver to end", but rather "Use something like SetThreadExecutionState() with ES_DISPLAY_REQUIRED to keep the screen saver from running. And show a full-screen top-most window that shows the current status, and when you want to alert the user, flash an eye-catching graphic and/or play a sound to get their attention".
Here's what that looks like, using tkinter to show the window:
from datetime import datetime, timedelta
import ctypes
import tkinter as tk
# Constants for calling SetThreadExecutionState
ES_CONTINUOUS = 0x80000000
ES_SYSTEM_REQUIRED = 0x00000001
ES_DISPLAY_REQUIRED= 0x00000002
# Example work, show nothing, but when the timer hits, "alert" the user
ALERT_AT = datetime.utcnow() + timedelta(minutes=2)
def timer(root):
# Called every second until we alert the user
# TODO: This is just alerting the user after a set time goes by,
# you could perform a custom check here, to see if the user
# should be alerted based off other conditions.
if datetime.utcnow() >= ALERT_AT:
# Just alert the user
root.configure(bg='red')
else:
# Nothing to do, check again in a bit
root.after(1000, timer, root)
# Create a full screen window
root = tk.Tk()
# Simple way to dismiss the window
root.bind("<Escape>", lambda e: e.widget.destroy())
root.wm_attributes("-fullscreen", 1)
root.wm_attributes("-topmost", 1)
root.configure(bg='black')
root.config(cursor="none")
root.after(1000, timer, root)
# Disable the screen saver while the main window is shown
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED)
root.mainloop()
# All done, let the screen saver run again
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS)
While more work, doing this will solve issues around the secure desktop with "On resume, display logon screen" set, and also prevent the system from going to sleep if it's configured to do so. It just generally allows the application to more clearly communicate its intention.
SetCursorPos is failing because the cursor is probably set to NULL while the screensaver is running.
Instead of moving the cursor, try to find the current screensaver executable path and just kill the process. I think, this will be a fine solution.
you can check the Windows Registry record to obtain a filename of the screensaver (HKEY_USERS\.DEFAULT\Control Panel\Desktop\SCRNSAVE.EXE (msdn)
or you can check currently running processes list to find the one with .scr extension
Then just kill the process using TerminateProcess or just os.system('taskkill /IM "' + ProcessName + '" /F')
This is a classic XY problem: Say, you manage to stop the screensaver from turning up on your machine/test setup. But there are further questions:
What happens if your program runs on a terminal server that doesn't have an UI session?
Does your solution work if the power saving settings are set in such a way that they put the computer to sleep after a certain amount of time?
Will it work with future windows versions? With different subproducts? (the creative "look at this undocumented registry key and then kill some random process" solution seems destined for this)
Who knows and definitely hard to test.
What you really need is a way to tell the OS "hey I'm busy and keep the session active even if your normal heuristics would tell you that the user is away". This is a standard problem which video players and presentation software faces all the time.
The standard solution is to use SetThreadExecutionState with something along the lines of ES_DISPLAY_REQUIRED | ES_CONTINUOUS (and possibly other flags as well - the documentation is quite reasonable there) at the start of the program.
Raymond Chen has written about this in the past (no surprise there).
Note that this doesn't stop an already active screensaver - this is generally not a problem, because you can set the flag at startup (or when the intended action is triggered). It also doesn't stop the user from putting the computer manually to sleep, but that's something you shouldn't generally disable.
is it possible to send the alt+tab for switch to last window with AutoKey ?
i tried without success:
keyboard.send_keys("<alt>+<shift>+<tab>")
Or forward window:
keyboard.press_key('<alt>')
keyboard.press_key('<tab>')
keyboard.release_key('<tab>')
keyboard.release_key('<alt>')
Or backward window:
keyboard.press_key('<alt>')
keyboard.press_key('<shift>')
keyboard.press_key('<tab>')
keyboard.release_key('<tab>')
keyboard.release_key('<shift>')
keyboard.release_key('<alt>')
result: no error but only moves the tab count inside the editor.
TL;DR: Not directly with our API.
The AutoKey API talks directly to the current active window. So, sending events targeted at the desktop (DTE) will only work if the current active window recognizes them as such and either forwards them to the DTE or emulates what they do.
However, since AutoKey scripts are written in full Python 3, if you can figure out how to do it yourself in Python, AutoKey can run it for you. And, if some other solution is available, you can run it from within an AutoKey script using the subprocess module.
Autokey's Window class allows you to activate a window by name (via wmctrl), among other functionality. Something in that class may be what you're looking for.
Hello I found a little issue with using the turtle library and python's IDLE. Observe the following code written in Python 3.5.2:
import turtle
turtle.Turtle()
input("Try moving/resizing the window in IDLE. Press enter than try again.")
Before pressing enter to give your input, the turtle window will be labeled as unresponsive and not allow the user to resize it. That is, if it's ran with IDLE. However, it works perfectly fine when ran through something such as the windows command prompt or PowerShell.
If ran through IDLE, after the user presses enter the window may then be freely moved and resized with no issues. This comes with the drawback that we're no longer inside the script but back to python's shell. Of course, we can still interact with the turtle via the shell but this isn't what I want in my actual application. My main program uses input() to ask the user if they wish to move/rotate and by how much. This remains in an endless loop until the user enters a phrase such as 'quit' to end the program. I will note, even with IDLE the turtle still moves and is drawn correctly. The problem is that the window itself becomes unresponsive. The contents is all correct.
I'm very curious to as of why only IDLE is giving me such behavior... Not only that, is there a way I can keep turtle window from being unresponsive and still make use of input() and the like? Or perhaps there is an alternative I did not think of? My research has lead me to believe this has something to do with TkInter. That would explain why it only works outside of IDLE.
Below is an image that shows my predicament.
Unresponsive turtle window
As you suspected, the primary issue is with tkinter, not specifically with the turtle app that uses tkinter. I first reproduced in IDLE with a bare tk window.
import tkinter as tk
root = tk.Tk()
input('prompt: ')
I then did the above a line at a time in 3.5.2 running interactively in Command Prompt on Win10. The second line displays a bare window, add a entry in TaskManager, and adds a Python icon to the task bar. Clicking back and forth between CP and Tk causes the the corresponding window and icon to get the 'active' appearance. The window border shifts from gray to black, whereas the icon background shifts from black to gray.
While typing the input statement, but before hitting return, the Tk window shows 'not responding' (after a couple of seconds) both on the title bar and in TaskManager. The mouse over the inside of the window becomes the blue busy circle. The window becomes 'active' as described above, and can be moved, but cannot be resized and cannot be closed normally (clicking on [X] brings up the "Not responding, close or wait?" box.
When the statement is Entered, the tk window becomes normally responsive again. As soon as the 'user' enters anything, the tk window becomes unresponsive in the manner described above, until entry is completed with Enter.
If you retry in CP and enter some characters without hitting Enter, do you see the same unresponsiveness?
Differences in IDLE's shell: creating root puts a new entry in TaskManager, but the new window is attached to the IDLE icon. While the input statement is being written, the Tk window remains normally responsive. So at this stage, IDLE is better. When the input() statement is Entered, the Tk window becomes, as you noted, completely unresponsive (cannot move) even before any entry. This part is worse.
I tried one further experiment: running IDLE with the -n ('no subprocess') option.
C:\Users\Terry>python -m idlelib -n
In this mode, there is no problem that I could detect (unlike CP). This was still true when I ran turtle. You can ignore the deprecation warning for now.
Comment 1: IDLE is designed for development and learning, not for production execution. But it can be used for the latter if there is an advantage to doing so. You would just have to check that using -n does not introduce any other problems, or rather, that turtle and your code do not interfere with IDLE when running in the same process.
Comment 2: GUI programs usually do not use input and print. They are usually run without a console to interact with. If input() is used, even in the Windows console, user should not touch the window before responding to a prompt.
Comment 3: GUI programs usually use GUI widgets instead to fetch and display info. Turtle makes this more difficult, but not impossible, as demonstrated by turtledemo. You might be simplify and adapt the code in turtledemo/main.py.
I trying to make an application with a pop-up menu - when I type SPACE-R_ALT on my keyboard, globally across the OS (Windows in my case). When that happens, I want to pop-up a window (I know how to do that), and it is crucial that I can happen to be using Chrome or Word, then tap Space-Right Alt, then be able to open up this little menu.
Tkinter event bindings have two problems:
First, when I use an event binding for <Key> and then, in the
function, use evt.keysym, I can see that the program can't register
both at the same time. I could use a timer and then see if it works,
but I would prefer one line that fixes it all.
Second, I find that tkinter event bindings only work when the binded widget's window (or window itself) is FOCUSED. I will hide my root and TopLevel at all times, and so is not focused.
I would appreciate any help on this. If your suggestion uses another module, I don't really care, as long as it works on Windows 10 (not Mac OS X, not Linux, but Windows). I'm also using Python 3, but any version (aka 2) would also be okay, as I could either try to port YOUR suggestion to Py3, or port MY code to Py2. Thanks!
I figured it out with Furas's help - with Pyhook I can wait for events globally, and then tie in the event with tkinter events.
On Windows it is easy. Just run your program with pythonw instead with python and code will be executed in the background.
So, the thing I wish to achieve is easily arranged.
I have an application which is really a service doing underground stuff. But this service needs a control panel.
So, on Windows I use wxPython to create a GUI, even some wx stuff to provide needed service, and when user is done with adjustments she/he clicks Hide and Show(False) is called on main window.
Thus the GUI disappears and the service continues its work in the background. User can always bring it back using a hotkey.
The trouble is that on Mac OS X this strategy works only to some degree.
When wx.Frame.Show(False) is called, the window disappears along with its menu bar and service works fine, but the Application is still visible there.
You can switch to it regardless the fact that you cannot do anything with it. It is still present in the Dock etc. etc.
This happens when program is using python or pythonw or when it is bundled with Py2App.
No matter what I do, the icon stays there.
There must be some trick that allows a programmer to remove this naughty icon and thus stop bothering poor little user when she/he doesn't want to be bothered.
Hiding window is obviously not enough. Anyone knows the trick?
N.B.: I would really like to do it the way I described above and not mess with two separate processes and IPC.
Edit:
After much digging I found these:
How to hide application icon from Mac OS X dock
http://codesorcery.net/2008/02/06/feature-requests-versus-the-right-way-to-do-it
How to hide the Dock icon
According to last link the proper way to do it is to use:
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];
or
[NSApp setActivationPolicy: NSApplicationActivationPolicyProhibited];
So what I want (runtime switching from background to foreground and back) is possible.
But how to do it from Python???
Constants: NSApplicationActivationPolicyProhibited and NSApplicationActivationPolicyAccessory are present in AppKit, but I cannot find setApplicationActivationPolicy function anywhere.
NSApp() doesn't have it.
I know there is a way of doing it by loading objc dylib with ctypes, delegating to NSApp and sending "setApplicationActivationPolicy: <constant_value>", but I don't know how much will this mess with wx.App(). And it is a bit much work for something that should be available already.
In my experience, NSApp() and wx.App() active at the same time dislike eachother pretty much.
Perhaps we can get the NSApp() instance that wx is using somehow and use wx's delegate???
Remember please, already suggested solutions with starting as agent and switching to foreground or running multiple processes and doing IPC is very undesirable in my case.
So, ideally, using setApplicationActivationPolicy is my goal, but how? (Simple and easy and no messup to wx.App() please.)
Any ideas???
OK people, there is a good, nice and correct solution without any messing around.
Firstly, I want to explain why Windows GUI process goes to background when wx.Frame.Show(MyFrame, False) is called.
Very short explanation and skipping over details is that Windows consider the Window and an application the same thing.
I.e. The main element of the MS Windows application is your main GUI window.
So, when this window is hidden, an application has no more GUI and continues to run in background.
Mac OS X considers the application to be your application and any windows you choose to put into it are its children so to speak.
This allows you to have an application running while presenting no windows but a menu bar, from which you may choose an action which would then generate a needed window.
Very handy for editors where you may have more than one file opened at once, each in its own window and when you close the last one, you can still open a new one or create a blank one, etc. etc.
Therefore a main element of Mac OS X application is the application itself, and that is why it stays opened after last window is hidden, logically. Destroying its menu bar also will not help. The name of the app will stay present in Dock and in application switcher and in Force Quit. You will be able to switch to it and do nothing. :D
But, luckily, Mac provides us with function to put it to background though. And this function is already mentioned setApplicationActivationPolicy() from NSApp object.
The trouble was its naming in Python's AppKit, which is NSApp.setActivationPolicy_(). To complicate matters further, it is not available directly from Python's interactive shell but it has to be called at least from an imported module.
Why? I have no idea. Anyway here is a complete example for throwing an application into background that will work on Mac and Windows.
I didn't try it on Linux, which combines behaviour of Mac and Windows in matter of presenting an app, so, whether only hiding a window would be enough remains to be seen.
Feel free to try and submit an edit to make the example more cross-platform.
Example:
"""
This app will show you small window with the randomly generated code that will confirm that reopened window is still the same app returned from background,
and the button allowing you to send it to background.
After you send it to background, wait 8 seconds and application will return to foreground again.
Too prove that the application is continuing its work in the background, the app will call wx.Bell() every second.
You should hear the sound while app is in the foreground and when it is in background too.
Merry Christmas and a happy New Year!
"""
import wx
import random, sys
if sys.platform=="darwin":
from AppKit import NSBundle, NSApp, NSAutoreleasePool, NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyProhibited
# Use Info.plist values to know whether our process started as daemon
# Also, change this dict in case anyone is later checking it (e.g. some module)
# Note: Changing this dict doesn't change Info.plist file
info = NSBundle.mainBundle().infoDictionary()
def SendToBackground ():
# Change info, just in case someone checks it later
info["LSUIElement"] = "1"
NSApp.setActivationPolicy_(NSApplicationActivationPolicyProhibited)
def ReturnToForeground ():
# Change info, just in case someone checks it later
info["LSUIElement"] = "0"
NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular)
else:
# Simulate Mac OS X App - Info.plist
info = {"LSUIElement": "0"} # Assume non background at startup
# If programmer chose not to display GUI at startup then she/he should change this before calling ReturnToForeground()
# To preserve consistency and allow correct IsDaemon() answer
def SendToBackground ():
info["LSUIElement"] = "1"
def ReturnToForeground ():
info["LSUIElement"] = "0"
def IsDaemon ():
return info["LSUIElement"]=="1"
class Interface (wx.Frame):
def __init__ (self):
wx.Frame.__init__(self, None, -1, "Test", pos=(100, 100), size=(100, 100))
wx.StaticText(self, -1, "Test code: "+str(random.randint(1000, 10000)), pos=(10, 10), size=(80, 20))
b = wx.Button(self, -1, "DAEMONIZE ME", size=(80, 20), pos=(10, 50))
wx.EVT_BUTTON(self, b.GetId(), self.OnDaemonize)
self.belltimer = wx.Timer(self)
wx.EVT_TIMER(self, self.belltimer.GetId(), self.OnBellTimer)
self.belltimer.Start(1000)
# On Mac OS X, you wouldn't be able to quit the app without the menu bar:
if sys.platform=="darwin":
self.SetMenuBar(wx.MenuBar())
self.Show()
def OnBellTimer (self, e):
wx.Bell()
def OnDaemonize (self, e):
self.Show(False)
SendToBackground()
self.timer = wx.Timer(self)
wx.EVT_TIMER(self, self.timer.GetId(), self.OnExorcize)
self.timer.Start(8000)
def OnExorcize (self, e):
self.timer.Stop()
ReturnToForeground()
self.Show()
self.Raise()
app = wx.App()
i = Interface()
app.MainLoop()
Of course, this example may be started from terminal or with CLI window. In this case the terminal control over your program will stay opened while app only will appear and disappear.
To complete your GUI daemon, you should start it with pythonw (on Windows) or launch it from daemontest.pyw file,
and on Mac you should use:
% nohup python daemontest.py &
or bundle it with py2app or use Python launcher that comes with python.org Python version to start daemontest.py without terminal.
Note: This example suffers from the same flaw on Mac OS X that is mentioned on links I supplied in my question. I refer to the problem of wrong focusing and menu bar not instantly appearing when app comes from background. User has to switch around and come back to newly returned app for it to work properly. I hope somebody will solve this too. And soon. It is quite annoying.
One more note: If you have threads running in your program, pause them while daemonizing and exorcizing. Especially if they are communicating with another app using Apple events. To be frank, something about wx.Timers should be done too. If you are not careful you may get leaking problems around non-existing NSAutoreleasePool and/or SegmentationFault upon program termination.
Ok. Here is the code to do what you want to do:
import AppKit
info = AppKit.NSBundle.mainBundle().infoDictionary()
info["LSUIElement"] = "1"
This the messier answer you do not want to do, but I will list it anyway. In the info.plist file add in this key:
<key>LSUIElement</key>
<string>1</string>
Another more daemonish solution but means it can't have a GUI, you add in this key to the info.plist file:
<key>LSBackgroundOnly</key>
<string>1</string>
Source