0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Win / Mac】Pythonで開いているアプリとフォーカスされているアプリのパスを取得する

Last updated at Posted at 2024-11-18

目標

# フォーカスされているアプリを取得
get_active_app_path()  # "アプリケーションのパス"

# 開いている全てのアプリを取得
get_open_apps()  # {"アプリケーション名": "アプリケーションのパス", ...}

環境構築

Windows
pip install pywin32 psutil
Mac
pip install pyobjc

まとめてインストールするにはrequirements.txtを作成します。

requirements.txt
pyobjc; sys_platform == 'darwin'
pywin32; sys_platform == 'win32'
psutil; sys_platform == 'win32'
pip install -r requirements.txt

コード

フォーカスされているアプリ

import sys

if sys.platform == "win32":
    import win32gui
    import win32process
    from psutil import Process, NoSuchProcess

    def get_active_app_path() -> str | None:
        hwnd = win32gui.GetForegroundWindow()
        _, pid = win32process.GetWindowThreadProcessId(hwnd)
        if pid <= 0:
            return None
        try:
            path = Process(pid).exe()
        except NoSuchProcess:
            return None
        return path

elif sys.platform == "darwin":
    from AppKit import NSWorkspace

    def get_active_app_path() -> str:
        apps = NSWorkspace.sharedWorkspace().activeApplication()
        path = apps["NSApplicationPath"]
        return path

開いているアプリ一覧

import sys

if sys.platform == "darwin":
    from AppKit import NSWorkspace

    def get_open_apps() -> dict[str, str]:
        apps = NSWorkspace.sharedWorkspace().launchedApplications()
        data = {app["NSApplicationName"]: app["NSApplicationPath"] for app in apps} # 因みに、app["NSApplicationProcessIdentifier"] でプロセスIDを取得できる
        return data

elif sys.platform == "win32":
    import win32gui
    import win32process
    import win32con
    import ctypes
    from ctypes.wintypes import DWORD, RECT
    import pythoncom
    from win32com.client import GetObject
    from psutil import Process

    class Win32Window:
        def __init__(self, hWnd: int):
            self._hWnd = hWnd
        @property
        def title(self) -> str:
            name = win32gui.GetWindowText(self._hWnd)
            if isinstance(name, bytes):
                name = name.decode()
            return name or ""
        @property
        def path(self) -> str:
            _, pid = win32process.GetWindowThreadProcessId(self._hWnd)
            return Process(pid).exe()
        def getHandle(self) -> int:
            return self._hWnd
    def _find_window_handles(parent: int | None = None, window_class: str | None = None, title: str | None = None, onlyVisible: bool = True) -> list[int]:
        handle_list: list[int] = []
        def findit(hwnd: int, _) -> bool:
            if window_class and window_class != win32gui.GetClassName(hwnd):
                return True
            if title and title != win32gui.GetWindowText(hwnd):
                return True
            if not onlyVisible or (onlyVisible and win32gui.IsWindowVisible(hwnd)):
                handle_list.append(hwnd)
            return True
        if not parent:
            parent = win32gui.GetDesktopWindow()
        win32gui.EnumChildWindows(parent, findit, None)
        return handle_list
    def _findMainWindowHandles() -> list[tuple[int, int]]:
        class TITLEBARINFO(ctypes.Structure):
            _fields_ = [
                ("cbSize", DWORD),
                ("rcTitleBar", RECT),
                ("rgstate", DWORD * 6)
            ]
        def win_enum_handler(hwnd: int, ctx: any):
            if not win32gui.IsWindowVisible(hwnd):
                return
            title_info = TITLEBARINFO()
            title_info.cbSize = ctypes.sizeof(title_info)
            ctypes.windll.user32.GetTitleBarInfo(hwnd, ctypes.byref(title_info))
            isCloaked = ctypes.c_int(0)
            DWMWA_CLOAKED = 14
            ctypes.windll.dwmapi.DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, ctypes.byref(isCloaked), ctypes.sizeof(isCloaked))
            title = win32gui.GetWindowText(hwnd)
            if win32gui.IsWindowVisible(hwnd) and title != "" and isCloaked.value == 0:
                if not (title_info.rgstate[0] & win32con.STATE_SYSTEM_INVISIBLE):
                    handle_list.append((hwnd, win32process.GetWindowThreadProcessId(hwnd)[1]))
        handle_list: list[tuple[int, int]] = []
        win32gui.EnumWindows(win_enum_handler, None)
        return handle_list
    def _get_all_apps():
        pythoncom.CoInitialize() # メインスレッド外で実行する場合のエラー回避
        WMI = GetObject("winmgmts:")
        mainWindows = [w[1] for w in _findMainWindowHandles()]
        return [(p.Properties_("ProcessID").Value, p.Properties_("Name").Value) for p in WMI.InstancesOf("Win32_Process") if p.Properties_("ProcessID").Value in mainWindows]
    def __remove_bad_windows(windows: list[int] | None) -> list[Win32Window]:
        outList = []
        if windows is not None:
            for window in windows:
                try:
                    outList.append(Win32Window(window))
                except:
                    pass
        return outList

    def get_open_apps() -> dict[str, str]:
        process_list = _get_all_apps()
        result: dict[str, str] = {}
        for win in __remove_bad_windows(_find_window_handles()):
            pID = win32process.GetWindowThreadProcessId(win.getHandle())
            for item in process_list:
                appPID = item[0]
                appName = str(item[1])
                if appPID == pID[1]:
                    if not appName in result:
                        result[appName] = win.path # 因みに、win.title でウィンドウのタイトルを取得できる
                    break
        return result

解説

割愛(リクエストがあれば書きます)

謝辞

上記のコードの中には私が書いたものだけではなく、ネットを彷徨って見つけたコードのコピペも紛れています。それらのページを見つけることができなかったので出典がありません。原作者の方ごめんなさい。
もしコピペ元だと思われるページを見つけた方は教えていただけるとありがたいです。

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?