コピペってなんだろう?Clipboard編
最近は専らwebのリッチテキストエディタの開発をしている@iricoです。
そんな折にあることに気づいてしまいました。
「コピペって奥が深い…!」
ということで、コピペ、つまりcopy and pasteについて詳しく知ろうというのが今記事の目的です。
ブラウザへのcopy and pasteは大まかな流れは以下です。
①Excelなどの各アプリケーションデータや画像データをCtrl+C
でコピーし、
②Ctrl+V
でブラウザにペーストし、
③そのデータをアプリケーション側で取り扱う
これら全てを扱うと膨大になってしまうため、今記事ではOSのClipboardを中心に①について解説していきます。
Clipboard
Clipboardの実態は、複数のアプリケーションからアクセス可能な共有メモリのことです。
Clipboardといえばcopy and pasteというイメージも強いですが、共有メモリとして他にも様々な使われ方をしています。
- スクリーンショットの一時保存
- ドラッグ&ドロップ
- ユニバーサルクリップボード/クラウドクリップボードなどの共有機能
- QRコードの読み取り
などなど…
また、OSによって細かい差はあるものの、おおよそ下記のように複数のデータタイプを保持しています。
これによってクリップボードデータにアクセスするアプリケーション側でどのデータタイプを扱うかを選択することができ、柔軟なデータ表現を可能にしています。
ちなみに実際のクリップボードのデータ取得は非同期のインターフェースになっていることが多いです。
これは全てのデータ形式をあらかじめ生成しておくのが非効率なため、特定のデータ形式がリクエストされてからそのデータを生成しているからです。
クリップボードはOSの機能であるため、当然OSごとに独自の機能や形式などがあります。
各OSのクリップボードについて詳しくみていきましょう。
MacOS
MacOSではNSPasteboardというIFを介してClipboardを操作します。
NSPasteboardの各データはNSPasteboard Itemと呼ばれ、各データ表現はUTI(Uniform Type Identifiers)と呼ばれる形式で表現されます。
これはMIMEタイプの表現揺れ(.jpg/jpeg)やOSTypeなどの他の表現をまとめて一意に表すためのApple独自の概念です。
UTIはMIMEやOSTypeと完全に互換性がある設計になっています。
This string identifier is fully compatible with any of the older tagging methods, and you can call utility functions to translate from one to the other. That is, for a given UTI, you can generate the equivalent OSType, MIME type, or filename extension, and vice versa.
※https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PasteboardGuide106 より引用
UTIは下記のように逆DNS形式で命名されています。
public.html
com.apple.pict
public.jpeg
dyn.ah62d4rv4gu8y6y4grf0gn5xbrzw1gydcr7u1e3cytf2gn
トップドメインは次の三種類です。
public.~~ | com.~~ | dyn.~~ |
---|---|---|
public識別子と呼ばれる Appleのみが定義可能 | 動的識別子 | 第三者が定義可能 |
動的識別子とは、未知のMIMEタイプやOSTypeのデータをUTIでラップした概念です。
これによってUTIが未定義なデータに当たった場合でもUTIとして扱えるようになります。
UTIの特徴的な点として、conformance hierarchyと呼ばれるオブジェクト指向のクラス階層に近い宣言を採用していることです。
※https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PasteboardGuide106 より引用
上の図で言うと、public.htmlはpublic.textに適合するため、public.textとしても扱える訳です。
これによって、例えば独自のUTIのデータが貼り付けられ、それがアプリケーション側で解釈できない場合、上位の階層のデータとして扱うことができます。
注意点として、逆DNSの命名とconformance hierarchyの階層は一致していません。
命名はあくまで一意性の表現のために使用されています。
実際のクリップボードのtypeを取得するmacOSアプリを実装しました。
Windows
Clipboard操作のために、GetClipboardDataやSetClipboardDataというWindows APIが用意されています。
クリップボードの形式にはいくつか種類があります。
- 標準のクリップボード形式 ・・・システム側で定義されている形式
- 登録済みクリップボード形式・・・アプリケーション側が登録する独自の形式
- プライベート クリップボード形式・・・特定のアプリケーション内でのみ扱いたい独自の形式
また、アプリケーションがクリップボードに存在しない形式を要求した場合に対応できるよう、合成クリップボード形式という仕組みが採用されています。
例えばクリップボードの形式がCF_UNICODETEXT
のみでアプリケーションがCF_TEXT
を要求した場合、そのデータを暗黙にCF_UNICODETEXT
に変換します。
この組み合わせは事前に決められており、その変換テーブルに従ってデータ処理が行われます。
※https://learn.microsoft.com/ja-jp/windows/win32/dataxchg/clipboard-formats より引用
こちらもクリップボード内部の形式を取得するプログラムを作成しました。
プログラムを起動するとクリップボードのタイプが出力されます。
わかりやすくするためにAPI側がCF_XX
とは別の文字列に変換していますが、これらの実体はCF_XX
です。
X Window System(Unix/Linuxなど)
X Window Systemには、実は三種類のクリップボードがあります。
Primary
・・・マウスで現在選択している範囲を貼り付けるためのクリップボード
Secondary
・・・歴史的に存在し今はほとんど使われていないクリップボード
Clipboard
・・・GUIのコピーアンドペーストでやり取りをするクリップボード
Primary
の機能はユニークですね。
Primary
のデータは通常のクリップボードデータと互換性がないため、実装で取り扱う場合は注意が必要です。
X Window Systemのクリップボードに関する詳しい仕様書はこちらです。
iOS
MacOSではNSPasteboard
クラスを介してクリップボードを操作しましたが、iOSの場合はUIPasteboardクラスを介して操作します。
データ表現型はNSPasteboard
の時と同じくUTIを利用します。
Android
AndroidはシステムクリップボードをClipboardManagerというクラスで表現しています。
クリップボードのデータの一単位をClipDataオブジェクトで表現し、中にClipDescriptionとClipData.Itemが一つ以上含まれています。
ClipDescriptionには利用可能なデータ表現のMIMEタイプの配列が含まれています。
また、ClipData.Itemにはテキスト/URI/インテントのいずれかのデータが含まれています。
インテントを使うと、アプリのショートカット情報をクリップボードにコピーできます。
※https://developer.android.com/develop/ui/views/touch-and-input/copy-paste?hl=ja より抜粋
まとめ
一言でコピーすると言っても、OS毎にデータの持ち方に様々な差があることがわかりました。
ペーストされた側のアプリケーションは個別にそれらに対応する必要があり、私が感じた「コピペって奥が深い…!」ということの片鱗を味わっていただけたのではないでしょうか。
次回はブラウザ側で実際にどのようにデータを変換しているのかを見て行きたいと思います。
参考文献
Discussion
web だと クリップボード(※主に貼り付け) は DataTransfer で やりとりしますね。
とありますが、コピー元のアプリケーションが終了した後もコピーしたデータを貼り付けできるのはどのような仕組みで実現されているのでしょうか?
共有メモリにデータが保存されているので、アプリを閉じてもアクセスできます。
非同期で実行しているのは、恐らく実際のデータの組み立て部分だと思われます。
例えばjpegでしたら圧縮されてるので復号を行ったり、求められているデータ形式と異なる形式のbufferの場合は変換処理を挟む必要があります。
(生のbufferを覗いたわけではないので私の推測が入っています🙇♀️)