ブラウザでQMK Firmwareをコンパイルした
この記事は「キーボード #2 Advent Calendar 2025」の14日目の記事です。
はじめに
この記事を3行でまとめると、以下のようになります。
- Webページ内でQMK Firmwareをコンパイルできるようにしてみました。できたもの
- ただし、起動からコンパイル完了までに3分以上かかります。
- みんなも「俺が考えた最強のキーボードカスタマイズ環境」を作ろう!
モチベーション
自作キーボードの世界で広く使われているQMK Firmwareですが、そのコンパイル環境を整えるのは多くの人にとって悩みの種です。
- QMK MSYSの環境問題: Windowsユーザー向けの公式ツールQMK MSYSは、何かの拍子に環境が壊れてしまったり、古いバージョンが自動でアップデートされなかったりと、トラブルの報告をしばしば見かけます。
- 環境汚染への抵抗: そもそも、キーボードファームウェアのビルドのためだけに、PCに専用のツールチェインをインストールしたくない、という方もいるでしょう。
- Python環境の壁: QMKのビルドはPythonにも依存しており、そのバージョン管理も考慮が必要です。
- サポートの難しさ: 自分で環境をセットアップするのも大変ですが、初心者にセットアップ方法を教えたり、エラーのトラブルシューティングを手伝うために、全く同じ環境を用意するのはさらに困難を極めます。
これらの課題を解決するために、GitHub ActionsのようなCIサービスや、QMK公式が提供するQMK ConfiguratorのようなSaaSも存在しますが、誰かの善意やフリーミアムに依存するため、サービスの継続性など、別の観点での懸念もあります。
そこで私は「ブラウザでページを開いたら、すぐにQMK Firmwareがコンパイルできる環境があれば最高なのでは?」と思いました。
作ったもの
その思想のもとに作ったのが、今回紹介するブラウザ完結型のQMKビルドシステムです。 今回は、Meishi2キーボードを対象デバイスとして作ってみています。 システムは以下の3つのモジュールで構成されています。
1. keymap.c生成ページ

まずはキーマップを定義するためのページです。ここでは例としてMeishi2キーボードをターゲットにし、クリックやドラッグといった直感的な操作で簡易的なkeymap.cを生成できるようにしました。完成したら、次のコンパイラページへ移動します。
実際に作った物をここで試せます: https://stirring-twilight-89db84.netlify.app/qmk-in-browser/keymapgen/
2. コンパイラページ

このシステムの心臓部です。前のページで生成されたkeymap.cのソースコードを受け取り、ブラウザ内でQMK Firmwareのビルドを実行します。完了すると、書き込み用の.hexファイルが生成され、最後の書き込みページに渡されます。
3. 書き込みページ

