Skip to content Skip to sidebar Skip to footer

Ctypes.argumenterror When Using Kivy With Pywinauto

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

Solution 1:

I was able to reproduce the behavior using:

  • Python 3.7.3 x64
  • 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 installed 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, no style, improvements, ... and so on).

code.py:

import random
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)classDemoLayout(BoxLayout): pass
Builder.load_string("""
#: import datetime  datetime.datetime
<DemoLayout>:
  padding: 75

  Button:
    on_press: print(f"PRESSED @ {datetime.now()}")
""")


classDemo(App):

  defbuild(self):
    self.root = DemoLayout()

  defon_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)


Demo().run()

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q055928463]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" code.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)
[INFO   ] [Text        ] Provider: sdl2
[INFO   ] [Base        ] Start application main loop
 Traceback (most recent call last):
   File "code.py", line 36, in <module>
     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:

  1. [Python 3.Docs]: ctypes - A foreign function library for Python
  2. [MS.Docs]: SetWindowLongPtrW function

As seen from the latter, SetWindowLongPtrW's 3 argument can be a DWORD, a HANDLE, a function pointer, depending on the 2 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 64bit) 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 1 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 MSURL), it turns out that:

  • Pywinauto is doing things correctly (I'm curious how it would work on 32bit, 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! :) ):

Working

Note: A (simpler) variant encountered here: [SO]: How to keep pynput and ctypes from clashing?


Update #0

I've submitted [GitHub]: kivy/kivy - SetWindowLongPtrW ctypes prototype bug, which was merged. 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). I am using Cygwin, btw. 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.

Post a Comment for "Ctypes.argumenterror When Using Kivy With Pywinauto"