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.
Related
So I'm building this joke program in Python. Basically, what it is and does is it is an app disguised as a game, and when opened, the headphone port is disabled, the audio output is set to computer speakers, volume is set to max, and it says 'gamer alert' many times. The problem I am having is that there is no way to stop it; even if you delete the application while it is running still won't do anything. I need a method of detecting a keyboard shortcut made to stop it. It needs to be compatible with Mac.
Note: if you want to test, you can set it to a lower volume by changing 100 to something around 20 or 30.
I've tried using PyGame, Tkinter, Pynput, Getch, etc. all didn't work. I'm not too sure if it's because I'm not using them right, or if they won't work at all with what I'm trying to do.
import os
while True:
os.system('SwitchAudioSource -s "Built-in Output"')
os.system("osascript -e 'set volume output volume 100'")
os.system("say gamer alert")
I have gotten a few error messages with some of the modules I mentioned above, but otherwise no error messages. The voice might stop after one iteration. Without this kill feature, it works beautifully.
Is there any way to start/lunch a program through Process in another screen?
Someone asked that here but there was no answer.
Note: it is not a form in my app, I'm asking about running an external program in another screen!
Since the window is not yours, you can only move it by invoking the Windows API. You will have to do this:
Launch the process.
Use FindWindow to retrieve the handle to the window. If the window doesn’t exist yet, the process hasn’t created it yet; sleep for 500ms and then try again. (But don’t go into an infinite loop; stop if you can’t find the window after a reasonable timeout.)
Use SetWindowPos to change the position of the window.
If you don’t know the title of the window, you can’t use FindWindow. In that case,
Launch the process and get the process handle by retrieving Process.Handle.
Use EnumWindows to retrieve all the windows. For each window, use GetWindowThreadProcessId to check whether it belongs to your process. If no window belongs to your process, wait and keep trying.
Use SetWindowPos to change the position of the window.
Of course, you can use Screen.AllScreens[n].WorkingArea to retrieve the position and size of the screen you want, and then you can position the window relative to that.
First get out the area of the second monitor using something like:
Rectangle area = Screen.AllScreens[1].WorkingArea;
The use the Windows API SetWindowPos to move it, using the Process.MainWindowHandle you got from the starting of the other process as the handle.
Timwi provided a very useful tip, so I decided to create a powershell script calling a library with these functions for easier use, and share the solution.
You may have a look at the solution on GitHub: https://github.com/alex-tomin/Tomin.Tools.KioskMode
I needed to run multiple Chrome windows on startup, and the solution on GitHub targets exactly this problem (related question: https://superuser.com/a/901790/111424).
But the underlying logic is the same:
Find Windows Handle to operate with. You may use FindWindow or EnumWindows in generic case as Timwi mentioned. But if your process is simple one and has a single main window, it is just:
var hndl = proc.MainWindowHandle
Having the handle, you may use the following function. You just need to provide Display number (starting from 1) and the handle:
public static bool MoveToMonitor(IntPtr windowHandle, int monitor)
{
monitor = monitor - 1;
return WinApi.SetWindowPos(windowHandle, IntPtr.Zero, Screen.AllScreens[monitor].WorkingArea.Left,
Screen.AllScreens[monitor].WorkingArea.Top, 1000, 800, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOREDRAW);
}
All enums and function imports you may find on http://www.pinvoke.net/ or just copy my code on GitHub: https://github.com/alex-tomin/Tomin.Tools.KioskMode.
You would need to start the process, get the processes main window and use an API call like SetWindowPos() to move the window to the screen you want.
Try to put this code in the form_load method:
this.setdesktoplocation(int x, int y);
this.windowstate = formwindowstate.maximized;
The value of x must be greater than the width of your main screen. For example, if your main screen has a resolution of 1366 by 786 pixels, you should give x a value of at least 1368 or above.
It worked for me. But it is just for debugging purposes only. After all, you'll have to run it in the main monitor when the app is finished.
If you do not maximize your window but have it non-maximized when you close it, the next time the app is being opened, it will appear on the monitor it was on last time.
If the app / window is maximized when you close it, and if you open it up again next time, it will appear on your primary monitor instead.
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
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.
I'm trying to write an Xvfb-to-HTML5-canvas tool that will need to know when an X11 window changes so it can send a screen update to the client. Think of it like a web-based VNC or RDP but just for X11 windows (why send the whole desktop? =).
I thought there would be a straightforward way to do this via Xlib or xcb (xpyb) but in my experiments the best I've been able to do is detect when a window is created, destroyed, or moved. That's great and all but I need to know when the contents of windows change as well (imagine sending a keystroke to an xterm and having it appear frozen until you move the window).
If someone knows of a way to tell when the contents of an X11 window have changed I'd love to hear it! I'm open to creative solutions. For example, I tried using ffmpeg to stream x11grab through a fifo with regular checks to see if anything changed but it turned out to be extremely inefficient in terms of CPU utilization (it also seems to slow the whole system down even if nothing is going on).
I also tried just grabbing 15fps worth of screenshots in a loop while checking for changes in the most efficient way I could (e.g. does this cStringIO buffer match the last one?). That also was very CPU intensive.
The ideal solution would be for me to be able to watch the file descriptor of a socket and call a handler when there's a change in the X11 window. I'm willing to settle for detecting when the whole X11 screen has a change... That'd still be better than what I've got.
Any and all help with this is appreciated!
First of all, you can actually use vnc to track changes in just one window, not whole desktop. From x11vnc documentation:
-id windowid Show the X window corresponding to "windowid" not
the entire display. New windows like popup menus,
transient toplevels, etc, may not be seen or may be
clipped. Disabling SaveUnders or BackingStore in the
X server may help show them. x11vnc may crash if the
window is initially partially obscured, changes size,
is iconified, etc. Some steps are taken to avoid this
and the -xrandr mechanism is used to track resizes. Use
xwininfo(1) to get the window id, or use "-id pick"
to have x11vnc run xwininfo(1) for you and extract
the id. The -id option is useful for exporting very
simple applications (e.g. the current view on a webcam).
-sid windowid As -id, but instead of using the window directly it
shifts a root view to it: this shows SaveUnders menus,
etc, although they will be clipped if they extend beyond
the window.
-appshare Simple application sharing based on the -id/-sid
mechanism. Every new toplevel window that the
application creates induces a new viewer window via
a reverse connection. The -id/-sid and -connect
options are required. Run 'x11vnc -appshare -help'
for more info.
If you want to code similar functionality manually you need to use damage extension.
Here is simple example in javascript using node-x11 (sorry, I'm not sure about damage extension support in python)
var x11 = require('x11');
var X = x11.createClient(function(err, display) {
X.require('damage', function(Damage) {
var damage = X.AllocID();
Damage.Create(damage, parseInt(process.argv[2]), Damage.ReportLevel.NonEmpty);
X.on('event', function(ev) {
Damage.Subtract(damage, 0, 0);
console.log("window content changed!");
});
});
});
start it with window id as command line argument and you'll be notified whenever window content is changed.