4

This question is probably quite basic but I'm having difficulty cracking it. I assume that I will have to use something in ctypes.windll.user32. Bear in mind that I have little to no experience using these libraries or even ctypes as a whole.

I have used this code to list all the window titles, but I have no idea how I am supposed to change this code to get a window title with a process name:

import ctypes

EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible

titles = []
def foreach_window(hwnd, lParam):
    if IsWindowVisible(hwnd):
        length = GetWindowTextLength(hwnd)
        buff = ctypes.create_unicode_buffer(length + 1)
        GetWindowText(hwnd, buff, length + 1)
        titles.append(buff.value)
    return True
EnumWindows(EnumWindowsProc(foreach_window), 0)

print(titles)

This code is from https://sjohannes.wordpress.com/2012/03/23/win32-python-getting-all-window-titles/

If my question is unclear, I would like to achieve something like this (just an example - I'm not asking specifically about Spotify):

getTitleOfWindowbyProcessName("spotify.exe") // returns "Avicii - Waiting For Love" (or whatever the title is)

A complication that may arise, if there are multiple windows running with the same process name (e.g. multiple chrome windows)

Thank you.


EDIT: To clarify, I want some code that takes a process name and returns a (possibly empty) list of window titles owned by that process as strings.

Anish Gupta
  • 2,218
  • 2
  • 23
  • 37
  • The complication which you note should be a clear indication that getting a window title from a process name isn't possible. – TigerhawkT3 Jul 07 '15 at 20:29
  • @TigerhawkT3 Surely I could get at an array at least? – Anish Gupta Jul 07 '15 at 20:32
  • That's more realistic, but not indicated in your question. Also, what if the process doesn't have a window? – TigerhawkT3 Jul 07 '15 at 20:34
  • @TigerhawkT3 `IsWindowVisible` makes sure it has a window. I am only going to be using this for specific ones that I know for sure that there will be a window so it is not a problem anyway. – Anish Gupta Jul 07 '15 at 20:35
  • So, basically what you want is a piece of code that takes as an input a string (process name) and outputs a list(may be empty) of strings (window titles owned by that process). – CristiFati Jul 07 '15 at 20:54
  • @CristiFati Exactly. I'll update my question – Anish Gupta Jul 07 '15 at 20:55
  • Note: I am a big fan of `ctypes` but when it comes to calling funcs that take complex structures as args, i'd recommend [pywin32](https://sourceforge.net/projects/pywin32), it's a nice tool that wraps most of the WinAPIs in pythonic form... – CristiFati Jul 07 '15 at 21:01
  • @CristiFati Ehhh I was trying to avoid using any external libraries, but I will try it if it makes it simpler to a decent degree. Could you possibly expand on how I could use it? – Anish Gupta Jul 07 '15 at 21:04
  • It's hard to write this in a comment but i'll try (this is the equivalent code for enumerating the titles; i will use __\n__ literal as a line separator): `import win32gui\ndef enumWindowsProc(hwnd, lParam):\n print win32gui.GetWindowText(hwnd)\nwin32gui.EnumWindows(enumWindowsProc, 0)`. I recommended this module to make it simpler for the work that will come. – CristiFati Jul 07 '15 at 21:21
  • what's the python version that you are using? – CristiFati Jul 07 '15 at 22:03
  • @CristiFati I'm using 3.4. What does the code do? Apologies, these names are a bit cryptic to me. – Anish Gupta Jul 07 '15 at 22:31

1 Answers1

7

Before everything, I want to point out [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer). Read it before working with CTypes.

Here's what I meant in the comment:

import win32gui


def enumWindowsProc(hwnd, lParam):
    print win32gui.GetWindowText(hwnd)


win32gui.EnumWindows(enumWindowsProc, 0)

Below, I pasted the whole thing...it doesn't work on the PC that I am at right now, since I messed up with security settings (it's an XP!!!) and I get a bunch of Access denied (error code: 5) errors, but here it is.

code00.py:

#!/usr/bin/env python

