おはようございます。最近子供達はライフル銃のような物を紙工作しています。@kjunichiです。
背景
gopheronでraspberry piのCPU温度の表示が既に出来ていた。
ふと昔のQiitaの投稿
を見つけてから、Swiftが出てからこの辺りのAPI何か変化ないか調べたら、 特に変化はないものの、
を見つけた。
これで、これまでのRaspberry Piと同様のgRPCのクラサバ方式で gopheronでCPU温度を表示できるはず。
メインの処理はすぐ出来た
gopheron-raspberrypi同様に外部コマンドを起動して結果を取得するだけなので、
ファミレスでドヤリングしてmacOSのCPU温度を表示するサーバ、クライアントの開発がほぼできたw
— kjunichi (@kjunichi) 2018年7月21日
RaspberryPi向けのアイコンをmacOSっぽいのに変えないとなぁを pic.twitter.com/3LrDZc6uQg
というツイートの通り、ファミレスドヤリング駆動であっさり実装できた。
Macらしいアイコンを表示したい!
macOSと分かるアイコンを表示したいと思い、これを実現するのが 険しい道のりだった。
もっとも、アイコンデータを吸い出して、Raspberry PiのCPU温度の表示のするクライアントと同様に クライアント側にアイコンデータを埋め込めば全然難易度低いが、 Githubに公開して場合にアイコンのライセンスが非常に気になる。
何は無くともアイコンデータ
以下の記事に助けられ、
/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/FinderIcon.icns
のファインダーのアイコンを使えば良さそうだと発見。
このアイコンの画像データをgRPCでクライアント側にimgタグにData URI形式して埋め込んで、 クライアント側にHTMLデータとして送信して、gopheron側で、HTMLをレンダリングすれば、 macOSを示すアイコンが表示されるはず。
icnsファイルをどうやって読み込むか
libicnsなるUNIX系なライブラリを発見するも、ハタと考えた。 今回はgRPCサーバ側の処理はmacOS限定なので、Cocoa APIで手軽にできそうなら そちらで実装した方がスッキリ出来そう。
cgoからCocoa APIを使う
数年前に、Cocoa APIをcgoで使う以下の記事を読んでいたので、Objective-Cと連携させれば出来そうな事が分かってはいたが、 今回改めて調べたら、以下が見つかった。
http://unknownplace.org/archives/cgo-and-eventloop.html
ポイントは、CFLAGSでの指定と、LDFLAGSで依存するフレームワークを指定するところ。
#cgo CFLAGS: -x objective-c #cgo LDFLAGS: -framework Foundation -framework AppKit
Cocoa APIの冒険
ここで、一旦Cocoa APIを使ってicnsファイルを読み込んで、PNG画像データをData URI形式の 文字列を返却するObcjective-Cのコードを書いておく。
icnsファイルをCocoa APIで読み込むには
を見つけたが、試行錯誤したものの、どうもダメ。 以下のコードが自分の環境では動いた。
NSBitmapImageRepにimageRepsWithContentsOfFileなるファイルパスを指定すると配列に読み込んでくれるメソッド?が 見つかった。
配列からは、本来は、使いたい画像サイズから適切な画像要素を取得するべきなのだろうが、メンドくさいので、 2番目を決め打ちで指定している。
NSString *path = @"/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/FinderIcon.icns"; NSArray * imageReps = [NSBitmapImageRep imageRepsWithContentsOfFile:path]; NSImageRep *imageRep = [imageReps objectAtIndex: 1];
PNGのバイナリデータを取得するには
NSBitmapImageRep型になっていれば、そこから指定した画像フォーマットを取得するのは簡単だった。 でもpropertiesを明示的に指定しないと落ちたのでこの点は要注意。
NSData *data = [imageRep representationUsingType:NSPNGFileType properties : nil];
NSData型のデータをBase64エンコードするには
base64EncodedStringWithOptionsを使えば出来る。昔はbase64EncodedStringもあったが、現在は base64EncodedStringWithOptionsが推奨されてる模様。
NSString *pngstr = [data base64EncodedStringWithOptions:0 ];
NSString型をchar*型に変換するには
cgoでGo側に連携するためにC.String型に変換する必要がある。 以下のコードで行えた。
int len = [pngstr length]; char *buf = (char*)malloc(sizeof(char)*(len+1)); strcpy(buf, [pngstr UTF8String]);
cgoでObjective-Cの結果を扱う
Objective-Cで作った関数を呼ぶには
CやC++同様にC.関数名で呼び出せる。
tmpStr := C.getFinderIconStr()
cgoでCString型をGoのstring型に変換するには
pngDataUrl := C.GoString(tmpStr)
cgoでCStringの領域を解放するには
C.free(unsafe.Pointer(tmpStr))
成果物
動かし方
macOS側で
go get -u github.com/kjunichi/gopheron-macos cd $GOPATH/src/github.com/kjunichi/gopheron-macos go run server/main.go
gopheronを動かすPCで
go get -u github.com/kjunichi/gopheron cd $GOPATH/src/github.com/kjunichi/gopheron go build gopheron.go ./gopheron & node macosClient.js hostname.local
まとめ
Cocoa APIを調べるとiOS専用っぽいAPIが出てきたり、SwiftのAPIだったり、cgoで使える型式で すぐには見つからないこともあるようだ。
今回はgoroutineを使ってないから、あんまり問題なさそうだが、Cocoa APIを特定のスレッドから 呼び出す必要があったりする模様。
参考資料
関連記事
- Raspberry Piの温度をgRPCを使ってElectronで作ったデスクトップマスコットに表示させた
- DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition insteadの警告を消すには
- gRPCでmojaveに勝利してリモートからWebカメラを利用した