V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
XIVN1987
V2EX  ›  Python

subprocess.Popen 能不能模拟点击应用窗口右上角的叉号关闭应用程序?

  •  
  •   XIVN1987 · 2023-11-07 09:25:01 +08:00 · 1329 次点击
    这是一个创建于 382 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的需求和实现代码如下:

    ''' 脚本修改 uvproj 和 uvopt 会导致文件格式变化,执行此函数打开工程再关闭,可恢复原本格式 '''
    def open_close_uvproj(path):
        print(f'\n{path}')
        for file in os.listdir(path):
            if file.endswith('.uvproj') or file.endswith('.uvprojx'):
                proc = subprocess.Popen(fr'D:\Program\Keil\UV4\UV4.exe {os.path.join(path, file)}')
                time.sleep(10)
                proc.terminate()
    

    现在能够打开指定的 Keil 工程,,也能在延时 10s 后自动关闭工程,,但工程文件没有恢复格式。。

    猜测是因为只有通过点 Keil 右上角的叉号关闭,,Keil 才能写工程文件,,用 terminate() 和 kill() 杀死进程时,,Keil 不会保存工程文件

    请问 Popen 有什么方法可以实现模拟点应用窗口右上角叉号关闭应用的方法吗??

    我知道 Popen 有个 send_signal() 方法,,是不是通过该方法发送个特定信号就行了??请大神指点,,谢谢。。

    11 条回复    2023-11-07 11:41:21 +08:00
    NessajCN
        1
    NessajCN  
       2023-11-07 09:36:45 +08:00
    你应该直接问怎么修改 uvproj 和 uvopt 才能保证文件格式不变,而不是自己想个这么奇葩的方法来问怎么实现
    建议直接把你修改文件的脚本贴上来
    clemente
        2
    clemente  
       2023-11-07 09:49:09 +08:00
    获取进程号 subprocess.Popen 然后强制 kill
    XIVN1987
        3
    XIVN1987  
    OP
       2023-11-07 09:54:39 +08:00
    @NessajCN

    uvproj 和 uvopt 文件都是 XML 格式的。。python 似乎没有库能够修改 xml 文件同时保持格式完全不变。。

    比如下面这个提问,,十多年了也没有简单的解决方案。。

    https://stackoverflow.com/questions/6539891/python-library-for-editing-xml-preserving-formatting-and-comments
    XIVN1987
        4
    XIVN1987  
    OP
       2023-11-07 09:59:16 +08:00
    之所以想要让 keil 恢复格式,,是因为每次修改 uvproj 和 uvopt 后提交代码,,都会显示整个文件都完全改变了,,但实际上我只修改了其中几个字符而已。。
    ysc3839
        5
    ysc3839  
       2023-11-07 10:01:00 +08:00 via Android
    FindWindow 找到对应窗口,然后发送 WM_CLOSE 消息
    henix
        6
    henix  
       2023-11-07 10:19:39 +08:00   ❤️ 2
    可能最好的办法是用 Python 调用 Keil 的命令行工具,实现你要的操作。因为 subprocess.Popen 主要是针对命令行程序。但我对 Keil 不了解,不知道能否纯用命令行实现。

    如果你要自动化地打开一个 GUI 程序再关闭,可以在 Win32 的层面使用 Win32 API 来自动化。

    大致的原理是:

    1. 使用 FindWindow 或其他方法得到窗口句柄
    2. 向该窗口发送 WM_CLOSE 消息

    参考:

    https://learn.microsoft.com/zh-cn/windows/win32/learnwin32/closing-the-window
    https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-findwindowa
    https://stackoverflow.com/questions/5402158/using-sendmessage-to-send-wm-close-to-another-process

    但是以上是 C++ 层面的 API ,如果用 Python 又该怎么做呢?可以使用 pywinauto 。代码大概如下:

    ```py
    import time
    from pywinauto.application import Application

    app = Application().start("???.exe")
    time.sleep(10)
    app.kill(soft=True)
    ```

    另一种方法是找到顶层窗口之后 close:

    ```py
    import time
    from pywinauto.application import Application
    from pywinauto.controls.hwndwrapper import HwndWrapper

    app = Application().start("???.exe")
    time.sleep(10)
    root: HwndWrapper = app.top_window()
    root.set_focus()
    root.close()
    ```
    NessajCN
        7
    NessajCN  
       2023-11-07 10:23:13 +08:00
    @XIVN1987 你这需求甚至不需要用到任何库呀,简单的一个 re 正则替换不就完了
    with open("/path/to/uvproj", "r") as fp:
    lines = fp.readlines()
    with open("/path/to/uvproj"", "w") as fp:
    for line in lines:
    fp.write(re.sub(r'<regexp>', '<string>', line))
    XIVN1987
        8
    XIVN1987  
    OP
       2023-11-07 10:38:15 +08:00
    @NessajCN

    用正则表达式修改 XML 文件??我感觉这不是好办法。。

    XML 是结构化文件,,相同的字符在不同的位置有不同的含义,,不是简单正则能处理的,,
    NessajCN
        9
    NessajCN  
       2023-11-07 10:43:03 +08:00
    @XIVN1987 文件就是文件,只要不是二进制的你这么替换肯定没问题,你正则替换别去动其他字符,文件结构怎么会变?你用这个方法改了试试,不好用你找我
    geelaw
        10
    geelaw  
       2023-11-07 11:07:14 +08:00 via iPhone
    抽象层级错误的原因,点关闭按钮是图形用户界面的概念(更具体来说是 user32 ),结束进程是进程( kernel32 )的概念,进程不一定创建窗口,自然不可能期待操作进程的代码能够处理用户界面。

    正确的方法,根据 .NET 里的 Process.CloseMainWindow:

    1. 获得目标窗口的句柄
    2. 判断目标窗口是否是禁用状态( GetWindowLong ),如果是,则不可关闭
    3. 否则,用 PostMessage (不等待回复)或者 SendMessage (等待回复)发送 WM_CLOSE 到目标窗口

    但我个人的意见是不需要第二步,因为它不能保证第三步操作的时候窗口依然处于非禁用状态。另外第三步,如果目标窗口是对话框则无效,此时正确的操作是 WM_COMMAND ;第三步也可以考虑 WM_SYSCOMMAND 和 SC_CLOSE 。

    最后,如果目标程序提供 COM (例如 Office 系列),则应该优先采用 COM 操作。

    P.S. 如果有人搜索“禁用窗口”并看到了这条回复,有必要提示改变窗口禁用/启用状态不可以用 SetWindowLong ,而是要用 EnableWindow 。
    XIVN1987
        11
    XIVN1987  
    OP
       2023-11-07 11:41:21 +08:00
    @henix

    感谢,,用 pywinauto 成功了。。

    看来是我一开始找错了库啊。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   914 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 21:50 · PVG 05:50 · LAX 13:50 · JFK 16:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.