最後に、生成されたファームウェアをキーボードに書き込みます。このページは、せきごん氏が開発した「Pro Micro Web Updater」をベースにしており、WebSerial経由でキーボードへの書き込みが可能です。オリジナルのプロジェクトではローカルの.hexファイルをアップロードする必要がありましたが、今回はコンパイラページから直接データを受け取れるように改造しました。
コンパイラページの作り方
このプロジェクトの最も面白い部分である、コンパイラページの仕組みについて詳しく解説します。
使用技術:Docker + container2wasm
鍵となる技術はDockerとcontainer2wasmです。
- Docker: アプリケーションの実行環境をコンテナとしてパッケージングする技術。
- WebAssembly (Wasm): Webブラウザ上でバイナリコードを高速に実行するための技術。
- container2wasm: DockerコンテナをWasmに変換してくれる夢のようなツール。
今回は、QMK Firmwareのコンパイル環境をDockerコンテナとして構築し、それをcontainer2wasmでWasmに変換することで、ブラウザ内でのコンパイルを実現しました。
実装ステップ
プロジェクト関連ファイルはこちらに置いてあります。
1. Dockerfileの準備
QMKのビルド環境を定義するDockerfileを作成します。いくつか工夫した点があります。
- 公式Dockerfileは使わない: QMK公式のDockerfileは多機能な分サイズが大きいため、今回は使用しません。
- alpine linuxベースで軽量化: ベースイメージには軽量な
alpine linuxを採用し、最終的なWasmファイルのサイズを少しでも小さくする努力をしました。 - 不要なソースの削除: ターゲットキーボード(今回はMeishi2)以外のソースコードを削除し、コンテナイメージをスリム化します。
- 事前コンパイル:
keymap.c以外の、変更頻度の低いソースコードはDockerfileのビルド時点で一度コンパイルしておくことで、ブラウザ上での実行時間を短縮します。
2. エントリーポイント build.sh の作成
Dockerコンテナの起動時に実行されるシェルスクリプトbuild.shを用意します。このスクリプトの役割は非常にシンプルです。
- 標準入力から
keymap.cのソースコードを受け取る。 - 受け取った
keymap.cを使ってQMK Firmwareをコンパイルする。 - 完成した
.hexファイルの内容を標準出力に出力する。
3. container2wasm でWasmに変換
container2wasmを使って、作成したDockerコンテナをWasmファイルに変換します。
container2wasmは内部でCPUエミュレータを実行しており、デフォルトではbochsが使われます。高速化を期待してqemuを使うオプションも試してみましたが、私の環境では逆にbochsより倍近く時間がかかってしまいました。理由は今のところ不明です。
4. HTML/JavaScriptの連携
container2wasmは、変換後のWasmを実行するためのサンプルHTMLを用意してくれています。このサンプルはxterm.jsとxterm-ptyを利用しており、Wasmの標準入出力をブラウザ上のターミナルに表示する仕組みになっています。
xterm.js: コンテナの入出力を画面に表示するターミナルUIライブラリ。xterm-pty: Wasmとxterm.jsを繋ぎ、擬似端末(pseudo-terminal)として機能させるライブラリ。
今回は、このxterm-ptyにフックを仕掛けることで、Wasmプロセスとの対話を実現しました。
- Wasmの起動ログを監視し、
build.shが実行可能になるのを待つ。 keymap.cのデータを標準入力に送信する。- ビルドの完了を待ち、標準出力に出てくる
.hexファイルのデータを取り出す。
パフォーマンスについて
夢のような環境ですが、大きな課題がパフォーマンスです。
- Wasmファイルのサイズ: 生成されたWasmファイルは非圧縮で約500MB、gzip圧縮しても160MBにもなります。利用者は全員これをダウンロードする必要があり、決して快適とは言えません。
- コンパイル速度:
Chrome -> Wasm -> Bochs(CPUエミュレータ) -> Linux -> Python -> GCCという非常に深いスタックでコンパイルが実行されるため、凄まじく時間がかかります。手元のマシンでは2秒で終わるコンパイルが、このシステムでは3分30秒もかかりました。
インストール不要で一度だけコンパイルしたい、という用途にはギリギリ競争力があるかもしれませんが、キーマップを何度も調整するような継続的な開発には全く向いていません。
他の手法との比較
最後に、既存のQMKカスタマイズ手法と今回の試みを比較してみます。
| 手法 | メリット | デメリット |
|---|---|---|
| QMK MSYS | デフォルトの選択肢。ローカルで高速。 | セットアップが手間。環境が壊れやすい。Windows限定。 |
| VIA/REMAP | 最も手軽。ブラウザで完結。 | keymap.cの全機能は使えない。対応ファームウェアが必要。 |
| QMK Configurator | 公式サービス。ブラウザで完結。 | keymap.cの全機能は使えない。QMK APIサーバーに依存。 |
| GitHub Actions | フル機能を使える。複数人での管理が容易。 | Git/GitHubの知識が必要。フリーミアム枠への依存。 |
| qmk2wasm (今回) | インストール不要。ブラウザで完結。keymap.cの全機能が使える(理論上)。 |
とにかく遅い。巨大なWasmファイルのダウンロードが必要。 |
まとめ
今回は、Webページ内でQMK Firmwareをコンパイルする事に挑戦しました。
パフォーマンスという大きな壁はありますが、「インストール不要で誰でもすぐに使えるビルド環境」という思想は、QMKのすそ野を広げる上で一つの可能性を示せたのではないかと思います。
皆さんも「俺が考えた最強のキーボードカスタマイズ環境」を作りましょう。
Happy Hacking!
この記事は3mk2023で書きました。