Backstory: I'm creating an application using Electron and am currently attempting to run a function when the computer is locked/unlocked.
After much trial and error I finally managed to get the following python code working. The code prints either Locked or Unlocked on the screen when the relevant codes are fired. I now need to run the python script from Node JS so that I can run more functions when the events fire.
import win32con
import win32gui
import win32ts
import time
print("Test")
WM_WTSSESSION_CHANGE = 0x2B1class WTSMonitor():
className = "WTSMonitor"
wndName = "WTS Event Monitor"
def __init__(self):
wc = win32gui.WNDCLASS()
wc.hInstance = hInst = win32gui.GetModuleHandle(None)
wc.lpszClassName = self.className
wc.lpfnWndProc = self.WndProc
self.classAtom = win32gui.RegisterClass(wc)
style = 0
self.hWnd = win32gui.CreateWindow(self.classAtom, self.wndName,
style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
0, 0, hInst, None)
win32gui.UpdateWindow(self.hWnd)
win32ts.WTSRegisterSessionNotification(self.hWnd, win32ts.NOTIFY_FOR_ALL_SESSIONS)
def start(self):
win32gui.PumpMessages()
def stop(self):
win32gui.PostQuitMessage(0)
def WndProc(self, hWnd, message, wParam, lParam):
if message == WM_WTSSESSION_CHANGE:
self.OnSession(wParam, lParam)
def OnSession(self, event, sessionID):
if event == 7:
print("Locked")
if event == 8:
print("Unlocked")
print(event)
myststa(event)
WTSMonitor().start()
The Node code:
const { spawn } = require('child_process');
let py = spawn('python',['locked.py'])
py.stdout.on('data', data => console.log('data : ', data.toString()))
py.on('close', ()=>{
})
When I run python from the console using "Python locked.py" I see the test message printed. However, when running using node locked.js the script looks like it's running but never prints to the console.
The other thing to mention is that if I comment out the final line WTSMonitor().start() then I can see the test message print to the node console.
Before getting down actual troubleshooting, what's main reason to use python to invoke w32api? First you can do it via electron / nodejs itself, moreover electron will have a specific event in powerMonitor directly (https://github.com/electron/electron/blob/3a0640993ba9748ced9f9cd00de5dbfe7651f788/docs/api/power-monitor.md#event-lock-screen-macos-windows) doesn't necessarily need 3rd party codes.
Related
I am calling macros from VBA using xlwings (python 3.7). When run, the macro populates a message box
I was wondering if there's a way to suppress that (e.g. not showing the message box at all or click ok automatically) from the xlwings end (can't change macro, locked). My current setting looks like this:
app = xw.apps.active # open application instance
app.visible = False # Excel application not visible
app.display_alerts = False # supress alert messages
app.screen_updating = False # supress screen updates
Thanks!
As you can't change the macro, the left option is to click the OK button automatically. Obviously, you can't do it in the main process since it gets stuck once the message box occurs. So you need to create a child thread to do it concurrently, getting the main process back from stuck. In summary, you need two things:
a child thread, e.g. threading.Thread
a GUI automation library to catch the message box, e.g. pywin32, pywinauto or PyAutoGUI.
As you're using xlwings, pywin32 should be installed already as a dependency. So, use it here for example.
The whole process looks like:
import xlwings as xw
from listener import MsgBoxListener
# start child thread
listener = MsgBoxListener('Message-box-title-in-your-case', 3)
listener.start()
# main process as you did before
app = xw.apps.active # open application instance
app.visible = False # Excel application not visible
app.display_alerts = False # supress alert messages
app.screen_updating = False # supress screen updates
...
# stop listener thread
listener.stop()
Where MsgBoxListener is the child thread to catch and close the message box:
title is the title of message box, as hidden in your screenshot
interval is the frequency of detecting if exists a message box
# listener.py
import time
from threading import Thread, Event
import win32gui
import win32con
class MsgBoxListener(Thread):
def __init__(self, title:str, interval:int):
Thread.__init__(self)
self._title = title
self._interval = interval
self._stop_event = Event()
def stop(self): self._stop_event.set()
#property
def is_running(self): return not self._stop_event.is_set()
def run(self):
while self.is_running:
try:
time.sleep(self._interval)
self._close_msgbox()
except Exception as e:
print(e, flush=True)
def _close_msgbox(self):
# find the top window by title
hwnd = win32gui.FindWindow(None, self._title)
if not hwnd: return
# find child button
h_btn = win32gui.FindWindowEx(hwnd, None,'Button', None)
if not h_btn: return
# show text
text = win32gui.GetWindowText(h_btn)
print(text)
# click button
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None)
time.sleep(0.2)
win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None)
time.sleep(0.2)
if __name__=='__main__':
t = MsgBoxListener('Microsoft Excel', 1)
t.start()
time.sleep(10)
t.stop()
I have a Python script which creates an instance of an application and shows the application's window.
I'm trying to activate/focus the window so that it brings it to the foreground/top and has the keyboard input focus.
The code below usually works, but when the task manager's window is opened and focused before the code is executed, the application's window appears below the task manager and the task manager keeps the keyboard input focus.
The comments in the code are my attempts to circumvent the specific problem which didn't worked either. Only when using SwitchToThisWindow with False or SetWindowPos with HWND_TOPMOST (which sets the window as top most), does the window appear on top of the task manager's window, but the task manager still keeps the keyboard input focus.
def bring_window_to_top(window_handle):
import ctypes
# import win32com.client
# from win32con import HWND_TOP, HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE
current_thread_id = ctypes.windll.kernel32.GetCurrentThreadId()
foreground_window_handle = ctypes.windll.user32.GetForegroundWindow()
foreground_thread_id = ctypes.windll.user32.GetWindowThreadProcessId(foreground_window_handle, None)
ctypes.windll.user32.AttachThreadInput(current_thread_id, foreground_thread_id, True)
ctypes.windll.user32.BringWindowToTop(window_handle)
# ctypes.windll.user32.SwitchToThisWindow(window_handle, True)
# ctypes.windll.user32.SwitchToThisWindow(window_handle, False)
# ctypes.windll.user32.SetWindowPos(window_handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)
# ctypes.windll.user32.SetWindowPos(window_handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE)
# wscript_shell = win32com.client.Dispatch('WScript.Shell')
# wscript_shell.SendKeys('%')
# ctypes.windll.user32.SetForegroundWindow(window_handle)
# ctypes.windll.user32.SetFocus(window_handle)
# ctypes.windll.user32.SetActiveWindow(window_handle)
# ctypes.windll.user32.AttachThreadInput(current_thread_id, foreground_thread_id, False)
I have also attempted using the functions AllowSetForegroundWindow, LockSetForegroundWindow and SystemParametersInfoW to set SPI_SETFOREGROUNDLOCKTIMEOUT to 0 but I get the error Access denied. from ctypes.FormatError().
Is there any way to accomplish it?
You need to set UIAccess to true in the manifest to support accessibility features.
A process that is started with UIAccess rights has the following
abilities:
Set the foreground window.
Drive any application window by using the SendInput function.
Use read input for all integrity levels by using low-level hooks, raw input, GetKeyState, GetAsyncKeyState, and GetKeyboardInput.
Set journal hooks.
Use AttachThreadInput to attach a thread to a higher integrity input queue.
First, set the uiAccess=true in the manifest.
Then, sign the code.
Finally, put it in a secure location on the file system:
\Program Files\ including subdirectories
\Windows\system32\
\Program Files (x86)\ including subdirectories for 64-bit versions of
Windows
You could refer to this document and this answer.
UPDATE:
To set UIAccess to the python script:
Install PyInstaller: pip install pyinstaller.
Generate executable from Python Script using Pyinstaller.
Here is my testing sample, it sets a 5sec timer to bring the window to top.
hello.pyw:
import win32api, win32con, win32gui
import ctypes
class MyWindow:
def __init__(self):
win32gui.InitCommonControls()
self.hinst = win32api.GetModuleHandle(None)
className = 'MyWndClass'
message_map = {
win32con.WM_DESTROY: self.OnDestroy,
win32con.WM_TIMER: self.OnTimer,
}
wndcls = win32gui.WNDCLASS()
wndcls.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
wndcls.lpfnWndProc = message_map
wndcls.lpszClassName = className
win32gui.RegisterClass(wndcls)
style = win32con.WS_OVERLAPPEDWINDOW
self.hwnd = win32gui.CreateWindow(
className,
'Title',
style,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
500,
500,
0,
0,
self.hinst,
None
)
win32gui.UpdateWindow(self.hwnd)
win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
ctypes.windll.user32.SetTimer(self.hwnd,1,5000,0)
def OnDestroy(self, hwnd, message, wparam, lparam):
win32gui.PostQuitMessage(0)
return True
def OnTimer(self, hwnd, message, wparam, lparam):
current_thread_id = ctypes.windll.kernel32.GetCurrentThreadId()
foreground_window_handle = ctypes.windll.user32.GetForegroundWindow()
foreground_thread_id = ctypes.windll.user32.GetWindowThreadProcessId(foreground_window_handle, None)
ctypes.windll.user32.BringWindowToTop(hwnd)
return True
w = MyWindow()
win32gui.PumpMessages()
use --manifest <FILE or XML> option or use pyinstaller --uac-uiaccess hello.pyw directly, then the exe file is located in dist\\hello
Create the certificate and sign the application, sample(and don't forget to install the certificate to the Trusted Root Certication Authorities): https://stackoverflow.com/a/63193360/10611792
Place it in a secure location on the file system, for example I put the dist\\hello in the C:\\Program Files
Result:
I am trying to read a string from the ubuntu terminal and set that string as a label of a button. It works perfectly for some iteration and then freezes or closes with error. I couldn't find any pattern about when it freezes or closes. I am using gtk libraries and python 2.7.
A screenshot of the UI after it has frozen can be seen below.
As seen in the above screenshot, it has successfully updated the value 234, 56 and then exited with error after receiving 213 string. You can also observe that the button in the UI also has 213 value.
Sometimes the UI just freezes without displaying any errors or exiting.
I have used the below codes
1. thread.py ( main program called from terminal )
import thread
import time
import gui2
import vkeys1
import os
try:
thread.start_new_thread( vkeys1.main, ( ) )
thread.start_new_thread( gui2.main, ( ) )
except:
print "Error: unable to start thread"
# To stop this script from closing
os.system("mkfifo d1 2> error.log")
fd = os.open('d1', os.O_RDONLY)
ch = os.read(fd,1) # No writer
2. vkeys1.py ( It reads the input from terminal and calls textinit() )
import gui2
def main() :
while True:
try :
gui2.ch = str(input('\nInput a string : '))
gui2.textinit()
except :
print(" \n\n Exception!! \n\n")
3. gui2.py ( Updates the button label )
from gi.repository import Gtk, GdkPixbuf, Gdk, GLib
import Image
import os, sys
import time
import vkeys1
import threading
global ch # ch is used at vkeys1.py to store the input
ch = 'dummy content'
button0 = Gtk.Button(label="Initially empty")
class TableWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="String retrieval widget")
self.set_size_request(500,200)
self.connect_after('destroy', self.destroy)
self.main_box=Gtk.VBox()
self.main_box.set_spacing(5)
self.label = Gtk.Label(" ")
table = Gtk.Table(7,4, True)
self.add(self.main_box)
self.main_box.pack_start(self.label, False, False, 0)
self.main_box.pack_start(table, False, False, 0)
table.attach(button0, 0, 4, 0, 1)
self.show_all()
def destroy(window, self):
Gtk.main_quit()
def textinit(): # called from vkeys1.py
class Thrd(threading.Thread) :
def __init__(self) :
threading.Thread.__init__(self)
print('\nReceived string')
print(str(ch))
print('\n')
button0.set_label(str(ch)) # Button label updated here
Thrd2 = Thrd()
Thrd2.start()
return
def main():
app=TableWindow()
app.set_keep_above(True)
app.set_gravity(Gdk.Gravity.SOUTH_WEST)
Gtk.main()
if __name__ == "__main__":# for any error exit
sys.exit(main())
The above codes can be run by typing python thread.py (after creating the above 3 files off-course). Please suggest any solution to overcome this freezing problem.
The most likely cause of the crash is that your code invokes GTK code from threads other than the thread that runs the main loop, which the documentation states is not allowed.
To resolve the issue, replace the call of gui2.textinit() with GLib.idle_add(gui2.textinit) (note the lack of parentheses after textinit).
Several remarks about the code:
The generic exception handler is masking exceptions that occur. Remove it, and you will see a useful traceback when something goes wrong.
If you are running under Python 2, you probably want to change input to raw_input, otherwise the code chokes on any input that is not a valid Python expression.
textinit creates a thread object that never runs an actual thread. When inheriting from threading.Thread, one must override the run function, which will be invoked in the new thread once start() is called. Doing the work in the constructor accomplishes nothing.
thread.start_new_thread is a low-level API that should not be used in normal circumstances and that is demoted to _thread in Python 3. Instead of thread.start_new_thread(fn, ()), use threading.Thread(target=fn), which has the same meaning, and also returns a Thread object.
I see there's win32process.GetWindowThreadProcess() that gets a window handler and returns it's process id. Is there a way to do the opposite: get the window handler of a running process by it's process id? Something like win32gui.GetWindowHandler(processId) ?
Specifically What I'm trying to do:
I have a python script that runs an external program, lets say notepad.exe.
Notepad is fired when runProgram() method is called. I want to prevent this method from running Notepad more than once. I accomplish this in the following way, using win32process:
import win32process as process
import sys
PORTABLE_APPLICATION_LOCATION = "C:\\Windows\\system32\\notepad.exe"
processHandler = -1
def runProgram():
global processHandler
#don't run a process more than once
if (isLiveProcess(processHandler)):
#Bring focus back to running window!
return;
try:
startObj = process.STARTUPINFO()
myProcessTuple = process.CreateProcess(PORTABLE_APPLICATION_LOCATION,None,None,None,8,8,None,None,startObj)
processHandler = myProcessTuple[2]
except:
print(sys.exc_info[0])
def isLiveProcess(processHandler): #Process handler is dwProcessId
processList = process.EnumProcesses()
for aProcess in processList:
if (aProcess == processHandler):
return True
return False
runProgram()
This works as expected, but if the process is found to be already alive, I'd like to bring it's window back to front with win32gui
I dont think that Windows API provides a method for this , but you could iterate over all open windows , and find the one that belongs to you .
I have modified your program so it looks like this :
import win32process
import win32process as process
import win32gui
import sys
PORTABLE_APPLICATION_LOCATION = "C:\\Windows\\system32\\notepad.exe"
processHandler = -1
def callback(hwnd, procid):
if procid in win32process.GetWindowThreadProcessId(hwnd):
win32gui.SetForegroundWindow(hwnd)
def show_window_by_process(procid):
win32gui.EnumWindows(callback, procid)
def runProgram():
global processHandler
#don't run a process more than once
if (isLiveProcess(processHandler)):
#Bring focus back to running window!
show_window_by_process(processHandler)
return;
try:
startObj = process.STARTUPINFO()
myProcessTuple = process.CreateProcess(PORTABLE_APPLICATION_LOCATION,None,None,None,8,8,None,None,startObj)
processHandler = myProcessTuple[2]
except:
print(sys.exc_info[0])
def isLiveProcess(processHandler): #Process handler is dwProcessId
processList = process.EnumProcesses()
for aProcess in processList:
if (aProcess == processHandler):
return True
return False
runProgram()
When using win32api.setConsoleCtrlHandler(), I'm able to receive shutdown/logoff/etc events from Windows, and cleanly shut down my app.
However, this only works when running the app under python.exe (i.e., it has a console window), but not under pythonw.exe (no console window).
Is there an equivalent way in Windows to receive these events when you have no console and no window to receive them? Or, is there a programmatic way to hide the console window?
To be clear - my goal is to be able to successfully receive Windows shutdown/logoff/etc events, without having any kind of console window showing.
EDIT:
I've been playing around, and I've gotten quite a bit further. I wrote a piece of test code for this. When I do a taskkill /im pythonw.exe - it will receive the message.
However, when I do a shutdown, restart, or logoff on Windows, I do not get any messages.
Here's the whole thing:
""" Testing Windows shutdown events """
import win32con
import win32api
import win32gui
import sys
import time
def log_info(msg):
""" Prints """
print msg
f = open("c:\\test.log", "a")
f.write(msg + "\n")
f.close()
def wndproc(hwnd, msg, wparam, lparam):
log_info("wndproc: %s" % msg)
if __name__ == "__main__":
log_info("*** STARTING ***")
hinst = win32api.GetModuleHandle(None)
wndclass = win32gui.WNDCLASS()
wndclass.hInstance = hinst
wndclass.lpszClassName = "testWindowClass"
messageMap = { win32con.WM_QUERYENDSESSION : wndproc,
win32con.WM_ENDSESSION : wndproc,
win32con.WM_QUIT : wndproc,
win32con.WM_DESTROY : wndproc,
win32con.WM_CLOSE : wndproc }
wndclass.lpfnWndProc = messageMap
try:
myWindowClass = win32gui.RegisterClass(wndclass)
hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
myWindowClass,
"testMsgWindow",
0,
0,
0,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
win32con.HWND_MESSAGE,
0,
hinst,
None)
except Exception, e:
log_info("Exception: %s" % str(e))
if hwnd is None:
log_info("hwnd is none!")
else:
log_info("hwnd: %s" % hwnd)
while True:
win32gui.PumpWaitingMessages()
time.sleep(1)
I feel like I'm pretty close here, but I'm definitely missing something!
The problem here was that the HWND_MESSAGE window type doesn't actually receive broadcast messages - like the WM_QUERYENDSESSION and WM_ENDSESSION.
So instead of specifying win32con.HWND_MESSAGE for the "parent window" parameter of CreateWindowEx(), I just specified 0.
Basically, this creates an actual window, but I never show it, so it's effectively the same thing. Now, I can successfully receive those broadcast messages and shut down the app properly.
If you don't have a console, setting a console handler of course can't work. You can receive system events on a GUI (non-console) program by making another window (doesn't have to be visible), making sure you have a normal "message pump" on it serving, and handling WM_QUERYENDSESSION -- that's the message telling your window about shutdown and logoff events (and your window can try to push back against the end-session by returning 0 for this message). ("Windows Services" are different from normal apps -- if that's what you're writing, see an example here).