この記事の主役は、「古いUSBスキャナーを、今のPCから使えない問題」をどうにかしようというWeb appです。
最近のパソコンは、昔の周辺機器に冷たいです。とくにスキャナーは、メーカーのドライバが古かったり、そもそもOSが変わってもう動かなかったりして、押し入れに眠りがち。
この記事の作者も、まさにそんな状況に遭遇していたようです。
きっかけは、もともと両親や義理の両親のために古いCanon製プリンターを使えるようにした printervention.app。その開発中に、「そういえば、棚に古いCanonのUSBスキャナーもあるじゃん」と思い出した、と。
この流れ、すごくわかります。ひとつ困りごとを解決すると、**“あ、これもいけるのでは?”** と芋づる式に発想が広がるんですよね。技術好きあるあるです。
ブラウザの中に仮想マシンを作り、その中でLinuxとスキャナー用ソフトを動かしている、というのが大枠です。
かなり荒っぽく見えますが、これがこのアプリの面白いところ。
普通は「ブラウザでスキャンしたい」となると、Web APIで直接スキャナーにアクセスする方法を考えそうです。ですがこの作者は、もっと大胆に “スキャナーを動かすための環境ごとブラウザに持ち込む” 方向を選んでいます。
これは発想としてかなり好きです。
「ブラウザに不足しているなら、ブラウザの中に必要なものを全部入れてしまえ」という、ある種の力業ですが、ちゃんと成立しているのがすごい。
この仕組みの核は v86 です。
v86は、x86 CPUだけでなく、その周辺を含めた“パソコン一式”をブラウザ内でエミュレートする技術です。
エミュレートとは、ざっくり言えば別の機械をソフトウェアでまねすること。
つまり、古いPCをブラウザの中で再現してしまうようなものです。
しかも v86 は、機械語を WebAssembly に実行時変換するので、ただの「遅い仮想PC」ではありません。
ここは地味に重要で、ブラウザ内でそれなりの速度を出せる理由になっています。もちろん本物のネイティブ実行には及ばないでしょうが、動かすこと自体が大変な領域なので、これはかなり立派です。
作者は、v86 の仮想マシンの中で Alpine Linux を動かし、その上で SANE (Scanner Access Now Easy) を実行しています。
SANEは、Linux系でよく使われるスキャナー用のソフトウェア基盤です。
名前はちょっとふざけている感じですが、役割は真面目。スキャナーを扱うための共通インターフェースのようなものだと思えばよいです。
つまり、流れとしてはこんな感じです。
この構成は、素朴に言えばかなり重そうです。
でも、「古い機器を今のブラウザで使いたい」という目的に対しては、かなり筋が通っています。古い周辺機器問題の本質って、実はハードよりソフトウェア互換性の断絶にあるので、こういう“環境ごと持ち込む”アプローチは合理的だと思います。
では、ブラウザの世界と、仮想マシン内のSANEはどうやって会話しているのでしょうか。
ここで登場するのが、作者いわく Claudeの助けを借りて書いた小さなCプログラムです。
このプログラムが、
という役割を担っています。
JSONは、データを扱いやすい形で表すテキスト形式です。
つまり、スキャナーの設定を「ブラウザ側でUIを作るために読みやすい形」にして渡しているわけです。
ここも面白いポイントです。
単にスキャンできればいいのではなく、設定UIまで自動で組み立てられるようにしているのが、かなりちゃんとした作りです。古い機器をただ「動く」だけで終わらせず、使いやすさまで考えているのが好印象でした。
ブラウザ側では、受け取ったスキャンデータを次のように扱っています。
<canvas> に描画する
<canvas> は、ブラウザ上で画像や図形を描ける領域です。この構成もなかなかスマートです。
プレビューでは軽く描画し、保存時にはちゃんと圧縮する。処理を役割分担しているわけです。
個人的には、こういう**“見せる処理”と“保存する処理”を分ける設計**はかなり好きです。雑に全部メインスレッドでやると、ブラウザはすぐもっさりしますからね。
このアプリの真骨頂は、やはり USB側の接続です。
作者はすでに printervention.app で USB 周りを動かしていたそうで、そこからの応用になっています。
流れはこうです。
USB/IP は、USB通信をネットワーク越しにやり取りする仕組みです。
ざっくり言うと、USBデータをTCPパケットに包んで送る技術。
SANEから見ると、まるで普通のUSBスキャナーにつながっているように見えます。
でも実際には、USB通信がネットワークっぽく運ばれている。ここがトリックです。
次に、JavaScript側では tcpip.js が動きます。
これは lwIP をWebAssemblyにコンパイルして使っているそうです。
lwIPは軽量なTCP/IPスタック、つまりネットワーク通信を扱うための基本ソフトです。
v86の仮想マシンには仮想ネットワークカードがありますが、ブラウザに届くのは生のEthernet frameです。
ところがブラウザには本物のネットワークスタックがないので、EthernetレベルのデータをTCP/IPに戻す役が必要になります。そこで tcpip.js が働く、というわけです。
この部分はかなり玄人向けですが、要するに
「ブラウザはネットワークっぽい生データを受け取るだけなので、足りないTCP/IPの部分を自前で補っている」
と考えるとわかりやすいです。
再構成された USB/IP パケットは、最終的に WebUSB API を使って、あなたのパソコンのUSBポートへ流されます。
WebUSBは、ブラウザからUSBデバイスにアクセスするためのAPIです。
つまり、ここでようやくブラウザが、現実世界のUSBスキャナーとつながるわけです。
ここまで来ると、かなり壮観です。
ブラウザ → 仮想マシン → Linux → SANE → USB/IP → WebUSB → 実機スキャナー
という長い道のりを通って、ようやく1枚のスキャン画像が出てくる。
普通なら「もっと素直な方法はないの?」と思うかもしれませんが、古い機器を相手にすると、素直な方法が存在しないことも多いです。だからこそ、この迂回路は妙に説得力があります。
記事では、作者の CanoScan LiDE 100 でテストしたと書かれています。
ただし、それだけでなく、数百種類のモデルで動く可能性があるとも述べています。対象メーカーには、
などが挙げられています。
ただし、これは実際に全部で動作確認したという意味ではありません。
あくまでSANEとUSB周りの互換性から見て、対応範囲が広そうだという話です。ここは期待しすぎず、でも夢はある、という距離感がちょうどいいと思います。
作者は、まだコードの大部分をオープンソース化していないと、わざわざ謝っています。
これは少し残念ですが、実際にはこういう高度な個人開発では珍しくもないです。
全部公開するには整備が大変ですし、そもそも試行錯誤の途中で作られた部分も多いはず。
とはいえ、こういう面白い仕組みこそ公開されると他の人が応用できるので、将来的に何かしらの形で広がるといいな、とは思います。
個人的にこの記事でいちばん面白かったのは、ブラウザに機能を足すのではなく、必要な環境ごと持ってくるという割り切りです。
普通は「Webアプリでスキャナー制御したい」となると、ブラウザAPIの制約とにらめっこしながら小さく戦います。
でもこの作者は、v86、Linux、SANE、USB/IP、WebUSBを組み合わせて、かなり大きな解を作っています。
もちろん、これは万人向けの設計ではありません。
でも、**“動くこと”を最優先にして、既存資産を救う**という意味では、とても強いアプローチです。
古い機器って、捨てるのは簡単だけど、使えたら十分価値があることも多いですからね。そういう意味で、このアプリはかなりロマンがあります。
Yes We Scan は、古いUSBスキャナーをブラウザから使えるようにするための、かなり野心的なWeb appです。
v86で仮想マシンを動かし、その中でLinuxとSANEを使い、USB/IPやWebUSBまでつないでしまう。やっていることは相当複雑ですが、目的はとてもシンプルです。
「もう使えないと思っていたスキャナーを、また使えるようにする」
たったそれだけのために、ここまでやる。
その執念がまず面白いし、技術の力で“古いものにもう一度命を吹き込む”感じも気持ちいい。そんな記事でした。