STREAM DECK互換の仮想デバイスの紹介

この記事は [Wano Group Advent Calendar 2022](https://qiita.com/advent-calendar/2022/wano-group)の24日目の記事となります。
4日目[Product-Led Growthまとめ](https://ktat.hatenadiary.jp/entry/2022/12/04/171031)と25日目[Notionとtandemで作る相談しやすいリモートワーク開発のすすめ](https://qiita.com/ktat/items/f333d4519aca21238dff)にも記事を書いているので、そちらもどうぞ。
また、グループ会社の[EDOCODE](https://www.edocode.co.jp/)(メインで仕事してます)の[Advent Calendar](https://adventar.org/calendars/7645) もありますので、よろしければそちらもどうぞ。

## この記事に書いていること

個人的に開発している[STREAM DECK](https://www.elgato.com/ja/stream-deck-mk2)互換の仮想ボタンデバイスの[MyDeck](https://github.com/ktat/mydeck/)について書きます。

## MyDeck とは？

[STREAM DECK](https://www.elgato.com/ja/stream-deck-mk2)と互換性のある仮想ボタンデバイスです。実物のSTREAM DECKデバイスをLinuxから使うのが不便だったので作り始めたのですが、実物がなくても開発したいとか、ブラウザでエミュレートできれば、色々面白いんじゃないか？
というところで、もともと、`MyStreamDeck`という名前で作っていましたが、`MyDeck`と変更しました。

デバイスを操作するライブラリはすでにある[python-elgato-streamdeck](https://github.com/abcminiuser/python-elgato-streamdeck)を使っているので、それをラッピングするような形で作っています。

なお、まだまだ荒削りなところが多いです。というのと、Pythonはそんなにわかっていません。

### MyDeckの特徴

* STREAM DECKのデバイス制御を行う[Python実装](https://github.com/abcminiuser/python-elgato-streamdeck)にラッピングする形で、仮想Deck及び物理STREAM DECKにアクセスできる
* 言語はPython。仮想Deckの実装はVue.js
* Python のスレッドでアプリケーションやゲームを動かすことが可能

ブラウザ上で動くものなので、スマートフォンのブラウザ上に表示しておけば、STREAME DECKを買わなくてもそれっぽく使えなくはないです。PC上のデスクトップの端っこに置いて便利ショートカットとして使うのもありかもしれません(Electronアプリでも作れば良いのかも)。

### MyDeckの仮想Deckでできること

* ボタンのPush(クリック)
* ボタンへの画像表示
* 右クリックでボタン設定

### 課題

* メモリリークしてるっぽい...
* 色々やってるうちにキーの反応が悪くなった...(↑が原因かも)

というのは、調査中です。

### MyDeckのデモ

どんな感じで動くのデモです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/Zb31Jcr9Y3g" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## MyDeckの動作フロー

ざっくりなデバイスを動かすまでのフロー。

```plantuml
@startuml
MyDecks-->start_decks()
-->MyDecksManager
note right: 物理デバイスと\n仮想デバイスの管理
-->物理デバイス読み込み
-->python-elgato-streamdeck の\nStreamDeck のインスタンス作成(deck)
--> 物理デバイスと仮想デバイスを配列に入れる
MyDecksManager-->仮想Deckの設定読み取り
--> VirtualDeck作成(deck)
--> 物理デバイスと仮想デバイスを配列に入れる
-> 配列から一つずつforで回す
-> deck.open()
--> MyDeck作成(deckを渡す)
--> mydeck.init_deck()
note right: 設定ファイルの反映\nアプリの起動(スレッド)
--> mydeck.key_setup()
note left: ボタンの画像や\nコールバックを設定する
-.-> 配列から一つずつforで回す
@enduml
```

### 仮想デバイスについて

仮想デバイスは、python-elgato-streamdeck の `StreamDeck`クラスと(だいたい)互換性がある`VirutalDeck`のインスタンスです。

## MyDeckのアプリの仕組み

MyDeckではボタンに表示するアプリを動かす仕組みがあります。
`app_*`から始まるソースコードにアプリのコードがあります。

### アプリが動く仕組み

ざっくりとこんな感じで動きます。

```plantuml
@startuml
(*)-->deck.open()
--> MyDeck作成(deckを渡す)
--> mydeck.init_deck()
--> mydeck.config = MyDeckCofnig(設定ファイル)
--> mydeck.config.reflect_config()
note right: 設定ファイルの反映
--> mydeck.threading_apps()
note right: アプリの起動
--> mydeck.key_setup()
note right: ボタンの画像や\nコールバックを設定する
@enduml
```

### 簡単なアプリの例

デジタル時計をボタン上に表示するアプリの例です。

```python
from PIL import Image, ImageDraw, ImageFont
from mydeck import ThreadAppBase, ImageOrFile
import datetime

# whole image size
X: int = 100
Y: int = 100

class AppClockDigital(ThreadAppBase):
    def set_image_to_key(self, key: int, page: str):
        now = datetime.datetime.now()
        im = Image.new('RGB', (X, Y), (0, 0, 0))
        font = ImageFont.truetype(self.mydeck.font_path, 25)
        draw = ImageDraw.Draw(im)
        time_text = "{0:02d}:{1:02d}:{2:02d}".format(now.hour, now.minute, now.second)
        draw.text((2, 33), font=font, text=time_text, fill="white")

        self.mydeck.update_key_image(
            key,
            self.mydeck.render_key_image(
                ImageOrFile(im),
                "",
                'black',
                True,
            )
        )
```

## おしまい

ということで、MyDeckのご紹介でした。実際のスースコードは、[GitHub](https://github.com/ktat/mydeck/)を参照してください。

ちなみに、これを開発するために、STREAM DECKを2台買いました(1台はメルカリですが)。そして、最近、[STREAM DECK +](https://www.elgato.com/ja/stream-deck-plus)が出ましたね。さすがに買わないと思うので、対応はしないかも。

## 人材募集

現在、Wanoグループでは人材募集をしています。興味のある方は下記を参照してください。
[JOBS | Wano Group](https://group.wano.co.jp/jobs/)

EDOCODEについては、以下をご参照ください。
[Jobs at EDOCODE](https://go.edocode.co.jp/jobs)