Pywinauto: hwndwrapper.py::HwndWrapper.set_focus() fails when used via interpreter

Created on 19 Nov 2016  路  14Comments  路  Source: pywinauto/pywinauto

Environment: Python 3.5.2, pywinauto 0.6.0, Windows 10 (build 14393)

This code works fine when in a *.py file, whether I run it on its own or through IDLE:

from pywinauto import Application
app = Application().start("notepad")
app.notepad.edit.type_keys("example")

It fails if I run it within the interpreter, but it fails differently depending on whether I launch python.exe directly or load it into cmd.exe.

[python.exe]

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\base_wrapper.py", line 811, in type_keys
    self.set_focus()
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 1208, in set_focus
    win32gui.SetForegroundWindow(self.handle)
pywintypes.error: (0, 'SetForegroundWindow', 'No error message is available')

[cmd.exe > python.exe]

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\base_wrapper.py", line 811, in type_keys
    self.set_focus()
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 1206, in set_focus
    1)
pywintypes.error: (87, 'AttachThreadInput', 'The parameter is incorrect.')

Similarly, this works in a script:

from pywinauto import Application
app = Application().start("notepad")
app.notepad.menu_select("help->about notepad")

In this case, it actually works in python.exe directly, but not through cmd.exe:

[cmd.exe > python.exe]

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 955, in menu_select
    self.menu_item(path, exact=exact).select()
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\menuwrapper.py", line 312, in select
    self.ctrl.set_focus()
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 1206, in set_focus
    1)
pywintypes.error: (87, 'AttachThreadInput', 'The parameter is incorrect.')

The AttachThreadInput failures may seem similar to #240, but in this case neither cur_fore_thread nor control_thread is getting set to 0. They match when run via a script, but they're different values via the interpreter, so set_focus() enters the if cur_fore_thread != control_thread block and then fails.

Interestingly, the failing scenarios work fine if I switch to another unrelated window before the exceptioning call happens. For example, time.sleep(3); app.notepad.edit.type_keys("example") and then I change windows during the sleep. cur_fore_thread and control_thread still don't match in this case, but the call to AttachThreadInput() succeeds.

bug

Most helpful comment

@mtkennerly, indeed, you made a very thorough problem examination. Just want to remind about SetForegroundWindow (https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539(v=vs.85).aspx)

A process can set the foreground window only if one of the following conditions is true:

  • The process is the foreground process.
  • The process was started by the foreground process.
  • The process received the last input event.
  • There is no foreground process.
  • The process is being debugged.
  • The foreground process is not a Modern Application or the Start Screen.
  • The foreground is not locked (see LockSetForegroundWindow).
  • The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
  • No menus are active.

If none of above conditions is true then we probably get the error 0 and, apparently, this is the reason for getting the different behavior when running the script from a cmd interpreter or directly.

All 14 comments

Hi @mtkennerly, thank you for such a detailed report!
pywintypes.error: (0, 'SetForegroundWindow', 'No error message is available') looks very confusing to me. It might be a bug of pyWin32, but I'm in doubt. Will take a look at this after getting back from SQA Days conference held in Minsk next week.

OK, I could quickly reproduce it on Python 3.5 32-bit. This workaround could be used for the first problem so far:

app.notepad.set_focus(); app.notepad.edit.type_keys("example")

I've fixed the first problem on my side, now working on tests.
I just couldn't reproduce it for app.notepad.menu_select("help->about notepad"). It works for me even if the notepad window is minimized.

Thanks, @vasily-v-ryabov. I just tried and can confirm that the workaround you mentioned works in python.exe directly (avoids error 0), although it does not work on my system when going through cmd.exe (still gets error 87).

I found that, at least on my system, all of these scenarios work fine if I strip out all the if cur_fore_thread != control_thread stuff and have it skip to win32gui.SetForegroundWindow(self.handle) (line 1231). That even works when I switch to an unrelated window before it executes. I was hesitant to make a PR for that, though, since I imagine the handling is probably needed for other Python/OS versions. I could experiment with these changes in Python 2.7, but are you aware of any particular OS version dependencies to keep in mind (or whatever situation requires the AttachThreadInput handling)?