import sys
import os
import traceback
import ctypes as ct
from ctypes import wintypes as wt
import win32con as wcon
import win32api as wapi
import win32gui as wgui
import win32process as wproc


def enumWindowsProc(hwnd, lParam):
    if (lParam is None) or ((lParam is not None) and (wproc.GetWindowThreadProcessId(hwnd)[1] == lParam)):
        text = wgui.GetWindowText(hwnd)
        if text:
            wStyle = wapi.GetWindowLong(hwnd, wcon.GWL_STYLE)
            if wStyle & wcon.WS_VISIBLE:
                print("%08X - %s" % (hwnd, text))


def enumProcWnds(pid=None):
    wgui.EnumWindows(enumWindowsProc, pid)


def enumProcs(procName=None):
    pids = wproc.EnumProcesses()
    if procName is not None:
        bufLen = 0x100

        _OpenProcess = ct.windll.kernel32.OpenProcess
        _OpenProcess.argtypes = (wt.DWORD, wt.BOOL, wt.DWORD)
        _OpenProcess.restype = wt.HANDLE

        _GetProcessImageFileName = ct.windll.psapi.GetProcessImageFileNameA
        _GetProcessImageFileName.argtypes = (wt.HANDLE, wt.LPSTR, wt.DWORD)
        _GetProcessImageFileName.restype = wt.DWORD

        _CloseHandle = ct.windll.kernel32.CloseHandle
        _CloseHandle.argtypes = (wt.HANDLE,)
        _CloseHandle.restype = wt.BOOL

        filteredPids = ()
        for pid in pids:
            try:
                hProc = _OpenProcess(wcon.PROCESS_ALL_ACCESS, 0, pid)
            except:
                print("Process [%d] couldn't be opened: %s" % (pid, traceback.format_exc()))
                continue
            try:
                buf = ct.create_string_buffer(bufLen)
                _GetProcessImageFileName(hProc, buf, bufLen)
                if buf.value:
                    name = buf.value.decode().split(os.path.sep)[-1]
                    #print("proc name:", name)
                    if name.lower() == procName.lower():
                        filteredPids += (pid,)
                else:
                    _CloseHandle(hProc)
                    continue
            except:
                print("Error getting process name: %s" % traceback.format_exc())
                _CloseHandle(hProc)
                continue
            _CloseHandle(hProc)
        return filteredPids
    else:
        return pids


def main(*argv):
    if argv:
        procName = argv[0]
    else:
        procName = None
    pids = enumProcs(procName)
    #print(pids)
    for pid in pids:
        enumProcWnds(pid)


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.")
    sys.exit(rc)

Needless to say that:

  • In order for this code to work, you need to run it as a privileged user (Administrator); at least SeDebugPrivilege ([MS.Docs]: Privilege Constants) is required.
  • There might be surprises when the processes are running in 32 / 64 bit modes (the python process that you execute this code from, and the target processes enumerated by the code)


Update #0

  • Replaced all CTypes calls by PyWin32 ones
  • Improved algorithm
  • Fixed error in previous version

code01.py:

#!/usr/bin/env python

import sys
import os
import traceback
import win32con as wcon
import win32api as wapi
import win32gui as wgui
import win32process as wproc


# Callback
def enum_windows_proc(wnd, param):
    pid = param.get("pid", None)
    data = param.get("data", None)
    if pid is None or wproc.GetWindowThreadProcessId(wnd)[1] == pid:
        text = wgui.GetWindowText(wnd)
        if text:
            style = wapi.GetWindowLong(wnd, wcon.GWL_STYLE)
            if style & wcon.WS_VISIBLE:
                if data is not None:
                    data.append((wnd, text))
                #else:
                    #print("%08X - %s" % (wnd, text))


def enum_process_windows(pid=None):
    data = []
    param = {
        "pid": pid,
        "data": data,
    }
    wgui.EnumWindows(enum_windows_proc, param)
    return data


