Pythonでホットキーを取得する方法|pywin32とwindowprocを使った画面外からのショートカットキー取得

Python
この記事は約15分で読めます。
スポンサーリンク

おひさしぶりです。
今回は、PythonでWindowsのショートカットキーを実行画面以外から取得する方法について紹介します。
たとえば、Ctrl+Cを入力するとテキストがコピーされますが、そういったイメージで違うアプリにフォーカスが当たっていても、実行したPythonアプリでキー入力を取得できるようになります。

ホットキーとは

まず、今回のように画面外にいてもキー入力を取得できる機能のことホットキーを言います。

正確には、特定のキーとウィンドウ(アプリ)でWindowに対して登録しておくと、どのような画面にいてもそのアプリでのキー入力をキャンセルし、登録したウィンドウのWM_HOTKEYとして通知されてくるものです。
このホットキーはショートカットキーとは別ものとなります。ショートカットキーはフォーカスが当たっているアプリ内でのみ動作するキー入力イベントのことを言います。

ライブラリのインストール

これで、これから何をしたいのかをひとまず理解いただけたかと思います。
ではこのプログラムを作るにあたって、Windowsから通知を受け取るために1つライブラリをインストールする必要がありますのでそれについて紹介します。

今回インストールするライブラリは、pywin32というものです。
windowsでアプリの開発をしていた方は聞いたことがあると思いますが、そうですこれはwin32apiを使用するためのライブラリとなります。

インストールはいつも通りコマンドプロンプトを起動して、以下のコマンドを実行すればインストールできます。

pip install pywin32

以下のような感じでインストールできます。

プログラム全体

ここまでで、プログラミングをするための準備は完了しました。
まずは、いつも通り今回作成したプログラムの全体を記載します。

このプログラムでは、「Ctrl+Shift+A」をクリックするとアプリが終了するように組んであります。

import threading 
from ctypes import *          # windll 使用のため
from ctypes.wintypes import * # handle取得のため
import win32api,win32gui,win32con # win32のAPI使用のため

#ホットキーIDは このプログラムで使用するための物なので適当な値を設定する
#0x0000-0xBFFFの間で設定
HOTKEY_ID_1 = 0x0001
exit_flg = False

def Regist_selfhotkey():
    global exit_flg
    try:
        hInstance  = win32api.GetModuleHandle(None)
        CLASS_NAME   = "myAppl"
        lpWindowName = CLASS_NAME
        
        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = WindowProc
        wc.hInstance = hInstance
        wc.lpszClassName = CLASS_NAME
        win32gui.RegisterClass(wc)
        
        hwnd = win32gui.CreateWindowEx(0,CLASS_NAME,lpWindowName,win32con.WS_OVERLAPPEDWINDOW,
                                       win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT,
                                       win32con.CW_USEDEFAULT,None,None,hInstance,None)
        
        if hwnd != None:
            windll.user32.RegisterHotKey(hwnd,HOTKEY_ID_1,win32con.MOD_CONTROL | win32con.MOD_SHIFT,0x41)

        msg = MSG()
        while (windll.user32.GetMessageW(pointer(msg),0,0,0)):
            windll.user32.TranslateMessage(pointer(msg))
            windll.user32.DispatchMessageW(pointer(msg))
            if exit_flg == True:
                break
    finally:
        windll.user32.UnregisterHotKey(None,HOTKEY_ID_1)    
    

def WindowProc(hwnd,uMsg,wParam,lPram):
    global exit_flg
    if uMsg == win32con.WM_HOTKEY:
        if wParam == HOTKEY_ID_1:
            print("receive Ctrl Shift A")
            exit_flg = True
    return windll.user32.DefWindowProcW(hwnd,uMsg,wParam,lPram)

def main():
    Regist_selfhotkey()
    

if __name__ == '__main__':
    main()

実行結果は以下の通りで、「Ctrl+Shift+A」が入力入力されると”receive Ctrl Shift A”と出力してアプリが終了しています。

続いてはプログラムの解説ですが、解説は次ページにて記載します。

プログラム解説

ライブラリのインポート

今回のプログラムに必要なライブラリは以下の3つです。
ここは以下をインポートするだけですので、特に説明はありません。

from ctypes import *          # windll 使用のため
from ctypes.wintypes import * # handle取得のため
import win32api,win32gui,win32con # win32のAPI使用のため

ホットキーの登録

では続いて今回の主題となりホットキーの登録処理について解説します。

#ホットキーIDは このプログラムで使用するための物なので適当な値を設定する
#0x0000-0xBFFFの間で設定
HOTKEY_ID_1 = 0x0001

def Regist_selfhotkey():
    global exit_flg
    try:
        hInstance  = win32api.GetModuleHandle(None)
        CLASS_NAME   = "myAppl"
        lpWindowName = CLASS_NAME
        
        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = WindowProc
        wc.hInstance = hInstance
        wc.lpszClassName = CLASS_NAME
        win32gui.RegisterClass(wc)
        
        hwnd = win32gui.CreateWindowEx(0,CLASS_NAME,lpWindowName,win32con.WS_OVERLAPPEDWINDOW,
                  win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT,
                  win32con.CW_USEDEFAULT,None,None,hInstance,None)
        
        if hwnd != None:
            windll.user32.RegisterHotKey(hwnd,HOTKEY_ID_1,
                                        win32con.MOD_CONTROL | win32con.MOD_SHIFT,0x41)

        msg = MSG()
        while (windll.user32.GetMessageW(pointer(msg),0,0,0)):
            windll.user32.TranslateMessage(pointer(msg))
            windll.user32.DispatchMessageW(pointer(msg))
            if exit_flg == True:
                break
    finally:
        windll.user32.UnregisterHotKey(None,HOTKEY_ID_1) 

