Windows のクリップボードの話をしよう
Ctrl + C
初めに
エンジニアであろうとなかろうと PC を触ったことがある人ならばクリップボードを使ったことがない人はいないのではないだろうか。
ここでは何気なく使っているクリップボードの話をする。
ただし残念ながら Mac は持っていないし、実機で用意できる Linux はその辺に転がっていないこと、私が Windows を信じているため内容は Windows のクリップボードの話になる。OS によって異なるかもしれない。
Web サービスにて
VSCode で書かれたテキストをコピーして Google Docs に張り付けたところ以下のような状態になる。
ただテキストを貼り付けたかっただけなのに生憎私の VSCode のテーマはダークになっているためその背景色、それどころかテキストカラーまでペーストされてしまう。
これを回避するために私は一旦 notepad.exe に張り付けてから Google Docs などに張り付けたりしている。
どうしてこのようなことになっているのか?
Clipboard
Windows では Clipboard を Data Exchange (データ交換) という部類に含めている。要するに、別々のアプリケーション間でデータをやり取りするための仕組みである。そのため Clipboard にデータを登録する場合などは GlobalAlloc
関数などという複数のアプリケーションからアクセスできるメモリ空間にデータを保存する必要が出てきたりするのだが保存することについてはあまり触れない。
どうしてあのようなペーストが行われたのか
結論から言うと クリップボードは何気なく1つテキストやファイルなどを Ctrl + C した時に1つだけでなく複数の情報を一気に保存している。
簡単に Windows API でその情報を取得してみよう。 Microsoft が公式の Windows API の Rust のラッパークレートを出してくれているのでこちらを使ってみる。
基本的に windows クレートの関数は unsafe である。また Windows.h がかなりのヘッダーをひとまとまりにしたものであることからも想像は付くがクレートにはとても多くの feature があり、必要な feature を調べては追加しなければならない。ここでは以下の3つの feature を有効化する。
cargo add windows --features="Win32_System_DataExchange,Win32_System_Ole,Win32_System_Memory"
EnumClipboardFormats
関数を使って Clipboard に保存されたキーの種類を取得する。この関数を使う前に OpenClipboard
関数を使っておく必要がある。
この関数はオリジナルでは UINT (32bit) の値を返すはずだが Rust のクレートでは u16 であることからするとおそらく歴史的な経緯で上位 16bit は使われないのだろう。ここで取得できる値は定義済みのクリップボード形式とアプリケーション固有のカスタム名を設定したクリップボード形式が存在する。カスタムなクリップボード形式についてはその名前を別途 GetClipboardFormatName
で取得することができる。
以下は notepad.exe のテキストがクリップボードにある状態で実行したものである。
cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
Running `target\debug\sample.exe`
CF_UNICODETEXT
CF_LOCALE
CF_TEXT
CF_OEMTEXT
- CF_UNICODETEXT
- CF_LOCALE
- CF_TEXT
- CF_OEMTEXT
このようにテキスト1つ保存したつもりが様々なフォーマットでテキストが保存されている。
ちなみに CF_TEXT
は日本語ならば CP932 (Windows の Shift-JIS) で保存されていて、 CF_UNICODETEXT
は Windows でいう UNICODE なので UTF-16 で保存されている。
いずれにしても Rust からは少し弄りにくいフォーマットである。
このように保存するタイミングで複数のクリップボード形式で保存され、また受け取り側も自分が読み込める形式で書かれているであろうクリップボード形式を読み込むようになっている。
VSCode から Google Docs に張られたもの
冒頭で問題提起として上げた VSCode のクリップボード、あれは何が入っているか
先のコードで出力すると以下のようなものが出力される。
- HTML Format
- CF_UNICODETEXT
- Chromium Web Custom MIME Data Format
- Chromium internal source URL
- CF_LOCALE
- CF_TEXT
- CF_OEMTEXT
それっぽいものがいそう。 そういえば VSCode は Electron 製。実質 Chrome である。
Chrome と名のついたカスタムなフォーマット形式もあるし、何より HTML Format
がそのもののオーラを放っている。
こちらを GetClipboardData
と GlobalLock
関数で捕まえて中身を覗いてみる。
このクリップボード形式の中身は UTF-8 で保存されている。とても Rust に優しい。
中身は
Version:0.9
StartHTML:0000000105
EndHTML:0000000542
StartFragment:0000000141
EndFragment:0000000506
<html>
<body>
<!--StartFragment--><div style="color: #cccccc;background-color: #1f1f1f;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #dcdcaa;">println!</span><span style="color: #cccccc;">(</span><span style="color: #ce9178;">"Hello, world!"</span><span style="color: #cccccc;">);</span></div></div><!--EndFragment-->
</body>
</html>
となっている。
CF
で始まってはいないことからもわかるが本当はクリップボード形式の部類としてはカスタムなクリップボード形式であるがほぼデフォルト化しているようで Microsoft のリファレンスについてもこの形式に言及している。
とりあえずはブラウザのテキストなどをコピーしたときにはこのデータを入れてくれるようで、対応したアプリケーションであればそのフォーマットをもとにアプリケーション側でも再現してくれる。
例えば <table> な HTML の要素を保存すれば Microsoft Word でテーブルとして表示をしてくれる。
HTML Text
に限らず Excel と PowerPoint 間や Adobe Illustrator と Adobe PhotoShop 間といったアプリケーション同士も独自のカスタムクリップボード形式で連携を行うことでアプリケーションを跨いで様々な情報をやり取りすることができる。
まとめ
普段何気なく使っている Clipboard という機能について少し踏み込んでみてみた。
流石にネイティブアプリケーションは書かないという人でも Google Drive はブラウザのタブ間でコピーを実現したりと結構クリップボードを使っておもしろい機能を実装している。
これを機にクリップボードやネイティブアプリケーションに興味を持っていただけたら幸いである。
Discussion