I have a kivy application that can interact with other windows using the pywinauto module. The application works fine in Linux (where pywinauto isn't used) but in Windows I get the following error, the application won't even start up:
C:\Program Files (x86)\Python36_64\lib\site-packages\pywinauto\__init__.py:80: UserWarning: Revert to STA COM threading mode
warnings.warn("Revert to STA COM threading mode", UserWarning)
[INFO ] [GL ] NPOT texture support is available
[INFO ] [Base ] Start application main loop
Traceback (most recent call last):
File ".\application.py", line 368, in <module>
Application().run()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\app.py", line 826, in run
runTouchApp()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\base.py", line 477, in runTouchApp
EventLoop.start()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\base.py", line 164, in start
provider.start()
File "C:\Program Files (x86)\Python36_64\lib\site-packages\kivy\input\providers\wm_touch.py", line 68, in start
self.hwnd, GWL_WNDPROC, self.new_windProc)
ctypes.ArgumentError: argument 3: <class 'TypeError'>: wrong type
The reason I think it's an issue with pywinauto, is that I have the following lines and it works fine in Linux:
if SYSTEM == "Windows":
import win32gui
import win32process
import wmi
from pywinauto import application
import pywinauto
Also I comment out the pywinauto import lines and it starts. It could be linked to this issue. I don't really know what code to include, as it works in other operating systems.... I'm assuming pywinauto is changing something that stops kivy from working.
My question is this: how can I have both the functionality of kivy and pywinauto in the same application?
I was able to reproduce the behavior using:
Python 3.7.3 pc064
Kivy 1.10.1
PyWinAuto 0.6.6
As a side note, I didn't work with any of the 2 packages before, I pip install
ed them specifically for this task.
Since I had no idea how to reproduce the behavior, I just copied the MCVE from [GitHub]: pywinauto/pywinauto - ctypes.ArgumentError @ click_input (that you also shared in the question), and slightly modified it (only to hit the error, with little style improvements).
code00.py:
#!/usr/bin/env python
import random
import sys
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
import pywinauto # @TODO - cfati: moved AFTER Kivy import(s), as it works otherwise (https://github.com/pywinauto/pywinauto/issues/419#issuecomment-488258224)
class DemoLayout(BoxLayout):
pass
Builder.load_string("""
#: import datetime datetime.datetime
<DemoLayout>:
padding: 75
Button:
on_press: print(f"PRESSED @ {datetime.now()}")
""")
class Demo(App):
def build(self):
self.root = DemoLayout()
def on_start(self):
title = f"__KIVY_APP__{random.getrandbits(128)}"
Window.set_title(title)
hwnd = pywinauto.findwindows.find_window(title=title)
app = pywinauto.Application()
app.connect(handle=hwnd)
window = app.window(handle=hwnd).wrapper_object()
window.click_input(button="left", pressed="", coords=(100, 100), double=False, absolute=False)
def main(*argv):
Demo().run()
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Output:
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q055928463]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" ./code00.py [INFO ] [Logger ] Record log in C:\Users\cfati\.kivy\logs\kivy_19-05-01_83.txt [INFO ] [Kivy ] v1.10.1 [INFO ] [Python ] v3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] [INFO ] [Factory ] 194 symbols loaded [INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored) [INFO ] [Window ] Provider: sdl2 [INFO ] [GL ] Using the "OpenGL" graphics system [INFO ] [GL ] GLEW initialization succeeded [INFO ] [GL ] Backend used <glew> [INFO ] [GL ] OpenGL version <b'4.5.0 - Build 23.20.16.4973'> [INFO ] [GL ] OpenGL vendor <b'Intel'> [INFO ] [GL ] OpenGL renderer <b'Intel(R) HD Graphics 530'> [INFO ] [GL ] OpenGL parsed version: 4, 5 [INFO ] [GL ] Shading version <b'4.50 - Build 23.20.16.4973'> [INFO ] [GL ] Texture max size <16384> [INFO ] [GL ] Texture max units <32> [INFO ] [Window ] auto add sdl2 input provider [INFO ] [Window ] virtual keyboard not allowed, single mode, not docked e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\pywinauto\__init__.py:80: UserWarning: Revert to STA COM threading mode warnings.warn("Revert to STA COM threading mode", UserWarning) Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] 064bit on win32 [INFO ] [Text ] Provider: sdl2 [INFO ] [Base ] Start application main loop Traceback (most recent call last): File "code00.py", line 50, in <module> rc = main(*sys.argv[1:]) File "code00.py", line 44, in main Demo().run() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\app.py", line 826, in run runTouchApp() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\base.py", line 477, in runTouchApp EventLoop.start() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\base.py", line 164, in start provider.start() File "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\lib\site-packages\kivy\input\providers\wm_touch.py", line 68, in start self.hwnd, GWL_WNDPROC, self.new_windProc) ctypes.ArgumentError: argument 3: <class 'TypeError'>: wrong type
Before going further, I want to point out:
As seen from the latter, SetWindowLongPtrW's 3rd argument can be a DWORD, a HANDLE, a function pointer, depending on the 2nd argument value: basically it's a void* that can be mapped to anything.
Both modules call this function via CTypes:
PyWinAuto ([GitHub]: pywinauto/pywinauto - (0.6.6) pywinauto/pywinauto/win32functions.py):
try:
SetWindowLongPtr = ctypes.windll.user32.SetWindowLongPtrW
SetWindowLongPtr.argtypes = [win32structures.HWND, ctypes.c_int, win32structures.LONG_PTR]
SetWindowLongPtr.restype = win32structures.LONG_PTR
except AttributeError:
SetWindowLongPtr = SetWindowLong
Kivy ([GitHub]: kivy/kivy - (1.10.1) kivy/kivy/input/providers/wm_common.py):
try:
windll.user32.SetWindowLongPtrW.restype = WNDPROC
windll.user32.SetWindowLongPtrW.argtypes = [HANDLE, c_int, WNDPROC]
SetWindowLong_wrapper = windll.user32.SetWindowLongPtrW
except AttributeError:
windll.user32.SetWindowLongW.restype = WNDPROC
windll.user32.SetWindowLongW.argtypes = [HANDLE, c_int, WNDPROC]
SetWindowLong_wrapper = windll.user32.SetWindowLongW
Explanation:
User32.dll is only loaded once in the current Python process
Code as above initializes the functions and typically is only executed once, at module import time (it can be executed as many times as desired, but it would decrease performance)
Both modules specify the function (windll.user32.SetWindowLongPtrW as we are on 064bit) prototype, but they do it differently
From the 3 above, results that the module that gets imported last, decides how the function prototype will look like
When the module that was imported 1st tries to use the prototype, it doesn't match the arguments passed, hence the error
That's why I had to move PyWinAuto import after Kivy, so that Kivy tried to call the function using PyWinAuto's prototype, otherwise it would have worked.
It's possible to go the other way around, but I didn't bother to find a scenario where PyWinAuto would call the function, as it's not relevant.
Looking at the 2 CTypes prototypes and the C one (from MS URL), it turns out that:
PyWinAuto is doing things correctly (I'm curious how it would work on 032bit, though)
Kivy only uses a SetWindowLongPtrW use-case (the one with the function pointer), and to make things easier, they adapted the prototype for their scenario. However, it doesn't quite match the C prototype, and this from my PoV, looks like a lame workaround (gainarie)
I modified my Kivy installation, and tadaa! (it's the Easter Bunny! :) ):
Check:
[SO]: How to keep pynput and ctypes from clashing? for a simpler variant
[SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a deeper and more generic analysis of this common (CTypes related) pitfall
I've submitted [GitHub]: kivy/kivy - SetWindowLongPtrW ctypes prototype bug, which was merged on 190502. Not sure when it will be available on the market (PyPI, so you can simply pip install
it), though.
As an alternative, you could download the patch, and apply the changes locally. Check [SO]: Run / Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (@CristiFati's answer) (Patching UTRunner section) for how to apply patches on Win (basically, every line that starts with one "+" sign goes in, and every line that starts with one "-" sign goes out).
Or you could download the 3 modified files and overwrite your existing ones.
In any case, back them up first! Also, I don't know how the changes fit into older Kivy versions.