まずは、ホットキーが発生した際に受信するメッセージで、登録したものかどうか判断するために必要なIDを決めます。
以下がそれに該当します。この値は、0x0000~0xBFFFの間であれば任意の値を設定すればいいようです。

HOTKEY_ID_1 = 0x0001

続いて、自分のアプリの名前の登録が必要となりますのでその名前を決めます。
以下が該当し、好きに決めてください。

CLASS_NAME   = "myAppl"

ここまで決めればあとは、以下のような流れでキーを登録していきます。

  1. アプリのインスタンスを取得
  2. ウィンドウクラスを生成して必要な設定を行う
  3. ウィンドウハンドルを生成
  4. ウィンドウハンドルにホットキーを登録

まず 1 アプリのインスタンスを取得 です。

以下のようにwin32apiのGetModuleHandleを呼び出しし、インスタンスを取得します。

hInstance  = win32api.GetModuleHandle(None)

続いて、2 ウィンドウクラスを生成して必要な設定を実施です。

まず、win32gui.WNDCLASS()でインスタンスを生成し、続いて必要な設定を行います。
lpfnWndProc には、ホットキーが発生したときのコールバックを設定します。
hInstance やlpszClassName には、事前に取得したアプリのインスタンスと事前に決めたアプリ名を登録します。
そして最後にwin32guiのRegisterClassを呼び出しクラスを登録します。

        wc = win32gui.WNDCLASS()
        wc.lpfnWndProc = WindowProc
        wc.hInstance = hInstance
        wc.lpszClassName = CLASS_NAME
        win32gui.RegisterClass(wc)

続いて、ウィンドウハンドルを生成します。

以下がそれに該当します。
ここで指定している引数については、別途win32apiを調べてみてください。

        hwnd = win32gui.CreateWindowEx(0,CLASS_NAME,lpWindowName,win32con.WS_OVERLAPPEDWINDOW,
                  win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT,
                  win32con.CW_USEDEFAULT,None,None,hInstance,None)

最後に、以下でホットキーを登録します。

windll.user32のRegisterHotKeyを呼び出しキーを登録します。
第1引数には、先ほど生成したウィンドウハンドルを指定、第2引数にはどのキーなのかを識別するためのIDを指定、第3・4引数には、キーを指定します。

MOD_CONTROL -> Ctrlキー
MOD_SHIFT -> Shiftキー
第4引数の0x41というので、Aキーを指定しています。
この第4引数はアスキーコードで0-9もしくは、A-Zを設定すればOKです。

 windll.user32.RegisterHotKey(hwnd,HOTKEY_ID_1,
                                        win32con.MOD_CONTROL | win32con.MOD_SHIFT,0x41)

メッセージの取得処理

そして、メッセージを取得するために以下を継続してメッセージ受信と解析・分配処理を呼び続ける必要があります。

以下がそれに該当します。
はじめのmsg = MSG()とい部分は、処理するためのポインタを取得しています。

 msg = MSG()
        while (windll.user32.GetMessageW(pointer(msg),0,0,0)):
            windll.user32.TranslateMessage(pointer(msg))
            windll.user32.DispatchMessageW(pointer(msg))
            if exit_flg == True:
                break

GetMessageWで、メッセージの取得をします。これは呼び出すとキー入力があるまではここで待機する同期関数になります。
TranslateMessageで解釈・DispatchMessageWで分配といった流れになります。
最後にexit_flgがTrueになるとLoopから抜ける処理を入れてあります。
※exit_flgは、登録したホットキーが入力されると、Trueになるようにしてあります。

コールバック処理

最後にキーの入力イベントを披露コールバック関数です。
以下が該当の関数です。
WindowProcの引数については、Win32の関数と同じものになりますので、そちらを参照ください。
この処理では、単純にイベントがWM_HOTKEYの時に、HOTKEY_ID_1の入力の場合に、プリント出力をして、フラグを立てるだけの処理を行っています。
最後に、DefWindowProcWを呼び出しして標準の処理も行うようにしている形となります。

def WindowProc(hwnd,uMsg,wParam,lPram):
    global exit_flg
    if uMsg == win32con.WM_HOTKEY:
        if wParam == HOTKEY_ID_1:
            print("receive Ctrl Shift A")
            exit_flg = True
    return windll.user32.DefWindowProcW(hwnd,uMsg,wParam,lPram)

まとめ

いかがだったでしょうか?
.netでアプリを作成する際と同じような処理でWin32 の処理が使用できることが理解いただけたと思います。
たったこれだけの処理が、アプリの外部からキーイベント取得できますので、とても便利なのではないかと思います。(あまり使用することもないのかもしれませんが・・・)

ただ、.netで作成するときも同じだったと思いますが、Winメッセージを取得するのにループ処理で取得処理を呼び出し続けないといけないので、このメッセージ取得は別でスレッド等を立ち上げて呼び出しする必要があるかと思いますので、ご使用の際はそのあたりを考慮に入れておいてください。

ちなみに、スレッドの使い方が分からないという方は以下でマルチスレッドの使用方法を記載していますので、参考にしてみてください。

最後に、Pythonの基礎を学びたい方は以下がおすすめです。私も持っていてたまに眺めて勉強していますものですのでぜひ購入して学習してみてください。

コメント

タイトルとURLをコピーしました