Windowsのタスクバーでもネコ走らせてみた🐈

# はじめに

![RunCat](https://kyome.io/runcat/images/demo.gif)

みなさまはMacのメニューバーでネコを飼うRunCatというアプリをご存知でしょうか？CPU負荷に合わせて走る速度の変わるネコをメニューバー上に表示するだけというしょうもないアプリですが、現在では世界累計45,000ダウンロードを突破し、多くのみなさまに可愛がってもらえる定番アプリとなりつつあります。はじめは悪戯心で作成したジョークアプリが、思いもよらず高評価をいただけており大変嬉しいです。

一方で、Windows版がほしいとの声もちらほら聞こえるようになり、Windows版の模倣アプリも作られつつあることを知りました。そこで、ちゃんと本家からWindows版も出したいなぁと思い立ったが吉日ということで、格安でThinkPadを仕入れてWindowsアプリ開発に初挑戦してみました。

# 成果物

**RunCat for Windows**
Windowsのタスクバー上に常駐し、CPU負荷に応じてネコが走るだけの最小限機能を搭載したアプリを開発しました。GitHub上にソースコードを公開しており、アプリそのもの(RunCat.exe)もここ経由でダウンロード可能にしています。→[Kyome22/RunCat_for_windows](https://github.com/Kyome22/RunCat_for_windows)（Releasesのページにexeがあります。）

![RunCat for Windows](https://kyome.io/runcat/images/runcat_for_windows_demo.gif)

# 初めてのWindowsマシン

- ThinkPad E495（14-inchノート）
- OS: Windows 10 Home 64
- メモリ: 16GB
- プロセッサ: Ryzen 5 3500U 2.1G 4C MB
- SSD: 256GB 
- キーボード: US配列カスタマイズ
- 価格: ￥77,550

メインマシンとして使うつもりはなく、小さいWindows向けアプリを開発できる程度にVisual Studioが動けばOKという気持ちでスペック決めをしました。このくらいのスペックのマシンが8万円弱で手に入ったのは上々ではないでしょうか。（調べてみたらMacBook Airだとメモリ8GB、SSDが256でも10万円オーバーなんですね。）

ただ、仕方がないことなのですが、メインマシンと同じUS配列ではあるものの、キー配置がかなり違っていて入力ミスが多いです。とりわけControlキーとS, Z, X, C, Vキーあたりの配置がかなり厄介者でMacで慣れた手つきでショートカットを入力すると、一つずれたコマンドを叩いてしまうんですよね。慣れるまで我慢...

# 初めてのVisual Studio開発

今回はVisual Studio 2019 (コミュニティ）を使いました。
Windows向けのアプリ開発プロジェクトの種類

- Windows フォームアプリケーション
- WPF: Windows Presentation Foundation アプリ
- UWP: Universal Windows Platform 

このような感じで何種類かあり、よく耳にする.NET Coreや.NET Frameworkなどフレームワークにより微妙に種類が違ったりするようですが、よくわからなかったので、タスクバーのアイコンについて検索した時に出てきたNotifyIconというキーワードを頼りに、タスクバーだけのアプリが作れるプロジェクト形式ということでWindows フォームアプリケーションを選択しました。

- [How can I make a .NET Windows Forms application that only runs in the System Tray?](https://stackoverflow.com/questions/995195/how-can-i-make-a-net-windows-forms-application-that-only-runs-in-the-system-tra)
- [Creating a Tasktray Application](https://www.codeproject.com/Articles/18683/Creating-a-Tasktray-Application)

Visual Studioでのアプリ開発について、基本的にXcodeでのアプリ開発と流れは変わらないので、あまり難しくは感じませんでした。強いて言えば画像や色などのリソース管理について、XcodeのAssets CatalogとVisual StudioのProperties-Resourcesは使い勝手が結構異なるので、とっつきづらかったですね。ただ、Propertiesで管理することで`Resources.リソース名`のようにリソースをコード上で扱うことができるようになるので、ここは便利ですね。（Android開発での`R.リソースキー名`に似ている）

# 開発ハマりポイント

最初に挑戦するアプリとしてはRunCatはかなり特殊なところをついているので、案の定いくつかハマってしまいました。

## なんか参考文献が古い！

`c# NotifyIcon size`とか`c# cpu usage`とか`c# detect dark theme`とかのキーワードで調べていくんですが、大体行き着く文献が2011年や2007年とかかなり古い記事ばかりなんですよね。おそらくWindows フォームアプリケーションが古い形式なのがいけないのでしょうが、信憑性がよくわからないし、今(Windows 10)でも通じる技術なのかもよくわからない感じで裏を取るのが結構大変でした。

## icoってなんや

Windowsアプリではアイコンを扱う時はicoという拡張子のものを使うんですが、こいつが曲者で、一つのファイルで複数のサイズの画像を取り扱えるやつなんですね。Webだとファビコンの拡張子がicoなんですが、これを自作するのがmacだと厄介で、Preview.appで書き出す時にoptionキーを押しながら形式を選択しようとすると`Microsoft アイコン`というのが選択できてicoで出力できるのですが、複数サイズに対応できるわけではありません。また、PhotoShopを使ってもプラグインを入れなければicoは取り扱えません。あまり編集/書き出し環境が整っていない形式がなぜ普及しているのかよくわかりません。

## どう頑張っても16×16pxの制限を突破できない

上にあげた成果物のGIFアニメーションをみていただければ分かる通り、タスクバー上のネコの解像度はかなり悪いです。それもそのはず、たったの16×16pxしか与えられていないのですから、ネコの輪郭がかなり潰れてしまいました。一応環境設定でUIを125%に拡大するオプションをオンにすれば、32×32pxのリソースが採用されるようですが、それでもかなり狭いです。まず、正方形でなければならないというのがかなり苦しい。NotifyIconに表示できる画像はicoなので、自ずと正方形になるのですが、横幅をもう少しくれれば...ぐぬぬという気持ちです。macOS版の方は正方形の縛りがなく、Retinaディスプレイならば56×36px、それ以下の解像度のディスプレイでも28×18pxが使えるのでかなりましですね。

## ダークテーマかどうかの取得方法が煩雑

昨今のダークモードの流行りを取り入れたのかはわかりませんが、最近のWindowsではダークテーマというのを環境設定で選択できるようです。しかし、昔からあった仕様ではなく、Windows フォームアプリケーションの方はフレームワークがあまりアップデートされていないため、現在のテーマがダークテーマかどうかを取得する簡単な方法はないようです。どっかの設定に書き込まれている情報を直接読み取って、現在どちらのテーマに属しているのか判断するというのが苦肉の策としてあるようです。

- [Win32アプリケーションでWindows 10のライト/ダークモードを検出する方法](https://www.it-swarm.dev/ja/windows/win32%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A7windows-10%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%88%E3%83%80%E3%83%BC%E3%82%AF%E3%83%A2%E3%83%BC%E3%83%89%E3%82%92%E6%A4%9C%E5%87%BA%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/806091802/)
- [C# Windows10のダークモードいいっすね！アプリケーションのダークモード設定を取得してみましょ！ヽ(*^｡^*)ノ](https://nln.jp/wp/archives/5653)
- [ユーザー設定の変更をイベントで受け取る](http://dobon.net/vb/dotnet/system/userpreferencechanged.html)

## Win32とUWPの違いが一見わからん

いろいろ実装を試しながら文献を調査していると、`Microsoft.Win32`由来のクラスやメソッドを使っている実装例と、`UWP`由来のクラスやメソッドを使っている実装例が、文献によって入り乱れていることに気付きました。「よっしゃ、これなら簡単に実装できそう」と思ったらUWP由来で今回は使えない...というのが何回もあって萎えました...

## EventHandlerを直呼びするのどうやるの？

RunCatは5秒に一回CPU負荷を取得してネコの走る速さを更新するのですが、そこでTimerを使っています。しかし、Swiftの`Timer.fire()`のようにTimerに指定した処理を即時発火するメソッドが用意されていないので、直接処理をコールしたくなりました。そこで、Timerに登録するEventHandler用のメソッドは、引数に`(object sender, EventArgs e)`を持つので引数が用意できず直接叩けないじゃんという状態になったのですが、メソッド内で`sender`も`e`も使っていないならば、適当に引数詰めてコールすれば良いようです。

```c#:例
private void ObserveCPUTick(object sender, EventArgs e)
{
    // 処理
}
// 直接叩く
ObserveCPUTick(null, EventArgs.Empty);
```

# 所感

まぁいろいろ書きましたが、現状のものはVisual Studioを初めて起動してからだいたい3日くらいで実装できたので、[mac版のときの躓き](https://qiita.com/Kyome/items/b85e7cfc2716a6f880ee)に比べたらかなり楽な勝負でしたね（Storeでのリリースもないし）。特に、CPU負荷取得するのが簡単すぎて逆に驚きました。たった数行でこういうシステム情報が取ってこれるところがWindowsアプリの良さかもしれません。
 
最小限機能のみの状態で、まだまだ改善の余地はありますが、とりあえず成果物を出すことができて満足ですね。今後はオープンソースの強みを活かして、みなさまのIssueやプルリクを待ってみようと思います。