def _filter_processes(processes, search_name=None):
    if search_name is None:
        return processes
    filtered = []
    for pid, _ in processes:
        try:
            proc = wapi.OpenProcess(wcon.PROCESS_ALL_ACCESS, 0, pid)
        except:
            #print("Process {0:d} couldn't be opened: {1:}".format(pid, traceback.format_exc()))
            continue
        try:
            file_name = wproc.GetModuleFileNameEx(proc, None)
        except:
            #print("Error getting process name: {0:}".format(traceback.format_exc()))
            wapi.CloseHandle(proc)
            continue
        base_name = file_name.split(os.path.sep)[-1]
        if base_name.lower() == search_name.lower():
            filtered.append((pid, file_name))
        wapi.CloseHandle(proc)
    return tuple(filtered)


def enum_processes(process_name=None):
    procs = [(pid, None) for pid in wproc.EnumProcesses()]
    return _filter_processes(procs, search_name=process_name)


def main(*argv):
    proc_name = argv[0] if argv else None
    procs = enum_processes(process_name=proc_name)
    for pid, name in procs:
        data = enum_process_windows(pid)
        if data:
            proc_text = "PId {0:d}{1:s}windows:".format(pid, " (File: [{0:s}]) ".format(name) if name else " ")
            print(proc_text)
            for handle, text in data:
                print("    {0:d}: [{1:s}]".format(handle, text))


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.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q031278590]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 064bit on win32

PId 8048 windows:
    131462: [Program Manager]
PId 10292 windows:
    133738: [Skype]
