セレクション
クライアント間の通信手段として、X には「セレクション」という概念があります。
セレクションには、所有者 (ウィンドウ) が存在し、一つのセレクションには、常に一つの所有者 (または所有者なし) がいます。
任意のセレクションから、任意のデータを取得したいクライアントは、現在のセレクション所有者に対して、欲しいデータのタイプを示すターゲットを指定して、データを要求します。
データは、ウィンドウのプロパティを介することで、取得できます。
セレクションは、主にクリップボードや D&D で使用します。
セレクションには、所有者 (ウィンドウ) が存在し、一つのセレクションには、常に一つの所有者 (または所有者なし) がいます。
任意のセレクションから、任意のデータを取得したいクライアントは、現在のセレクション所有者に対して、欲しいデータのタイプを示すターゲットを指定して、データを要求します。
データは、ウィンドウのプロパティを介することで、取得できます。
- 各セレクションは、アトムで識別されます。
- データを所持するクライアントは、XSetSelectionOwner 関数で、セレクションの所有権を取得します。
- セレクションからデータが欲しいクライアントは、XConvertSelection 関数で、現在のセレクション所有者に対してデータの変換を要求します。
その後、現在のセレクション所有者であるクライアントが、SelectionRequest イベントを受け取ります。 - セレクションの所有者は、SelectionRequest イベントで渡された値を元に、自身が所持しているデータを、要求されたターゲットのデータに変換して、要求者によって指定されたウィンドウの指定プロパティに、データをセットします。
その後、XSendEvent 関数で、要求者に対して SelectionNotify イベントを送信することで、データがセットされたことを通知します。 - データの要求者は、SelectionNotify イベントを受け取った時に、XGetWindowProperty 関数を使って、自身のウィンドウのプロパティから、データを読み込みます。
セレクションは、主にクリップボードや D&D で使用します。
クリップボード
クリップボードは、セレクションを使うことで実装することができます。
ICCCM では、PRIMARY, SECONDARY, CLIPBOARD の3つの標準セレクションが定義されているので、それを使います。
ICCCM では、PRIMARY, SECONDARY, CLIPBOARD の3つの標準セレクションが定義されているので、それを使います。
PRIMARY | マウスの中ボタンによる貼り付けで使われます。 範囲が選択された時は常に PRIMARY に内容をセットし、貼り付ける時は中ボタンを押します。 |
---|---|
SECONDARY | 基本的に使用しません |
CLIPBOARD | 通常のクリップボードとして扱い、ユーザーがコピー/貼り付けを行うことで操作します。 |
- コピーコマンドが存在しない場合は PRIMARY のみを使用し、CLIPBOARD は使わない。
- マウスの中ボタンでは PRIMARY のデータを貼り付ける必要があり、CLIPBOARD は使わない。
- 切り取り/コピーコマンドは、常に CLIPBOARD に対して、現在選択されているデータを設定する。
- 切り取り/コピーコマンドは、選択範囲がない場合でも、常に CLIPBOARD と PRIMARY の両方を設定する必要がある。
- 貼り付けコマンドでは、PRIMARY ではなく、CLIPBOARD のデータを貼り付ける必要がある。
- 選択範囲が解除された場合、PRIMARY のデータはそのままにすること。
(以前のデータをそのまま残して、貼り付けられるようにする)
クライアントの終了時
X のクリップボードは、データを所持するクライアントと、データを取得するクライアント間で、直接データをやりとりする形の実装となるため、クリップボードデータを所持しているクライアントが終了した場合、現在のクリップボードの内容は消滅する形になります。
これを解決するためには、クライアントの終了時に、クリップボードを管理しているクライアントにデータを渡して、所有権を引き継いでもらうか、クリップボードの内容を常に監視して収集するようなアプリケーションを使う必要があります。
これを解決するためには、クライアントの終了時に、クリップボードを管理しているクライアントにデータを渡して、所有権を引き継いでもらうか、クリップボードの内容を常に監視して収集するようなアプリケーションを使う必要があります。
セレクション関数
セレクションの所有者の変更
void XSetSelectionOwner(Display *display, Atom selection, Window owner, Time time);
指定したセレクションの所有者を、指定ウィンドウに設定します。
selection | セレクションの名前のアトム |
---|---|
owner | 所有権をセットするウィンドウ。 None で所有者をなしにする。 |
time | タイムスタンプ or CurrentTime |
タイムスタンプが、セレクションの最終変更時刻よりも早い、または現在の X サーバー時刻よりも遅い場合は、何もしません。
新しい所有者 (None 含む) が、現在の所有者と同じではなく、現在の所有者が None でない場合、現在の所有者に対して SelectionClear イベントが送信されます。
所有者のクライアントが終了した、または指定ウィンドウが破棄された場合、所有者は自動的に None になります。
現在の所有者の取得
Window XGetSelectionOwner(Display *display, Atom selection);
指定セレクションの現在の所有者のウィンドウを返します。存在しない場合、None が返ります。
セレクションデータの要求
void XConvertSelection(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time);
指定したセレクションに対して、指定したターゲットへのデータの変換を要求します。
所有者がいる場合、X サーバーはその所有者に SelectionRequest イベントを送信します。
所有者が存在しない場合、X サーバーは、要求元のクライアントに対して、SelectionNotify イベント (property = None) を生成します。
target | 要求するデータのタイプ |
---|---|
property | ウィンドウのプロパティ。 requestor ウィンドウのこのプロパティに対してデータがセットされる。 None の場合、所有者が任意のプロパティを選択する。 |
requestor | 要求者のウィンドウ |
time | タイムスタンプ or CurrentTime |
セレクションイベント
セレクションイベントに関しては、イベントマスクは必要ありません。常にイベントが送られてきます。
セレクションの所有者が、所有権を失った時に来ます。
window は、所有権を失ったウィンドウ。
セレクションの所有者に対して、XConvertSelection() でデータが要求された時に来ます。
所有者は、requestor ウィンドウの property プロパティに、target で示されたタイプでデータをセットした後、XSendEvent() で、requestor ウィンドウに対して SelectionNotify イベントを送信する必要があります。
(データの変換に失敗した場合は、イベントの property を None に設定します。成功しても失敗しても、常に送信する必要があります)
SelectionRequest の property が None の場合は、データをセットするプロパティを所有者が選択して、送信する SelectionNotify イベントの property に、実際にデータをセットしたプロパティを指定する必要があります。
XConvertSelection() 後、そのセレクションのデータ変換が完了した時、または変換に失敗した時に来ます。
セレクションの所有者がいなかった場合や、指定されたターゲットで所有者がデータ変換できなかった場合などは、property = None となり、失敗扱いになります。
property が None でない場合は、ウィンドウのプロパティに、実際にデータがセットされているということなので、requestor ウィンドウの property プロパティから、データを読み込みます。
time は、XConvertSelection() 時に指定されたタイムスタンプが、そのまま渡されます (CurrentTime の場合は 0)。
SelectionClear
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window window; Atom selection; Time time; } XSelectionClearEvent;
セレクションの所有者が、所有権を失った時に来ます。
window は、所有権を失ったウィンドウ。
SelectionRequest
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window owner; //セレクション所有者 Window requestor; Atom selection; Atom target; Atom property; Time time; } XSelectionRequestEvent;
セレクションの所有者に対して、XConvertSelection() でデータが要求された時に来ます。
所有者は、requestor ウィンドウの property プロパティに、target で示されたタイプでデータをセットした後、XSendEvent() で、requestor ウィンドウに対して SelectionNotify イベントを送信する必要があります。
(データの変換に失敗した場合は、イベントの property を None に設定します。成功しても失敗しても、常に送信する必要があります)
SelectionRequest の property が None の場合は、データをセットするプロパティを所有者が選択して、送信する SelectionNotify イベントの property に、実際にデータをセットしたプロパティを指定する必要があります。
SelectionNotify
typedef struct { int type; unsigned long serial; Bool send_event; Display *display; Window requestor; Atom selection; Atom target; Atom property; Time time; } XSelectionEvent;
XConvertSelection() 後、そのセレクションのデータ変換が完了した時、または変換に失敗した時に来ます。
セレクションの所有者がいなかった場合や、指定されたターゲットで所有者がデータ変換できなかった場合などは、property = None となり、失敗扱いになります。
property が None でない場合は、ウィンドウのプロパティに、実際にデータがセットされているということなので、requestor ウィンドウの property プロパティから、データを読み込みます。
time は、XConvertSelection() 時に指定されたタイムスタンプが、そのまま渡されます (CurrentTime の場合は 0)。
セレクションのターゲット
セレクションのデータ要求時に指定するターゲットは、要求するデータのタイプを示します。
セレクション所有者は、少なくとも以下のターゲットをサポートしている必要があります (サポートしていなくても動作する場合はあります)。
セレクション所有者は、少なくとも以下のターゲットをサポートしている必要があります (サポートしていなくても動作する場合はあります)。
TARGETS | (type = "ATOM", format = 32) 変換に成功する (サポートしている) ターゲットのアトムのリスト。 TARGETS も含む、すべてのリストが含まれること。 |
---|---|
MULTIPLE | (type = "ATOM_PAIR", format = 32) 複数のターゲットを一度に取得するための、ターゲットとプロパティのアトムのペアリスト。 |
TIMESTAMP | (type = "INTEGER", format = 32) 所有者が、所有権を取得するために使用したタイムスタンプ。 |
MULTIPLE
MULTIPLE ターゲットは、複数のターゲットを一度に取得したい時に使います。
一度の XConvertSelection() で、複数のプロパティに値を設定することができるので、X サーバーとクライアント間の転送量を減らせる上、同じタイミングで値を取得することができます (複数回データを要求すると、その間に所有者やデータが変わる可能性がある)。
一度の XConvertSelection() で、複数のプロパティに値を設定することができるので、X サーバーとクライアント間の転送量を減らせる上、同じタイミングで値を取得することができます (複数回データを要求すると、その間に所有者やデータが変わる可能性がある)。
- データの要求者は、取得したい実際のターゲットと、そのデータをセットするプロパティの、2つのアトムをペアにした配列を用意し、type = "ATOM_PAIR", format = 32 で、任意のプロパティに設定します。
この時、プロパティの値に None を指定することはできません (所有者が任意のプロパティを選択することはできない)。 - XConvertSelection() で、target = "MULTIPLE", property = アトムペアを設定したプロパティに指定して、セレクションにデータを要求します。
- セレクションの所有者は、SelectionRequest イベントを受け取った時、target = "MULTIPLE"、property != None の場合、指定されたプロパティから、ターゲットとプロパティのリストを読み込んで、それぞれのデータを各プロパティに保存します。
- リスト内の一部のターゲットで変換に失敗した場合、リストが設定されているプロパティ内の、データをセットするプロパティの値が None に置き換えられます。
- すべてのデータのセットが終わると、要求者に対して SelectionNotify イベントが来るので、各プロパティからデータを読み込みます。
プログラム
左クリックで、MULTIPLE ターゲットを使って、CLIPBOARD セレクションの TARGETS, TIMESTAMP を表示します。
その他のボタンで、CLIPBOARD セレクションから UTF8_STRING 文字列を取得して表示します。
閉じるボタンで終了します。
<d17-clipb1.c>
その他のボタンで、CLIPBOARD セレクションから UTF8_STRING 文字列を取得して表示します。
閉じるボタンで終了します。
$ cc -o run d17-clipb1.c util.c -lX11
<d17-clipb1.c>
#include <stdio.h> #include <stdlib.h> #include <X11/Xlib.h> #include <X11/Xatom.h> #include "util.h" enum { _ATOM_CLIPBOARD, _ATOM_MULTIPLE, _ATOM_ATOM_PAIR, _ATOM_MY_SELECTION, _ATOM_TARGETS, _ATOM_TIMESTAMP, _ATOM_UTF8_STRING, _ATOM_NUM }; const char *g_names[] = { "CLIPBOARD", "MULTIPLE", "ATOM_PAIR", "_MY_SELECTION_", "TARGETS", "TIMESTAMP", "UTF8_STRING" }; int main(int argc,char **argv) { Display *disp; Window win; XEvent ev; Atom atoms[_ATOM_NUM],multi[4]; char *str; int size; disp = XOpenDisplay(NULL); if(!disp) return 1; set_display(disp); XInternAtoms(disp, (char **)g_names, _ATOM_NUM, False, atoms); win = create_test_window2(disp, 200, 200, 0, ButtonPress); //イベント XMapWindow(disp, win); while(1) { XNextEvent(disp, &ev); if(event_quit(&ev)) break; switch(ev.type) { case ButtonPress: if(ev.xbutton.button == Button1) { //MULTIPLE 用のアトムペア multi[0] = multi[1] = atoms[_ATOM_TARGETS]; multi[2] = multi[3] = atoms[_ATOM_TIMESTAMP]; XChangeProperty(disp, win, atoms[_ATOM_MY_SELECTION], atoms[_ATOM_ATOM_PAIR], 32, PropModeReplace, (unsigned char *)multi, 4); //MULTIPLE を要求 XConvertSelection(disp, atoms[_ATOM_CLIPBOARD], atoms[_ATOM_MULTIPLE], atoms[_ATOM_MY_SELECTION], win, ev.xbutton.time); } else { //UTF8_STRING を要求 XConvertSelection(disp, atoms[_ATOM_CLIPBOARD], atoms[_ATOM_UTF8_STRING], atoms[_ATOM_MY_SELECTION], win, ev.xbutton.time); } printf("* XConvertSelection: time(0x%lx)\n", ev.xbutton.time); break; case SelectionNotify: printf("[SelectionNotify] requestor(0x%lx) property(%lu) time(0x%lx)\n", ev.xselection.requestor, ev.xselection.property, ev.xselection.time); if(ev.xselection.property == None) break; if(ev.xselection.target == atoms[_ATOM_MULTIPLE]) { //MULTIPLE put_prop_atoms(win, atoms[_ATOM_TARGETS], "TARGETS"); put_prop_num(win, atoms[_ATOM_TIMESTAMP], XA_INTEGER, "TIMESTAMP"); XDeleteProperty(disp, win, atoms[_ATOM_TARGETS]); XDeleteProperty(disp, win, atoms[_ATOM_TIMESTAMP]); XDeleteProperty(disp, win, atoms[_ATOM_MY_SELECTION]); } else if(ev.xselection.target == atoms[_ATOM_UTF8_STRING]) { //UTF8_STRING str = read_prop8(win, atoms[_ATOM_MY_SELECTION], &size); if(str) { printf("--- UTF8_STRING (size:%d) ---\n%s\n-----------\n", size, str); free(str); } XDeleteProperty(disp, win, atoms[_ATOM_MY_SELECTION]); } break; } } XCloseDisplay(disp); return 0; }
解説
MULTIPLE ターゲット
* XConvertSelection: time(0x11d21c5) [SelectionNotify] requestor(0x1c00002) property(665) time(0x11d21c5) <TARGETS> (6) TIMESTAMP, TARGETS, MULTIPLE, SAVE_TARGETS, UTF8_STRING, STRING, <TIMESTAMP> 18493619,
CLIPBOARD セレクションに所有者がいる状態で、MULTIPLE ターゲットを要求した場合、上記のようになります。
UTF8_STRING, STRING のターゲットがあるので、クリップボードに文字列が設定されていることになります。
<TARGETS> (14) TIMESTAMP, TARGETS, MULTIPLE, SAVE_TARGETS, text/html, text/_moz_htmlcontext, text/_moz_htmlinfo, UTF8_STRING, COMPOUND_TEXT, TEXT, STRING, text/plain;charset=utf-8, text/plain, text/x-moz-url-priv,
なお、FireFox で HTML ページのテキストをコピーしてみると、上記のようになります。
通常の文字列に加えて、各 MIME タイプでデータを取得することができます。
<TARGETS> (8) x-special/gnome-copied-files, text/uri-list, text/x-moz-url, text/plain, TARGETS, MULTIPLE, TIMESTAMP, SAVE_TARGETS,
ファイラでファイルをコピーしてみると、上記のようになります。
他にも、色々なものをコピーして、確認してみてくだだい。