I think removing AttachThreadInput calls may hide another problems (I remember some cases where it's useful, but forgot the details for now). We need to investigate it deeper.

Well. I've reproduced error 87 from cmd.exe too. Very interesting.

Just tried everything with Python 2.7.12, and all behavior is the same.

I've also just found that, in python.exe directly, if I try app.notepad.edit.type_keys("example") and let it fail on error 0, then your workaround will no longer work, not even if I instantiate a new Application. I have to exit and reload Python, and then your workaround works again.

[python.exe]

from pywinauto import Application
app = Application().start("notepad")
app.notepad.set_focus(); app.notepad.edit.type_keys("example")  # works fine
app.notepad.edit.type_keys("example")  # error 0
app.notepad.set_focus(); app.notepad.edit.type_keys("example")  # erorr 0

Their tracebacks are different, though; look at the line number in set_focus. Without workaround, cur_fore_thread != control_thread, but with your workaround they match. Either way, though, they're still failing on a call to SetForegroundWindow.

>>> app.notepad.edit.type_keys("example")  # error 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\base_wrapper.py", line 811, in type_keys
    self.set_focus()
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 1208, in set_focus
    win32gui.SetForegroundWindow(self.handle)
pywintypes.error: (0, 'SetForegroundWindow', 'No error message is available')
>>> app.notepad.set_focus(); app.notepad.edit.type_keys("example")  # erorr 0
<pywinauto.controls.win32_controls.DialogWrapper object at 0x0201A550>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\base_wrapper.py", line 811, in type_keys
    self.set_focus()
  File "C:\Python\Python35-32\lib\site-packages\pywinauto\controls\hwndwrapper.py", line 1231, in set_focus
    win32gui.SetForegroundWindow(self.handle)
pywintypes.error: (0, 'SetForegroundWindow', 'No error message is available')

@mtkennerly, indeed, you made a very thorough problem examination. Just want to remind about SetForegroundWindow (https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539(v=vs.85).aspx)

A process can set the foreground window only if one of the following conditions is true:

  • The process is the foreground process.
  • The process was started by the foreground process.
  • The process received the last input event.
  • There is no foreground process.
  • The process is being debugged.
  • The foreground process is not a Modern Application or the Start Screen.
  • The foreground is not locked (see LockSetForegroundWindow).
  • The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
  • No menus are active.

If none of above conditions is true then we probably get the error 0 and, apparently, this is the reason for getting the different behavior when running the script from a cmd interpreter or directly.

I also noticed that calling mouse.move(coords=(10000, 20000)) maybe isn't a good idea because moving the mouse pointer into the lower-right corner of the screen can produce a side effect. For example, trigger displaying an overlay Charms bar in Win8 or it can be resulted in another process received the last input event thus effectively breaking one of the conditions I mentioned above.

Just one more remark. cmd.exe is very specific application. It doesn't have its own message loop which every GUI application has. Say if you want to run cmd.exe under pywinauto you have to call app.start('cmd.exe', wait_for_idle=False). The root cause of these issues might be the fact that cmd.exe main thread has no its own keyboard input handler and therefore AttachThreadInput doesn't make sense in such case. For example, I usually use PythonWin IDE and these problems are not actual for me. We need to think about automating such test cases so that we don't miss them in the future.

I've found a Stack Overflow question about changing active windows with some relevant information. The second answer suggests AttachThreadInput, but one of the responses mentions that it indeed won't work for cmd.exe for exactly the reason you mentioned. The top answer suggests calling ShowWindow before SetForegroundWindow, and this seems to be working for me with a quick edit to set_focus() - my failing code now works in both cmd.exe and python.exe directly.

@mtkennerly, can you fix the links to the StackOverflow question that you mention in the comment above?

Just did. That was bizarre; I could have sworn I pasted the right links before.

I struggled with this issue as well, using the win32gui library. I would get the same error message: (0, 'SetForegroundWindow', 'No error message is available') My program involved running 2 processes to send an email via the Outlook desktop application. One would trigger the sending of the Outlook email and the other dealt with the popup dialogs. Using time.sleep solved my issues with set_foreground() and shell.SendKeys(). Some actions may appear to be happening in a certain order and at a given speed, however my assumptions on expected behavior didn't translate to an automated process. Once I added time.sleep prior to each command, the execution of the program worked in Visual Studio Code and Jupyter Notebook. For reference, I used 3 seconds for most commands, except for the Outlook security command which took longer to load (~10 secs). You can always reduce or remove the delays once you've narrowed down your issue. Even though this discussion is referencing a different library, below is the template I used as a base for controlling the windows gui: Python Window Activation | luc's answer.

Edit: Additional Reference -
Windows 7: how to bring a window to the front no matter what other window has focus?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nixgnef picture nixgnef  路  15Comments

rajarameshmamidi picture rajarameshmamidi  路  14Comments

Enteleform picture Enteleform  路  19Comments

nimuston picture nimuston  路  56Comments

jjbright picture jjbright  路  15Comments