将kivy与pywinauto

时间:2019-04-30 20:47:59

标签: python kivy ctypes pywinauto

我有一个kivy应用程序,可以使用pywinauto模块与其他窗口进行交互。该应用程序在Linux(未使用pywinauto)上运行良好,但是在Windows中,我收到以下错误消息,该应用程序甚至无法启动:

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

我认为这是pywinauto的问题,原因是我有以下几行,并且在Linux上运行良好:

if SYSTEM == "Windows":
    import win32gui
    import win32process
    import wmi

    from pywinauto import application
    import pywinauto

我也注释掉了pywinauto导入行,它开始了。它可能链接到this issue.,但我真的不知道要包含什么代码,因为它可以在其他操作系统上工作。...我假设pywinauto正在更改使kivy无法正常工作的内容。

我的问题是:如何在同一应用程序中同时具备kivy和pywinauto的功能?

1 个答案:

答案 0 :(得分:5)

我能够使用以下方式重现该行为:

  • Python 3.7.3 x64
  • Kivy 1.10.1
  • Pywinauto 0.6.6

请注意,之前我没有使用过这两个软件包中的任何一个,我pip install专门针对此任务将它们打包。

由于我不知道如何重现此行为,因此我只复制了[GitHub]: pywinauto/pywinauto - ctypes.ArgumentError @ click_input中的 MCVE (您也在问题中也分享了此内容),并对其进行了少许修改(仅可点击错误,没有样式,没有改进,等等。

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)


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)


Demo().run()

输出

[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

在进一步介绍之前,我想指出:

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

从后者中可以看到, SetWindowLongPtrW 的3 rd 参数可以是 DWORD HANDLE ,一个函数指针,取决于2 nd 参数值:基本上是一个void*,可以映射到任何东西。
两个模块都通过 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
    

说明

  • user32.dll 在当前的 Python 进程中仅加载一次
  • 上述代码初始化功能,通常在模块导入时仅执行一次(可以根据需要执行多次,但会降低性能)
  • 两个模块都指定了函数原型(与我们在 64bit 上一样windll.user32.SetWindowLongPtrW),但它们分别不同地
  • 从上面的3中,结果最后导入的模块决定了函数原型的外观
  • 导入的模块1 st 尝试使用原型时,它与传递的参数不匹配,因此出现错误

这就是为什么我必须在 Kivy 之后移动 Pywinauto 导入,以便 Kivy 尝试使用 Pywinauto 调用函数的原因em>的原型,否则就可以了。
是 可以进行其他操作,但是我没有费心找到 Pywinauto 会调用该函数的情况,因为它不相关。

看看2个 ctypes 原型和一个 C 原型(来自 MS URL ),发现

  • Pywinauto 做得正确(不过,我很好奇它在 32bit 上如何工作)
  • Kivy 仅使用一个 SetWindowLongPtrW 用例(带有函数指针的用例),并且为了使事情变得更简单,他们针对其情况调整了原型。但是,它与 C 原型不太匹配,而根据我的 PoV 来看,这似乎是一种me脚的解决方法( gainarie < / em>)

我修改了我的 Kivy 安装和 tadaa ! (这是复活节兔子!:)):

Working

注意:此处遇到一个(更简单的)变体:[SO]: How to keep pynput and ctypes from clashing?

@ EDIT0

我已提交 [GitHub]: kivy/kivy - SetWindowLongPtrW ctypes prototype bug ,该文件已已合并。不过,不确定何时将在市场上出售。

作为替代,您可以下载补丁,然后在本地应用更改。检查[SO]: Run/Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition? (@CristiFati's answer)修补 utrunner 部分),了解如何在 Win 上应用补丁(基本上,每行以开头)一个“ +” 符号进入,以一个“-” 符号开头的每一行都熄灭)。我正在使用 Cygwin btw
或者您可以下载3个修改后的文件并覆盖现有文件。
无论如何,先备份!另外,我不知道这些更改如何适应旧的 Kivy 版本。