タスクトレイアイコンのクリックをエミュレートする

Svitchのタスクトレイアイコン対応がロードマップに入っているので、そろそろ実装しようと思って技術的な問題を調べてみました。

  1. タスクトレイに登録されているアイコンとウィンドウを列挙する
  2. タスクトレイアイコンへのクリックをエミュレートする

どちらもXPにおいて「アクティブでないインジケータを隠す」設定で隠れているタスクトレイアイコンも含めています。逆に、そのアイコンが現在表示されているかどうかは把握できません。
1番は実はSvitch開発の最初の問題だった「タスクバー上のウィンドウを列挙する」と同じ方法で解決できていて*1、Svitchに同梱してあるenum_taskbar32.exeを実行することでタスクトレイ上のウィンドウを取得することが出来ます。使用方法はコマンドプロンプトから「enum_tasukbar32.exe tray」。


2番目がこのエントリ本題なのですが、別に難しいことはなく1番で取得したウィンドウに対してWM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUPメッセージを送ってあげれば良いだけです。
ただ、普通にマウスメッセージを送るわけではないので、事前に自身でタスクトレイにアイコンを登録して、メッセージを処理する方法を知っておくのが良いでしょう。以下のサイトの内容が把握できれば後は簡単です。
WILL - タスクトレイに常駐するアプリを作る


enum_taskbar32.exeを実行するとタスクトレイ上の各アイコンについて、次のような情報を取得できます。

// タイトル
AppTitle = ハードウェアの安全な取り外し
// ウィンドウハンドル
HWND = 131602
// 表示している領域
RECT = 0,54,18,72,
// 各アプリにおけるタスクトレイアイコンのID
uID = 1226
// メインウィンドウに通知する際のメッセージ
uCallbackMessage = 1226
// アイコンのハンドル
hIcon = 60269

この中で、今回使用するのは次の三つの情報です。HWND、uID、uCallbackMessage。

では、タスクトレイの概要は上のサイトで把握できたということでサンプルです。Python(+win32all)で書いていますが、各APIの呼び方はc言語と変わらないのでわかると思います。

import win32api, win32gui
from win32con import WM_RBUTTONDOWN, WM_RBUTTONUP
from win32api import PostMessage

def tasktray_right_click(hwnd, callback, uid):
  # タスクトレイ上のウィンドウをアクティブにする
  # アクティブにしないとメニューをキーボードで操作できない
  win32gui.SetForegroundWindow(hwnd)

  # 右ボタンdownメッセージを送る
  PostMessage(hwnd, callback, uid, WM_RBUTTONDOWN)

  # 右ボタンupメッセージを送る
  PostMessage(hwnd, callback, uid, WM_RBUTTONUP)

PostMessageの引数は次のようになっていますが、タスクトレイアイコンにメッセージを処理させるときはMsgにuCallbackMessageを、wParamにuIDを、lParamに送りたいメッセージを指定します。

BOOL PostMessage(
  HWND hWnd,      // ポスト先ウィンドウのハンドル
  UINT Msg,       // メッセージ
  WPARAM wParam,  // メッセージの最初のパラメータ
  LPARAM lParam   // メッセージの 2 番目のパラメータ
);

初めはwParamは0でいいやと送っていたのですが、一部のウィンドウで反応が無かったので、uIDが必要なことに気づきました。(一つのアプリが複数のアイコンをタスクトレイに登録しているときはuIDで区別してメッセージを処理する必要があるから)


ちなみに、この方法で適切なメッセージを送るとメニューを表示することが出来ますが、表示される位置が「現在のマウスカーソルがある位置」になります。PostMessageでは位置を指定することができないので、任意の場所に表示したいときは事前にマウスカーソルを無理矢理動かしておくしか無いかも。

*1:どちらもツールバーなので、中身の取り出し方は同じ