PyGetWindowを使って、開いているウィンドウを自在に操る方法を徹底的に学びます。
はじめに:PyGetWindowとは? 🤔
PyGetWindowは、Pythonでデスクトップアプリケーションのウィンドウに関する情報を取得したり、ウィンドウを操作(移動、リサイズ、最小化、最大化、アクティブ化、閉じるなど)したりするためのシンプルなクロスプラットフォームライブラリです。 元々はWindows専用でしたが、コミュニティの貢献によりmacOSやLinux(X11)にも対応が進んでいます(ただし、プラットフォームによっては一部機能に制限がある場合があります)。 特に、GUIオートメーションツールである PyAutoGUI と組み合わせて使われることが多く、特定のウィンドウを対象とした自動操作を実装する際に非常に役立ちます。 PyGetWindow自体はウィンドウの内容(ボタンやテキストボックスなど)を直接操作する機能は持ちませんが、ウィンドウの位置や状態を管理する上で強力なツールとなります。
このブログでは、PyGetWindowの基本的な使い方から、少し応用的なテクニック、プラットフォームごとの注意点まで、幅広く解説していきます。Pythonでウィンドウ操作を自動化したい方、PyAutoGUIなどのライブラリと連携させたい方はぜひ参考にしてください! 😊
インストール方法 🛠️
PyGetWindowのインストールは非常に簡単です。Pythonのパッケージインストーラであるpip
を使って、コマンドライン(ターミナルやコマンドプロンプト)から以下のコマンドを実行するだけです。
pip install pygetwindow
Python 2とPython 3が両方インストールされている環境や、特定のPython環境にインストールしたい場合は、pip3
やpython -m pip
のように、使用するPythonのバージョンに対応したpipコマンドを使用してください。
# Python 3 を明示的に指定する場合
pip3 install pygetwindow
# または
python3 -m pip install pygetwindow
conda環境を使用している場合は、conda-forgeチャンネルからインストールすることも可能です。
conda install -c conda-forge pygetwindow
インストールが正常に完了したか確認するには、Pythonインタプリタを起動し、以下のようにライブラリをインポートしてバージョンを表示させてみてください。エラーが出ずにバージョン番号が表示されれば成功です 🎉。
import pygetwindow
print(pygetwindow.__version__)
- Windows:
ctypes
(標準ライブラリ) を介して Win32 API を利用します。追加のインストールは通常不要です。 - macOS:
AppKit
やQuartz
(Core Graphics) フレームワークを利用します。これらはOS標準の機能ですが、PyGetWindowが内部で使用する他のPythonパッケージ (例:pyobjc-core
,pyobjc
) が必要になる場合があります (通常はpipが自動で依存関係を解決します)。 - Linux: X Window System (X11) の機能を利用するため、
python-xlib
(またはpython3-xlib
) が必要です。pipでのインストール時に自動で入らない場合は、別途OSのパッケージマネージャ (apt, yumなど) でインストールする必要があります。
また、PyAutoGUIをインストールすると、依存関係としてPyGetWindowも一緒にインストールされることがあります。# Debian/Ubuntu系 sudo apt-get install python3-xlib # Fedora/CentOS系 sudo yum install python3-xlib
基本的な使い方:ウィンドウ情報の取得 🔍
PyGetWindowを使うと、現在デスクトップ上に表示されているウィンドウに関する様々な情報を取得できます。
すべてのウィンドウタイトルを取得する
現在開かれているすべての(可視状態の)ウィンドウのタイトルをリストとして取得するには、getAllTitles()
関数を使用します。
import pygetwindow as gw
all_titles = gw.getAllTitles()
print("現在開いているウィンドウのタイトル一覧:")
for i, title in enumerate(all_titles):
# タイトルが空文字列の場合もあるため、空でないものだけ表示
if title:
print(f"{i+1}: {title}")
実行すると、その時点で開いているウィンドウのタイトルが順番に出力されます。ただし、タイトルを持たないウィンドウや内部的なウィンドウも含まれることがあるため、リストには空文字列が含まれることがあります。
すべてのウィンドウオブジェクトを取得する
タイトルだけでなく、ウィンドウの位置やサイズなどの情報も扱いたい場合は、getAllWindows()
関数を使用します。これは、各ウィンドウを表すWindow
オブジェクトのリストを返します。
import pygetwindow as gw
all_windows = gw.getAllWindows()
print("現在開いているウィンドウオブジェクト一覧:")
for i, window in enumerate(all_windows):
# isVisible や title プロパティなどでフィルタリング可能
if window.title and window.isVisible:
print(f"{i+1}: Title='{window.title}', Size=({window.width}, {window.height}), Position=({window.left}, {window.top})")
Window
オブジェクトには、後述する様々なプロパティやメソッドが用意されており、ウィンドウの詳細情報を取得したり、操作したりできます。
特定のタイトルを持つウィンドウを取得する
特定のアプリケーションウィンドウなどを操作したい場合、ウィンドウタイトルを指定してWindow
オブジェクトを取得するのが一般的です。getWindowsWithTitle()
関数を使用します。この関数は、指定したタイトル文字列に(部分一致ではなく)完全に一致するタイトルを持つWindow
オブジェクトのリストを返します。
import pygetwindow as gw
# 例: タイトルが「メモ帳」のウィンドウを取得 (Windowsの場合)
# 注意: アプリケーション名やドキュメント名によってタイトルは変わります
try:
# 同じタイトルのウィンドウが複数ある可能性があるのでリストで返る
notepad_windows = gw.getWindowsWithTitle('無題 - メモ帳') # 環境に合わせてタイトル名を調整してください
if notepad_windows:
notepad_window = notepad_windows[0] # 最初に見つかったウィンドウを取得
print(f"メモ帳ウィンドウが見つかりました: {notepad_window}")
print(f" タイトル: {notepad_window.title}")
print(f" サイズ: 幅={notepad_window.width}, 高さ={notepad_window.height}")
print(f" 位置: 左={notepad_window.left}, 上={notepad_window.top}")
print(f" アクティブ?: {notepad_window.isActive}")
print(f" 最小化されてる?: {notepad_window.isMinimized}")
print(f" 最大化されてる?: {notepad_window.isMaximized}")
else:
print("指定されたタイトルのウィンドウは見つかりませんでした。")
except Exception as e:
print(f"エラーが発生しました: {e}")
# 例: タイトルに「Chrome」を含むウィンドウを検索 (部分一致のような使い方)
# getWindowsWithTitle は完全一致なので、getAllTitles と組み合わせる
chrome_windows = []
all_titles = gw.getAllTitles()
for title in all_titles:
if "Chrome" in title: # 部分一致で判定
# タイトルが分かったら、改めてgetWindowsWithTitleでオブジェクトを取得
wins = gw.getWindowsWithTitle(title)
if wins:
chrome_windows.append(wins[0])
if chrome_windows:
print("\nChromeを含むタイトルのウィンドウ:")
for win in chrome_windows:
print(f"- {win.title}")
else:
print("\nChromeを含むタイトルのウィンドウは見つかりませんでした。")
getWindowsWithTitle()
は基本的に完全一致です。タイトルの一部で検索したい場合は、一度 getAllTitles()
や getAllWindows()
ですべてのウィンドウ情報を取得し、Pythonの文字列処理 (in
演算子など) を使って目的のウィンドウを絞り込む方法が一般的です。現在アクティブなウィンドウを取得する
ユーザーが現在操作している、最前面にあるウィンドウ(アクティブウィンドウ)を取得するには、getActiveWindow()
関数を使用します。これは単一のWindow
オブジェクトを返します。
import pygetwindow as gw
import time
print("5秒後にアクティブなウィンドウの情報を表示します...")
time.sleep(5) # ユーザーが目的のウィンドウをアクティブにする時間
try:
active_window = gw.getActiveWindow()
if active_window:
print(f"現在アクティブなウィンドウ:")
print(f" タイトル: {active_window.title}")
print(f" サイズ: ({active_window.width}, {active_window.height})")
print(f" 位置: ({active_window.left}, {active_window.top})")
else:
print("アクティブなウィンドウを取得できませんでした。")
except Exception as e:
# 環境によってはアクティブウィンドウの取得に失敗することがある
print(f"アクティブウィンドウの取得中にエラー: {e}")
フォーカスがどこにもない状態など、状況によってはアクティブウィンドウを正しく取得できない場合もあります。
Windowオブジェクトの主要なプロパティ
Window
オブジェクトから取得できる主な情報(プロパティ)には以下のようなものがあります。これらはウィンドウの状態を知るのに役立ちます。
プロパティ名 | 説明 | 型 | 書き込み可能か |
---|---|---|---|
title | ウィンドウのタイトルバーに表示されるテキスト | str | ❌ (読み取り専用) |
size | ウィンドウのサイズ (幅, 高さ) | tuple (int, int) | ✔️ (タプルで設定) |
width | ウィンドウの幅 | int | ✔️ |
height | ウィンドウの高さ | int | ✔️ |
topleft | ウィンドウの左上の座標 (x, y) | tuple (int, int) | ✔️ (タプルで設定) |
left (または x ) | ウィンドウの左端のX座標 | int | ✔️ |
top (または y ) | ウィンドウの上端のY座標 | int | ✔️ |
bottomright | ウィンドウの右下の座標 (x, y) | tuple (int, int) | ✔️ (タプルで設定) |
right | ウィンドウの右端のX座標 | int | ✔️ |
bottom | ウィンドウの下端のY座標 | int | ✔️ |
center | ウィンドウの中央の座標 (x, y) | tuple (int, int) | ✔️ (タプルで設定) |
centerx | ウィンドウの中央のX座標 | int | ✔️ |
centery | ウィンドウの中央のY座標 | int | ✔️ |
box | ウィンドウの境界ボックス (左, 上, 幅, 高さ) | tuple (int, int, int, int) | ❌ (読み取り専用) |
area | ウィンドウの面積 (幅 * 高さ) | int | ❌ (読み取り専用) |
isActive | ウィンドウが現在アクティブ(最前面)かどうか | bool | ❌ (読み取り専用、操作はactivate() を使用) |
isMinimized | ウィンドウが最小化されているかどうか | bool | ✔️ (True/Falseで設定) |
isMaximized | ウィンドウが最大化されているかどうか | bool | ✔️ (True/Falseで設定) |
isAlive | ウィンドウが存在している(閉じられていない)かどうか | bool | ❌ (読み取り専用) |
isVisible | ウィンドウが画面上で可視状態か(最小化されていてもTrueの場合あり) | bool | ✔️ (True/Falseで設定、非表示/表示) |
hWnd (Windowsのみ) | ウィンドウハンドル | int | ❌ (読み取り専用) |
書き込み可能なプロパティに値を代入することで、ウィンドウのサイズや位置を直接変更することもできます。例えば、window.width = 800
や window.topleft = (100, 100)
のように記述します。
ウィンドウ操作:動かす、サイズを変える、など ⚙️
PyGetWindowの真骨頂は、取得したWindow
オブジェクトを使ってウィンドウを操作できる点です。
ウィンドウをアクティブにする (最前面に表示)
特定のウィンドウをユーザーが操作できる状態(最前面)にするには、activate()
メソッドを使用します。
import pygetwindow as gw
import time
try:
# 例: 電卓アプリをアクティブにする (Windowsの場合)
calc_windows = gw.getWindowsWithTitle('電卓')
if calc_windows:
calc_window = calc_windows[0]
if not calc_window.isActive:
print("電卓ウィンドウをアクティブにします...")
calc_window.activate()
time.sleep(1) # アクティブ化が完了するのを少し待つ
if calc_window.isActive:
print("アクティブ化成功! ✨")
else:
# OSの制限などでアクティブにできない場合がある
print("アクティブ化を試みましたが、失敗しました。")
else:
print("電卓ウィンドウは既にアクティブです。")
else:
print("電卓ウィンドウが見つかりません。")
except Exception as e:
print(f"操作中にエラーが発生しました: {e}")
activate()
が期待通りに動作しないことがあります。
2021年頃の議論では、Windowsでactivate()
が成功コード(0)を返すにも関わらず実際にはアクティブにならないケースが報告されており、その回避策として一度minimize()
してからmaximize()
またはrestore()
を試す方法が提案されていました。# activate()が効かない場合の代替策の例
if not target_window.isActive:
try:
target_window.minimize()
time.sleep(0.1) # 少し待機
target_window.restore() # または target_window.maximize()
time.sleep(0.1) # 少し待機
# 再度activateを試みる (不要な場合もある)
# target_window.activate()
except Exception as e_ workaround:
print(f"アクティブ化の代替策中にエラー: {e_workaround}")
ウィンドウの移動
ウィンドウを画面上の特定の位置に移動させるには、moveTo(x, y)
メソッドまたは move(dx, dy)
メソッドを使用します。
moveTo(x, y)
: ウィンドウの左上の角を指定した絶対座標 (x, y) に移動します。move(dx, dy)
: ウィンドウを現在の位置から相対的に (dx, dy) だけ移動します。dxが正なら右へ、負なら左へ。dyが正なら下へ、負なら上へ移動します。
import pygetwindow as gw
import time
try:
target_window = gw.getActiveWindow() # 現在アクティブなウィンドウを対象にする
if target_window:
print(f"対象ウィンドウ: {target_window.title}")
original_pos = target_window.topleft
print(f"現在の位置: {original_pos}")
# 左上(0, 0)に移動
print("左上 (0, 0) に移動します...")
target_window.moveTo(0, 0)
time.sleep(2)
print(f"移動後の位置: {target_window.topleft}")
# 現在位置から右に100px、下に50px移動
print("右に100px、下に50px移動します...")
target_window.move(100, 50)
time.sleep(2)
print(f"移動後の位置: {target_window.topleft}")
# 元の位置に戻す
print("元の位置に戻します...")
target_window.moveTo(original_pos.x, original_pos.y)
time.sleep(1)
print(f"最終位置: {target_window.topleft}")
else:
print("操作対象のアクティブウィンドウが見つかりません。")
except Exception as e:
print(f"操作中にエラーが発生しました: {e}")
プロパティを使って window.topleft = (x, y)
や window.center = (cx, cy)
のように座標を設定することでも移動が可能です。
ウィンドウのリサイズ
ウィンドウのサイズを変更するには、resizeTo(width, height)
メソッドまたは resize(dWidth, dHeight)
メソッドを使用します。
resizeTo(width, height)
: ウィンドウの幅と高さを指定した絶対的なサイズ (width, height) に変更します。resize(dWidth, dHeight)
: ウィンドウの現在の幅と高さから相対的に (dWidth, dHeight) だけサイズを変更します。dWidthが正なら幅を広げ、負なら狭めます。dHeightが正なら高くし、負なら低くします。
import pygetwindow as gw
import time
try:
target_window = gw.getActiveWindow()
if target_window:
print(f"対象ウィンドウ: {target_window.title}")
original_size = target_window.size
print(f"現在のサイズ: {original_size}")
# 幅600px, 高さ400px にリサイズ
print("サイズを (600, 400) に変更します...")
target_window.resizeTo(600, 400)
time.sleep(2)
print(f"変更後のサイズ: {target_window.size}")
# 現在のサイズから幅を+100px、高さを-50px変更
print("幅を+100px、高さを-50px変更します...")
target_window.resize(100, -50)
time.sleep(2)
print(f"変更後のサイズ: {target_window.size}")
# 元のサイズに戻す
print("元のサイズに戻します...")
target_window.resizeTo(original_size.width, original_size.height)
time.sleep(1)
print(f"最終サイズ: {target_window.size}")
else:
print("操作対象のアクティブウィンドウが見つかりません。")
except Exception as e:
print(f"操作中にエラーが発生しました: {e}")
移動と同様に、プロパティ window.size = (width, height)
や window.width = new_width
, window.height = new_height
を使ってサイズを変更することもできます。
2023年頃に、特定のアプリ(例:Notepad++)のウィンドウサイズを指定した解像度(例:1920×1080)に設定するためにPyGetWindowが利用された事例があります。これは、デモ動画の録画などでウィンドウサイズを統一したい場合に便利です。
最小化、最大化、元のサイズに戻す
ウィンドウの状態を変更するメソッドも用意されています。
minimize()
: ウィンドウを最小化します(タスクバーに格納するなど)。maximize()
: ウィンドウを最大化します(画面全体に広げるなど)。restore()
: 最小化または最大化されているウィンドウを、元の通常のサイズと位置に戻します。
import pygetwindow as gw
import time
try:
target_window = gw.getActiveWindow()
if target_window:
print(f"対象ウィンドウ: {target_window.title}")
if target_window.isMaximized:
print("現在最大化されています。元に戻します...")
target_window.restore()
time.sleep(1)
if not target_window.isMinimized:
print("最小化します...")
target_window.minimize()
time.sleep(2)
print(f"最小化状態: {target_window.isMinimized}")
if target_window.isMinimized:
print("元のサイズに戻します...")
target_window.restore()
time.sleep(2)
print(f"最小化状態: {target_window.isMinimized}")
print(f"最大化状態: {target_window.isMaximized}")
if not target_window.isMaximized:
print("最大化します...")
target_window.maximize()
time.sleep(2)
print(f"最大化状態: {target_window.isMaximized}")
print("最後に元のサイズに戻します...")
target_window.restore()
time.sleep(1)
print("操作完了🏁")
else:
print("操作対象のアクティブウィンドウが見つかりません。")
except Exception as e:
print(f"操作中にエラーが発生しました: {e}")
これらの操作は、プロパティ window.isMinimized = True
や window.isMaximized = True
を設定することでも実行できます。restore()
に対応する直接的なプロパティ設定はありません(isMinimized=False
かつ isMaximized=False
の状態にすることになりますが、restore()
メソッドを使う方が意図が明確です)。
ウィンドウを閉じる
アプリケーションウィンドウを閉じるには、close()
メソッドを使用します。これは通常、ウィンドウの右上にある「閉じる」ボタンをクリックするのと同じ操作に相当します。
import pygetwindow as gw
import time
import subprocess # メモ帳を起動するため
try:
# テスト用にメモ帳を起動 (Windowsの場合)
print("メモ帳を起動します...")
subprocess.Popen(['notepad.exe'])
time.sleep(2) # 起動を待つ
# メモ帳ウィンドウを取得
notepad_window = gw.getWindowsWithTitle('無題 - メモ帳')[0] # タイトルは環境による
if notepad_window:
print(f"メモ帳ウィンドウ ({notepad_window.title}) を閉じます...")
notepad_window.activate() # 閉じる前にアクティブにするのが確実な場合がある
time.sleep(0.5)
notepad_window.close()
time.sleep(1) # 閉じる処理を待つ
# 閉じたか確認
if not notepad_window.isAlive:
print("メモ帳ウィンドウは正常に閉じられました。👍")
else:
# 保存ダイアログなどが出ている可能性
print("ウィンドウを閉じられませんでした。変更が保存されていない可能性があります。")
# 強制終了が必要な場合は別の手段 (os.killなど) を検討
else:
print("メモ帳ウィンドウが見つかりませんでした。")
except IndexError:
print("メモ帳ウィンドウが見つかりませんでした(起動直後などでタイトルが違う可能性)。")
except Exception as e:
print(f"操作中にエラーが発生しました: {e}")
close()
メソッドは、アプリケーションに通常の終了処理を要求します。もし編集中で未保存の変更がある場合、多くのアプリケーションは保存確認のダイアログを表示し、close()
だけではウィンドウは閉じません。強制的にプロセスを終了させたい場合は、Pythonのos
モジュールやsubprocess
モジュールを使ってプロセスIDを特定し、killする必要がありますが、これはデータ損失のリスクがあるため注意が必要です。プラットフォームごとの注意点と挙動の違い 💻🍎🐧
PyGetWindowはクロスプラットフォームを目指していますが、基盤となるOSのウィンドウシステムの違いにより、挙動や利用できる機能、注意点が異なります。
Windows
- 安定性: PyGetWindowは元々Windows向けに開発された経緯もあり、Windowsでの動作は比較的安定しています。Win32 APIを直接利用しています。
- ウィンドウハンドル (
hWnd
):Window
オブジェクトのhWnd
プロパティを通じて、Windows固有のウィンドウハンドルを取得できます。これは、pywin32
などの他のWindows専用ライブラリと連携する際に役立ちます。 - アクティブ化の挙動: 前述の通り、OSのセキュリティポリシーにより、バックグラウンドのプロセスが自由にウィンドウを前面に出すことが制限される場合があります。
activate()
が効かない場合は、minimize()
/restore()
の回避策を試す価値があります。 - 座標系の原点: 通常、画面の左上が(0, 0)です。マルチモニター環境での挙動はモニターの配置設定に依存します。
- 報告された問題: 過去には、
ctypes
の使い方の問題や、PyAutoGUIをインポートすると座標の取得結果が変わるなどの問題が報告されたことがあります(2020年頃)。最新バージョンで改善されている可能性がありますが、予期せぬ挙動に遭遇した場合はライブラリのバージョンや他のインポートしているライブラリとの相性を確認してみてください。
macOS
- 実装: macOS版の実装は、AppleScriptやQuartz (Core Graphics) API、AppKitフレームワークなどを利用しています。
- パーミッション: 他のアプリケーションのウィンドウを制御するには、通常、システム環境設定の「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」で、Pythonを実行しているターミナルやIDE、または作成したアプリケーション自体に制御の許可を与える必要があります。許可がないと操作が失敗したり、情報を取得できなかったりします。
- AppleScriptへの依存: 一部の機能(特に他のアプリのウィンドウ操作)はAppleScriptに依存している場合があります。AppleScriptはアプリケーションによってサポート状況が異なるため、全てのアプリで同じように操作できるとは限りません。また、AppleScriptの実行は比較的低速になることがあります。
- ウィンドウタイトル: macOSでは、アプリケーション名とドキュメント名が組み合わされてウィンドウタイトルになることが多いです(例: “文書1 – テキストエディット”)。
getAllTitles()
などで取得されるタイトル形式を確認することが重要です。 - アクティブウィンドウ:
getActiveWindow()
は、Quartz APIなどを使って前面のアプリケーション情報を取得しようとします。 - 進化: PyGetWindowから派生したPyWinCtlというライブラリでは、macOSサポートがより強化されています。PyGetWindow本体でのmacOSサポートも継続的に改善されている可能性があります。
Linux (X11)
- 依存関係: X Window System (X11) を操作するための
python-xlib
(またはpython3-xlib
) ライブラリが必須です。Wayland環境では基本的に動作しません(XWayland互換レイヤー上で動作する可能性はありますが、保証されません)。 - ウィンドウマネージャの影響: Linuxでは様々なデスクトップ環境 (GNOME, KDE, XFCEなど) とウィンドウマネージャ (Mutter, KWin, Compizなど) が存在し、ウィンドウの挙動や装飾、APIの応答が異なる場合があります。特定の環境でのみ問題が発生する可能性も考えられます。
- 機能の可用性: WindowsやmacOSと比較して、Linux版で利用できる機能が限られている可能性があります。開発はコミュニティベースで進められているため、プラットフォーム間の機能差が存在することがあります。
- 進化: macOSと同様に、派生ライブラリのPyWinCtlではLinuxサポートも強化されています。
高度な使い方 / Tips ✨
ウィンドウの存在を確認する
特定のウィンドウが開いているかどうかを確認するには、getWindowsWithTitle()
などで取得を試み、結果が空リストかどうかで判断できます。または、取得済みの Window
オブジェクトがあれば isAlive
プロパティを確認します。
import pygetwindow as gw
import time
TARGET_TITLE = "電卓" # 確認したいウィンドウタイトル
def is_window_open(title):
"""指定したタイトルのウィンドウが開いているか確認する"""
windows = gw.getWindowsWithTitle(title)
return len(windows) > 0
# 5秒おきに電卓ウィンドウが開いているかチェック
for i in range(5):
if is_window_open(TARGET_TITLE):
print(f"'{TARGET_TITLE}' ウィンドウは開いています。✅")
# 開いていたら何か処理をする
# win = gw.getWindowsWithTitle(TARGET_TITLE)[0]
# win.activate()
break
else:
print(f"'{TARGET_TITLE}' ウィンドウはまだ開いていません... ({i+1}/5)")
time.sleep(5)
else:
print(f"'{TARGET_TITLE}' ウィンドウが見つかりませんでした。❌")
# 既存のWindowオブジェクトで確認する場合
try:
win = gw.getWindowsWithTitle(TARGET_TITLE)[0]
print(f"\n取得したWindowオブジェクト: {win.title}")
time.sleep(3) # この間に手動でウィンドウを閉じてみるなど
if win.isAlive:
print("ウィンドウはまだ存在しています。")
else:
print("ウィンドウは閉じられました。")
except IndexError:
print("\nそもそもウィンドウが見つかりません。")
except Exception as e:
print(f"\nエラー: {e}")
特定のウィンドウが表示されるまで待機する
アプリケーションの起動直後など、目的のウィンドウがすぐに表示されない場合があります。そのような場合は、一定時間待機するか、ウィンドウが表示されるまでポーリング(繰り返し確認)する処理を入れると安定性が増します。
import pygetwindow as gw
import time
import subprocess
TARGET_TITLE = "無題 - メモ帳"
TIMEOUT_SECONDS = 30 # 最大待機時間(秒)
POLL_INTERVAL = 1 # 確認間隔(秒)
# メモ帳を起動
print("メモ帳を起動します...")
subprocess.Popen(['notepad.exe'])
start_time = time.time()
notepad_window = None
print(f"'{TARGET_TITLE}' ウィンドウが表示されるのを待ちます (最大{TIMEOUT_SECONDS}秒)...")
while time.time() - start_time < TIMEOUT_SECONDS:
windows = gw.getWindowsWithTitle(TARGET_TITLE)
if windows:
notepad_window = windows[0]
print(f"\nウィンドウが見つかりました!🎉: {notepad_window.title}")
break
else:
print(".", end="", flush=True)
time.sleep(POLL_INTERVAL)
else:
# ループがタイムアウトした場合
print(f"\nタイムアウトしました。'{TARGET_TITLE}' ウィンドウが見つかりませんでした。")
if notepad_window:
# ウィンドウが見つかった後の処理
notepad_window.activate()
notepad_window.resizeTo(500, 300)
print("ウィンドウを操作しました。")
エラーハンドリング
ウィンドウ操作は、対象のウィンドウが存在しない、OSの制限、パーミッション不足など、様々な理由で失敗する可能性があります。堅牢なスクリプトを作成するには、try...except
ブロックを使ってエラーハンドリングを行うことが重要です。
PyGetWindowは操作失敗時に PyGetWindowException
や、プラットフォーム固有の例外(Windowsなら pygetwindow.PyGetWindowException
にWindowsエラーコードが含まれる、macOSならAppleScriptエラーなど)を発生させることがあります。
import pygetwindow as gw
import time
try:
# 存在しない可能性のあるウィンドウを取得しようとする
non_existent_window = gw.getWindowsWithTitle("絶対に存在しないウィンドウタイトル")[0] # IndexErrorが発生する可能性
# または、取得できたウィンドウに対して不正な操作を試みる
# active_win = gw.getActiveWindow()
# if active_win:
# active_win.resizeTo(-100, -100) # 不正なサイズ指定 (PyGetWindowExceptionが発生する可能性)
except IndexError:
print("エラー: 指定されたタイトルのウィンドウが見つかりませんでした。")
except gw.PyGetWindowException as e:
print(f"PyGetWindowエラーが発生しました: {e}")
# Windowsの場合、エラーコードが含まれていることが多い
# 例: "Error code from Windows: 5 - アクセスが拒否されました。"
except Exception as e:
# 予期しないその他のエラー
print(f"予期しないエラーが発生しました: {type(e).__name__} - {e}")
finally:
print("エラーハンドリングのデモを終了します。")
特に、ユーザーが操作中にウィンドウを閉じてしまったり、予期せずアプリケーションがクラッシュしたりする場合に備え、Window
オブジェクトのメソッドを呼び出す前には isAlive
プロパティを確認するなどの対策も有効です。
マルチモニター環境での座標
PyGetWindowが返す座標(left
, top
など)は、通常、プライマリモニターの左上を原点(0, 0)とした仮想スクリーン座標に基づきます。セカンダリモニターは、プライマリモニターに対して相対的な位置に配置されるため、負の座標や非常に大きな座標値になることがあります。
マルチモニター環境で特定のモニター上にウィンドウを配置したい場合は、各モニターの解像度と配置(OSの設定で確認可能)を考慮して座標を計算する必要があります。PyGetWindow自体にはモニター情報を直接取得する機能は標準では含まれていませんが、screeninfo
やプラットフォーム固有のライブラリ(Windowsなら pywin32
、macOSなら AppKit
)を併用することでモニター情報を取得できます。
PyGetWindowと他のライブラリの比較 🆚
Pythonにはウィンドウ操作やGUIオートメーションのためのライブラリがいくつか存在します。PyGetWindowと他の主要なライブラリとの違いを見てみましょう。
ライブラリ | 主な機能 | プラットフォーム | 特徴 | 使い分け |
---|---|---|---|---|
PyGetWindow | ウィンドウ情報の取得、ウィンドウ状態の操作(移動、リサイズ、最小化/最大化、アクティブ化、閉じる) | Windows, macOS, Linux (X11) | ・シンプルで軽量 ・ウィンドウ自体の管理に特化 ・PyAutoGUIと連携しやすい | ・特定のウィンドウの位置やサイズを知りたい/変更したい ・ウィンドウを最前面に出したり、閉じたりしたい ・PyAutoGUIで操作対象のウィンドウを特定したい |
PyAutoGUI | マウス操作(移動、クリック、ドラッグ)、キーボード操作(入力、ホットキー)、スクリーンショット、シンプルな画像認識 | Windows, macOS, Linux (X11, 要scrot/tkinter) | ・GUI操作全般を自動化 ・座標ベース、画像ベースでの操作 ・PyGetWindowを内部で利用または連携可能 | ・画面上のボタンクリックや文字入力を自動化したい ・特定の画像が表示されたら処理を行いたい ・RPA的なタスクを実行したい |
PyWinAuto | Windows GUI要素(ボタン、テキストボックス、メニュー等)の特定と操作、ウィンドウ操作 | Windows (メイン), Linux (実験的) | ・Windowsアプリの詳細な自動化に特化 ・コントロールIDやクラス名、テキストで要素を特定 ・バックエンドとしてWin32 APIとUI Automationを選択可能 | ・特定のWindowsアプリケーションの内部要素を正確に操作したい ・座標や画像に頼らない、より安定したWindows自動化を行いたい |
PyWinCtl | PyGetWindowの機能に加え、マルチモニターサポート強化、ウィンドウ変更通知、メニュー操作など | Windows, macOS, Linux (X11) | ・PyGetWindowの進化版/フォーク ・より多くの機能と改善を含む ・macOS/Linuxサポートがより積極的 | ・PyGetWindowの機能で不足がある場合 ・マルチモニター環境での高度な制御が必要な場合 ・ウィンドウの状態変化を監視したい場合 |
pywin32 | Win32 APIへの包括的なアクセス | Windows | ・低レベルなWindows API操作が可能 ・非常に多機能だが、API知識が必要 ・PyGetWindowやPyWinAutoの内部でも利用されることがある | ・PyGetWindow等で提供されない特殊なWindows API機能を使いたい ・Windowsシステムと深く連携したい |
簡単にまとめると、
- ウィンドウ自体の位置や状態をシンプルに扱いたい → PyGetWindow
- マウス・キーボード操作や画面上の画像認識を含めた汎用的なGUI自動化 → PyAutoGUI (+ PyGetWindow)
- Windowsアプリのボタンやテキストボックスなどを正確に操作したい → PyWinAuto
- PyGetWindowより高機能なウィンドウ制御(特にマルチモニターや通知)が必要 → PyWinCtl
- Windows APIを直接叩きたい → pywin32
という使い分けになります。多くの場合、PyGetWindowはPyAutoGUIと組み合わせて、操作対象のウィンドウを特定・準備するために使われます。
import pygetwindow as gw
import pyautogui
import time
try:
# 1. PyGetWindowで対象ウィンドウを取得・準備
target_title = "無題 - メモ帳" # 例
win = gw.getWindowsWithTitle(target_title)[0]
if not win.isActive:
win.activate()
time.sleep(0.5)
if win.isMinimized:
win.restore()
time.sleep(0.5)
# ウィンドウを指定位置・サイズにする (操作しやすくするため)
win.resizeTo(800, 600)
win.moveTo(100, 100)
time.sleep(0.5)
# 2. PyAutoGUIでウィンドウ内の操作
# (ウィンドウが(100, 100)にある前提で相対座標を使うなど)
window_region = (win.left, win.top, win.width, win.height)
# 例: ウィンドウの中央をクリック
center_x = win.left + win.width // 2
center_y = win.top + win.height // 2
print(f"ウィンドウ中央 ({center_x}, {center_y}) をクリックします。")
pyautogui.click(center_x, center_y)
time.sleep(1)
# 例: ウィンドウ内に文字を入力
print("文字を入力します...")
pyautogui.typewrite("Hello from PyAutoGUI using PyGetWindow!", interval=0.05)
time.sleep(1)
# 例: ウィンドウ内の特定領域のスクリーンショット (pyautoguiの機能)
# screenshot_path = "notepad_content.png"
# pyautogui.screenshot(screenshot_path, region=window_region)
# print(f"スクリーンショットを {screenshot_path} に保存しました。")
print("自動操作完了✅")
except IndexError:
print(f"エラー: タイトル '{target_title}' のウィンドウが見つかりません。")
except Exception as e:
print(f"エラーが発生しました: {e}")
まとめ 📜
PyGetWindowは、Pythonでデスクトップウィンドウを扱うためのシンプルかつ強力なライブラリです。
メリット 👍
- 簡単なAPI: 直感的な関数とメソッドで、ウィンドウ情報の取得や操作が容易に行えます。
- クロスプラットフォーム: Windows, macOS, Linux (X11) で動作するため、多くの環境で利用可能です。
- 軽量: 複雑な依存関係が少なく、導入が容易です。
- 連携性: PyAutoGUIなどの他のGUIオートメーションライブラリと組み合わせることで、より高度な自動化が実現できます。
デメリット / 注意点 👎
- 機能の限界: ウィンドウ内のボタンやテキストフィールドなどのコントロールを直接操作する機能はありません。
- プラットフォーム差: OSによる挙動の違いや、必要なパーミッション、特定の環境(Waylandなど)での非互換性が存在します。
- 開発の状況: 元々の開発者による更新は緩やかになっている可能性がありますが、コミュニティによる貢献やPyWinCtlのような派生プロジェクトが存在します。
- エラーの可能性: 対象ウィンドウが存在しない場合やOSの制限により、操作が失敗することがあるため、適切なエラーハンドリングが重要です。
PyGetWindowを使いこなせば、 répétitive なウィンドウ操作の自動化、マルチタスク環境でのウィンドウ整理、テスト自動化の補助など、様々な場面で作業効率を向上させることができます。ぜひ、このライブラリを活用して、あなたのPythonプログラミングの幅を広げてみてください!🚀
コメント