I am trying to run a python script in Windows to send an F5 command to a Google Chrome v53 window. But unfortunately nothing happens. But it works like a charm with Internet Explorer v11 (after changing the Class Name).
Winspector detects the Class Name of the Google Chrome window
Python finds the window and sends the command
No message gets logged as send to the process
Python code
import win32gui, win32api, win32con
hwnd = win32gui.FindWindow("Chrome_WidgetWin_1", None)
print hwnd
# Sending command to the main windows also does not work
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
hwnd = win32gui.FindWindowEx(hwnd, None, "Chrome_RenderWidgetHostHWND", None)
print hwnd
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
Python output
1181972
0
EDIT: Included hidden windows
There seems to be more windows, that have the same Class Name. Therefore, I am now searching not only by the Class Name, but also by the Text. The first attempt did only find a window, which did not have any children. Therefore the second line of the output had a 0.
Altered Python code
import win32gui, win32api, win32con
hwnd = win32gui.FindWindow("Chrome_WidgetWin_1", "Stack Overflow - Google Chrome")
print hwnd
# Sending command to the main windows also does not work
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
hwnd = win32gui.FindWindowEx(hwnd, None, "Chrome_RenderWidgetHostHWND", None)
print hwnd
win32api.PostMessage(hwnd, win32con.WM_KEYDOWN, win32con.VK_F5, 0)
Python output
1247302
1509536
EDIT: SetForegroundWindow solution
The solution for the problem was first to set the window to foreground by win32gui.SetForegroundWindow(Handle) method and then to send the key commands.
But still, no commands are passed to the window and the website is not refreshed.
You can use this
import pyautogui
pyautogui.hotkey('f5')
This will simulate your refresh key, sending the signal to chrome.
In case you don't want the chrome window to be made active, then when you START chrome, start it by typing this in the command prompt:
start /B chrome.exe
Then close chrome. It will run in the background. When the work is done, go Task Manager and close chrome.
OR you could go to Chrome>Advanced>System and see the first option.
Related
I'm trying to build simple bot that will periodically send invites to the guild in game chat.
Developing scripts in bluestacks was an easy part, now I'm trying to develop a simple python program that will trigger those scripts by sending input to bluestacks.
Using Spy++ I figured out that app contains multiple windows and has the following structure:
[-] Window 00010912 "InviteBot" HwndWrapper[Bluestacks.exe;;<a long hex code>]
[-] Window 00030932 "BlueStacks Android PluginAndroid_1" WindowsForms10.Window.8.app.0.1<hex part>
[] Window 000409D0 "" WindowsForms10.EDIT.app.0.1<same hex part as above>
[] Window 0002092E "_ctl.Window" BlueStacksApp
Using 'Find Window' functionality of Spy++ pointed me to the last layer - '_ctl.Window'.
After googling I've found 2 approaches (on StackOverflow) to send input to the app, first one:
wsh = comclt.Dispatch("WScript.Shell")
wsh.AppActivate("InviteBot") # select another application
wsh.SendKeys("q") # send the keys you want
works good, but activates the window which makes diffcult to work on PC when it sends an input, so I needed another approach:
def enum_handler(hwnd, lparam):
if win32gui.IsWindowVisible(hwnd):
if 'InviteBot' in win32gui.GetWindowText(hwnd):
invite_bot_handle = hwnd
print("invite_bot_handle: {}".format(invite_bot_handle))
print(win32gui.GetWindowText(invite_bot_handle))
win32gui.PostMessage(invite_bot_handle, win32con.WM_CHAR, 'q', 0)
win32gui.PostMessage(invite_bot_handle, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
win32gui.PostMessage(invite_bot_handle, win32con.WM_KEYUP, win32con.VK_F1, 0)
blue_stacks_app_handle = win32gui.FindWindowEx(invite_bot_handle, None, None, "BlueStacks Android PluginAndroid_1")
print("blue_stacks_app_handle: {}".format(blue_stacks_app_handle))
print(win32gui.GetWindowText(blue_stacks_app_handle))
win32gui.PostMessage(blue_stacks_app_handle, win32con.WM_CHAR, 'q', 0)
win32gui.PostMessage(blue_stacks_app_handle, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
win32gui.PostMessage(blue_stacks_app_handle, win32con.WM_KEYUP, win32con.VK_F1, 0)
target_window_handle = win32gui.FindWindowEx(blue_stacks_app_handle, None, None, "_ctl.Window")
print("blue_stacks_app_handle: {}".format(target_window_handle))
print(win32gui.GetWindowText(target_window_handle))
win32gui.PostMessage(target_window_handle, win32con.WM_CHAR, 'q', 0)
win32gui.PostMessage(target_window_handle, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
win32gui.PostMessage(target_window_handle, win32con.WM_KEYUP, win32con.VK_F1, 0)
win32gui.EnumWindows(enum_handler, None)
I tried sending various types of input to all layers of this heirarchy, but seems those messages are not receieved.
When I tried to call
win32gui.MoveWindow(TargetWindowHandle, 0, 0, 760, 500, True)
just to make sure that window handles are the ones I'm looking for it worked fine. Calling this for the top-level window moved whole BlueStacks app. For other layers it is just caused window to look odd. So the handle values should be correct.
Example of output (executed from PyCharm)
>>> runfile('D:/Codes/DeffclanRose/BlueStackActions.py', wdir='D:/Codes/DeffclanRose')
invite_bot_handle: 67858
InviteBot
blue_stacks_app_handle: 198962
BlueStacks Android PluginAndroid_1
blue_stacks_app_handle: 133422
_ctl.Window
Edit: What I am looking for is a way to send input to an app, running in a background.
Bluestack game control is disabled whenever the window is inactive that's why inputs are not working inside the game. I have the same problem and I fixed it by using this WM_ACTIVATE before sending inputs: I found it here: https://stackoverflow.com/a/45496600/11915042
Here is my sample code:
import win32gui, win32api, win32con
import time
hwnd = win32gui.FindWindow(None, 'BlueStacks')
hwndChild = win32gui.GetWindow(hwnd, win32con.GW_CHILD)
hwndChild2 = win32gui.GetWindow(hwndChild, win32con.GW_CHILD)
win32gui.SendMessage(hwnd, win32con.WM_ACTIVATE, win32con.WA_CLICKACTIVE, 0)
time.sleep(1) # Without this delay, inputs are not executing in my case
win32api.PostMessage(hwndChild2, win32con.WM_KEYDOWN, win32con.VK_F1, 0)
time.sleep(.5)
win32api.PostMessage(hwndChild2, win32con.WM_KEYUP, win32con.VK_F1, 0)
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:
Source code
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
app = Application(backend='uia').start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
win = app['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.wait('ready')
win.menu_select("Setup->Refresh Serial and Ethernet")
win.top_window().print_control_identifiers(filename="file.txt")
# win.top_window().OKButton.click_input() ---------This is what I hope to do
else
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
Problem Statement
I had to run this application with elevation rights. The above is my code. The problem is I can't identify the window (view in output image) that pops up after selection from menu. I need to close the window. Please excuse the line
win.top_window().print_control_identifiers(filename="file.txt")
It was meant write the identifiers into a text file because the structure of this code does not display the outputs for me to view. However, since nothing is appended, I guess pywinauto couldn't identify the dialog.
For a clearer understanding, please view the image (input) of when it selects the menu.
Input
Now, it pops up with this dialog (output)
Output
I've also used spy to identify the caption and it gives:
(Handle: 004E07D4,
Caption: Information,
Class: #32770(Dialog),
Style: 94C801C5)
Other things I've tried:
Besides using win.topwindow() to identify the dialog, I've used
win[Information].OKButton.click_input()
win[Information].OK.click_input()
win[Information].OK.close()
win[Information].OK.kill(soft=false)
win.Information.OKButton.click_input()
win.Information.OK.click_input()
win.Information.OK.close()
win.Information.OK.kill(soft=false)
app[Information] ...... curious if I could discover the new window from original application
I've also send keys like enter, space, esc & alt-f4 to close the dialog with libraries like keyboard, pynput & ctypes. It still doesn't work.
Link to download the same application: http://downloadt.advantech.com/download/downloadsr.aspx?File_Id=1-1NHAMZX
Any help would be greatly appreciated !
I finally found a thread that demonstrated the way multi thread works to solve this issue. I tried it myself and it works. It's a little different as a few parts of the code have depreciated. Here is the link to the solution:
How to stop a warning dialog from halting execution of a Python program that's controlling it?
Here are the edits I made to solve the problem:
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
def __init__(self, window_name, quit_event):
threading.Thread.__init__(self)
self.quit_event = quit_event
self.window_name = window_name
def run(self):
while True:
try:
handles = windows.find_windows(title=self.window_name)
except windows.WindowNotFoundError:
pass
else:
for hwnd in handles:
app = Application()
app.connect(handle=hwnd)
popup = app[self.window_name]
popup.close()
if self.quit_event.is_set():
break
time.sleep(1)
quit_event = threading.Event()
mythread = ClearPopupThread('Information', quit_event)
mythread.start()
application = Application(backend="uia").start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
time.sleep(2)
win = application['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.menu_select("Setup->Refresh Serial and Ethernet")
quit_event.set()
else:
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
The best thing is this solution works for every other dialog that halts the main script from working & I could use them to do different actions like clicking buttons, inserting values, by adding more multi threads.
I'm trying to automate an application (let's call it app.exe) by sending keystrokes to it. When its main window is open and has focus, doing ALT+F opens the File menu. I have inspected all the messages posted to the window with Spy++, and I have carefully studied all the parameters.
Then I tried to replicate exactly the same behaviour with PostMessage (to be sure I'm not sending to the wrong window, I'm even looping on all hWnd associated to the relevant process ID):
import win32con, win32gui, win32process, win32api, subprocess, time
def get_hwnds_for_pid(pid):
def callback (hwnd, hwnds):
if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
_, found_pid = win32process.GetWindowThreadProcessId(hwnd)
if found_pid == pid:
hwnds.append(hwnd)
return True
hwnds = []
win32gui.EnumWindows(callback, hwnds)
return hwnds
app = subprocess.Popen(["notepad.exe"])
time.sleep(2.0)
for hwnd in get_hwnds_for_pid(app.pid):
win32gui.SetForegroundWindow(hwnd)
win32api.PostMessage(hwnd, win32con.WM_SYSKEYDOWN, 0x12, 0x20380001) # ALT
win32api.PostMessage(hwnd, win32con.WM_SYSKEYDOWN, 0x46, 0x20210001) # F
time.sleep(1.0)
win32api.PostMessage(hwnd, win32con.WM_KEYUP, 0x46, 0xC0210001)
win32api.PostMessage(hwnd, win32con.WM_KEYUP, 0x12, 0x20380001)
Problem: this works when the app is notepad.exe. But it fails with another particular application I'm using (a regular Win32 application, which uses QWidget for the main window).
To be more precise, I see the _ underline flashing under the first letter of the menu during one second (so the simulated ALT is recognized ; the usual behaviour in Windows application is that the underlined F appears when you hold the ALT button down) but the F is not recognized.
Once again, I have sent with PostMessage exactly the same messages to the window as what I have observed with Spy++ when I did the hotkey manually with the real keyboard.
Question: what could prevent a given software app.exe to receive messages posted/injected from another application (=here, my Python script)?
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).