プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

C++でタスクトレイに格納するアプリケーションを作る

やりたいこと

 別に立派なGUIが作りたいんじゃない。作りたいのは、コンソールアプリで十分だ。
 しかし、そのコンソールアプリが常に何かを出力し続けていたら?ちょっとモード変更したいとか、そのための入力処理を作るのも億劫だし、そもそも流れ続けるコンソールに入力したくもない。

 画面出力も特になくて、機能的には終了させるコマンドを受け付けるだけで十分だったら?コマンドプロンプトの為にタスクバーの領域一個潰したくない。終了機能以外、バックグラウンド化したい。


 これって、タスクトレイをコンソールアプリからちょいと弄れば、話は簡単じゃないか。

 しかし、そんなことをC++でやろうとすると、途端に非常に面倒くさいことが要求される。Windowsならメッセージを受け取る為の見えないウィンドウを作り、Shell_NotifyIconでNOTIFYICONDATAを登録し…。

 違う。違うんだ、私がやりたいのはそんなことじゃないんだ。ボタン押したら対応するイベントハンドラが発火して、後はチェックボックスぐらいがあれば、それで良いんだ。

 

zserge/tray


 WINAPIを叩いたとしても、まぁ、数百行あればかけるっちゃかけるだろう。
 でも私はWin APIを叩きたくないのだ。何故なら私はGUIを作りたい訳じゃないからだ。そこは本質ではないのだ。情けないことを言えば、ぶっちゃけWinAPIは得意ではないのだ。
 かといって、コンソールアプリを作ってるのに、Qtの出る幕は無い。

 なのに、「C++ タスクトレイ ライブラリ」で検索しても、くそ真面目なWinAPIの解説記事ばかり。日本語はホント使えねぇな!


 というわけで、早々に日本語検索は諦めて、Google.comで「C++ system tray library」を検索すると二つ程ヒット。
 斜め読みしてみて、私のニーズに完全にマッチしたのが、今回紹介するzserge/trayです。

github.com

 C++でタスクトレイに格納したアプリケーションを、ヘッダファイル1つをインクルードするだけで簡単に作れてしまうライブラリ。リンクは不要。しかもクラスプラットフォームだ。
 私はC++で使ったが、Cでも使える。(というか、C用だから、C++用も欲しいな)

 


 最後にコードは載せておくが、永遠と「Hoge Hoge」と言い続けるが、タスクトレイから「Piyo Piyo」に変更も出来て、終了も出来るプログラムがさっくり57行で出来た。ワーオ。

f:id:nodamushi:20180917020301p:plain


 理解するべきはtrayとtray_menu構造体、後はtray_init,tray_update,tray_loop,tray_exit関数だ。わー楽だ。

tray構造体

 タスクトレイの情報。メニューとアイコンを設定する。Windowsでもアイコンってpngでいけるんやね。

static tray tray ={
  (char*)"icon.png",
  (tray_menu[]){
    {nullptr}      //最後の要素。必須。
  }
};

tray_menu構造体

 タスクトレイのメニュー。入れ子構造に出来たり、Check、Disable/Enableも設定出来る。textが"-"だとセパレータになる。

要素 説明
char* text メニュー名。"-"だとセパレータ
int disabled 有効無効の設定
int checked チェックの設定
void (*cb)(struct tray_menu*) コールバック関数。
void *context お好きな様にお使い下さい。
tray_menu* submenu サブメニュー

関数

  • tray_init(tray*): 初期化する。1が返ると失敗
  • tray_update(tray*): disabledとか、checkedとかを変更した時に呼び出して、画面を更新
  • tray_loop(int) : UIのループ。引数はブロッキングするかどうか。tray_exitが呼ばれると1が返る。
  • tray_exit() : UIループを終了する

サンプルコード

//↓ LinuxならTRAY_APPINDICATOR、MacならTRAY_APPKIT。Makefileに書いとくといい。
#define TRAY_WINAPI 1
#include "tray.h"
#include <iostream>
#include <thread>
#include <chrono>

using namespace std;
using namespace std::chrono;

volatile bool piyoMode;    //!< Piyo Piyoとコンソールに出力するモード
volatile bool processDone; //!< 処理完了フラグ
void piyo(tray_menu *);    //!< Piyoモードのトグル
void finish(tray_menu *);  //!< 終了処理
void _main();              //!< メイン処理
tray tray ={        //!< タスクトレイの設定
  (char*)"icon.png",
  (tray_menu[]){
    {(char*)"piyoMode",0,0,piyo}, //サブメニューを指定すれば、階層化も可能
    {(char*)"-"},                 // セパレータ
    {(char*)"exit",0,0,finish},
    {nullptr}                     //最後の要素。必須。
  }
};


int main(int argc, char *argv[]){
  piyoMode=processDone=false;
  thread t(_main);//メイン処理は別スレッド化
  tray_init(&tray);
  while(tray_loop(1) == 0);
  t.join();
  return 0;
}

void piyo(tray_menu * item){
  bool b =!item->checked;
  piyoMode = b;
  item->checked = b; //チェックボックスの表示非表示変更
  tray_update(&tray); //更新
}

void finish(tray_menu *){
  processDone=true;
  tray_exit();
}

void _main(){
  while(!processDone){
    cout << (piyoMode? "Piyo Piyo" : "Hoge Hoge") << endl; // piyoかhogeを出力
    this_thread::sleep_for(seconds(2)); //2秒スリープ
  }
  cout << "Done"<<endl;
}