PId 5716 windows:
    89659824: [python - Get the title of a window of another program using the process name - Stack Overflow - Google Chrome]
    132978: [Service Name and Transport Protocol Port Number Registry - Google Chrome]
    329646: [CristiFati/Prebuilt-Binaries: Various software built on various platforms. - Google Chrome]
    133078: [unittest — Unit testing framework — Python 3.8.2 documentation - Google Chrome]
    263924: [libssh2/libssh2 at libssh2-1.9.0 - Google Chrome]
    264100: [WNetAddConnection2A function (winnetwk.h) - Win32 apps | Microsoft Docs - Google Chrome]
    525390: [Understanding 4D -- The Tesseract - YouTube - Google Chrome]
    198398: [Workaround for virtual environments (VirtualEnv) by CristiFati · Pull Request #1442 · mhammond/pywin32 - Google Chrome]
    591586: [struct — Interpret bytes as packed binary data — Python 3.8.2 documentation - Google Chrome]
    263982: [Simulating an epidemic - YouTube - Google Chrome]
    329312: [SetHandleInformation function (handleapi.h) - Win32 apps | Microsoft Docs - Google Chrome]
    263248: [studiu functie faze - Google Search - Google Chrome]
    198364: [Lambda expressions (since C++11) - cppreference.com - Google Chrome]
PId 13640 windows:
    984686: [Total Commander (x64) 9.22a - NOT REGISTERED]
    44046462: [Lister - [c:\c\pula.txt]]
    4135542: [Lister - [e:\Work\Dev\CristiFati\Builds\Win\OPSWpython27\src\pywin32-b222\win32\src\win32process.i]]
    3873800: [Lister - [e:\Work\Dev\CristiFati\Builds\Win\OPSWpython27\src\pywin32-b222\win32\src\PyHANDLE.cpp]]
    29825332: [Lister - [E:\Work\Dev\Projects\DevTel\ifm\yosemite\GitLabA\yose\issues\buildnr\dependencies_200412.json]]
    8329240: [Lister - [e:\Work\Dev\Projects\DevTel\ifm\yosemite\src\svn\yosemite\CFATI_TRUNK_2\src\res\icpVerAppX.h]]
    985026: [Lister - [e:\Work\Dev\CristiFati\Builds\Win\OPSWpython27\src\pywin32-b222\win32\src\win32apimodule.cpp]]
PId 10936 windows:
    264744: [Junk - cristian.fati@devtelsoftware.com - Mozilla Thunderbird]
PId 10848 windows:
    1115842: [Registry Editor]
PId 6164 windows:
    264756: [Rocket.Chat]
PId 2716 windows:
    854508: [Skype]
    199310: [New tab and 5 more pages ‎- Microsoft Edge]
PId 14936 windows:
    655466: [Administrator: C:\Windows\System32\cmd.exe]
PId 15132 windows:
    526852: [Administrator: Cmd (064) - "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe"]
PId 15232 windows:
    133918: [Microsoft Edge]
PId 9748 windows:
    68492: [Microsoft Edge]
PId 14968 windows:
    134146: [Microsoft Edge]
    68634: [Microsoft Edge]
PId 15636 windows:
    134208: [Microsoft Edge]
PId 16348 windows:
    1379450: [Microsoft Edge]
PId 15568 windows:
    68828: [Microsoft Edge]
    68788: [Microsoft Edge]
PId 16040 windows:
    265406: [Administrator: Cmd (032)]
PId 5792 windows:
    2034532: [e:\Work\Dev\StackOverflow\q031278590\code00.py - Notepad++ [Administrator]]
PId 12032 windows:
    69134: [Microsoft Edge]
PId 16200 windows:
    69146: [Microsoft Text Input Application]
PId 16224 windows:
    69184: [Microsoft Edge]
PId 5288 windows:
    265806: [Administrator: Cmd (064) - "e:\Work\Dev\VEnvs\py_pc032_03.07.06_test0\Scripts\python.exe"]
PId 16476 windows:
    265814: [Administrator: Cmd (064) - "e:\Work\Dev\VEnvs\py_pc064_03.08.01_test0\Scripts\python.exe"]
PId 16612 windows:
    331388: [Administrator: Cmd (064) - python]
PId 16796 windows:
    592540: [Administrator: Cmd (064)]
PId 16880 windows:
    264894: [Administrator: C:\Windows\System32\cmd.exe]
PId 17156 windows:
    69284: [Console1 - Microsoft Visual Studio (Administrator)]
PId 16636 windows:
    69396: [QtConsole0 - Microsoft Visual Studio  (Administrator)]
PId 18380 windows:
    69522: [Console0 - Microsoft Visual Studio (Administrator)]
PId 18108 windows:
    200528: [BlackBird - Microsoft Visual Studio (Administrator)]
PId 19476 windows:
    1052868: [pcbuild - Microsoft Visual Studio  (Administrator)]
PId 16680 windows:
    200924: [Yosemite - Microsoft Visual Studio  (Administrator)]
PId 16020 windows:
    201030: [-bash]
PId 6532 windows:
    200996: [-bash]
PId 13140 windows:
    266602: [-bash]
PId 6032 windows:
    790834: [-bash]
PId 8496 windows:
    8130950: [-bash]
PId 3208 windows:
    4198878: [-bash]
PId 19088 windows:
    528856: [-bash]
PId 12744 windows:
    266770: [-bash]
PId 3896 windows:
    201370: [-bash]
PId 11512 windows:
    1315422: [Yosemite - Microsoft Visual Studio  (Administrator)]
PId 20660 windows:
    267028: [Yosemite - Microsoft Visual Studio  (Administrator)]
PId 20684 windows:
    70554: [Microsoft Visual Studio  (Administrator)]
PId 14808 windows:
    201692: [Dependency Walker]
PId 13056 windows:
    5509836: [Oracle VM VirtualBox Manager]
PId 17756 windows:
    70802: [TIC2Vone.pdf - Adobe Acrobat Reader DC]
PId 14572 windows:
    267868: [Select Administrator: Powershell (064)]
PId 25588 windows:
    332550: [Administrator: Cmd (064)]
PId 20504 windows:
    463448: [Administrator: C:\Windows\System32\cmd.exe]
PId 24740 windows:
    2298466: [Administrator: C:\Windows\System32\cmd.exe - "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe"]
PId 25960 windows:
    2430020: [Utils [E:\Work\Dev\Utils] - ...\current\ifm\pe_ver.py - PyCharm (Administrator)]
PId 28836 windows:
    332582: [E:\Work\Dev\Projects\DevTel\ifm\yosemite\src\svn\yosemite - Log Messages - TortoiseSVN]
PId 29796 windows:
    4724788: [Administrator: C:\Windows\System32\cmd.exe]
PId 26344 windows:
    2883688: [Dependency Walker]
PId 34124 windows:
    242746876: [Administrator: C:\Windows\System32\cmd.exe]
PId 21972 windows:
    1317748: [Administrator: Cmd (064)]
PId 35060 windows:
    2563162: [Administrator: C:\Windows\System32\cmd.exe - "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe"  code00.py]
PId 14692 windows:
    102695792: [Device Manager]
PId 35776 windows:
    990338: [Process Explorer - Sysinternals: www.sysinternals.com [CFATI-5510-0\cfati] (Administrator)]
PId 33524 windows:
    656408: [PE Explorer - 30 day evaluation version]
    25368616: [PE Explorer]
PId 29488 windows:
    3218206: [Microsoft Edge]
PId 13184 windows:
    267896: [cfati-ubtu16x64-0 [Running] - Oracle VM VirtualBox]
PId 33716 windows:
    3932934: [Cheat Engine 7.0]
    73098: [Cheat Engine 7.0]

Done.

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q031278590]>
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q031278590]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py "notepad++.exe"
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 064bit on win32

