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)
Related
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'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)?
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.
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.
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).