PId 5792 (File: [C:\Install\pc064\NP++\NP++\Version\notepad++.exe]) windows:
    2034532: [e:\Work\Dev\StackOverflow\q031278590\code00.py - Notepad++ [Administrator]]

Done.
CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • I'm getting a bunch of errors that look like `Error getting process name: Traceback (most recent call last): File "D:\Desktop\procs.py", line 27, in enumProcs name = win32api.GetModuleFileName(hProc) pywintypes.error: (126, 'GetModuleFileNameW', 'The specified module could not be found.')` and `Process [5248] couldn't be opened: Traceback (most recent call last): File "D:\Desktop\procs.py", line 22, in enumProcs hProc = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, pid) pywintypes.error: (5, 'OpenProcess', 'Access is denied.')` I am running it as admin. – Anish Gupta Jul 08 '15 at 16:15
  • Fixed the code. It's a hybrid now for window handling it's using `pywin32` while for process handling is using `ctypes` (as `pywin32` doesn't export some of the functions needed). – CristiFati Jul 08 '15 at 18:06
  • Did it work? Of course there'll be some processes that will return `Access denied` when trying to open. – CristiFati Jul 09 '15 at 19:32
  • @CristiFabi When I run it without args it works absolutely fine, but when i try to run it with an arg, chrome.exe for example this is hte error I get: `Error getting process name: Traceback (most recent call last): File "ez.py", line 39, in enumProcs name = buf.value.split(os.path.sep)[-1] TypeError: 'str' does not support the buffer interface` – Anish Gupta Jul 10 '15 at 12:37
  • Also, I don't need it to list all the window titles, just one. – Anish Gupta Jul 10 '15 at 12:55
  • The extra windows can be filtered out from `enumWindowsProc` (I saw many `Default IME` useless windows displayed). Regarding the `TypeError` replace line `39` to `name = buf.value.decode().split(os.path.sep)[-1]` (i tested the script with _Python2_). – CristiFati Jul 10 '15 at 13:46
  • I placed a new version of the code (that should work with _Python3_ and also only display relevant windows). – CristiFati Jul 10 '15 at 13:58
  • Thank you very much for all your help! :) – Anish Gupta Jul 10 '15 at 16:21
  • What exactly do all these lines do? `def enumWindowsProc(hwnd, lParam): if (lParam is None) or ((lParam is not None) and (win32process.GetWindowThreadProcessId(hwnd)[1] == lParam)): text = win32gui.GetWindowText(hwnd) if text: wStyle = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE) if wStyle & win32con.WS_VISIBLE: print("%08X - %s" % (hwnd, text))` – Anish Gupta Jul 14 '15 at 17:16
  • for each window, if a name was not supplied or a name was supplied and the name matches the window's process owner name, the window is processed. the window processing consists in checking if the window is visible (to avoid displaying all those unwanted windows that do exist). check [MSDN](https://msdn.microsoft.com) for detail regarding windows handling funcs. – CristiFati Jul 14 '15 at 17:46