X11 использует гибкий многобуферный многоформатный асинхронный протокол буфера обмена на стороне приложения.
Он реализован в большинстве наборов инструментов (GTK gtk_clipboard_get()
, Qt QApplication::clipboard()
, Tk clipboard_get). Но вы можете сделать это вручную с помощью X11 API, например, если вы не используете наборы инструментов, или если вам нужно передать большой объем данных через буфер обмена, не сохраняя их все в памяти одновременно.
Теория
Буферов может быть много, но вам нужно знать только о двух:
CLIPBOARD
— это обычный явный буфер: вы копируете туда элементы с помощью меню «Правка/Копировать» и вставляете их с помощью меню «Правка/Вставка».
PRIMARY
выделение — это неявная функция выделения мышью: текст попадает в него при выделении курсором мыши и вставляется из него при щелчке средней кнопкой мыши в полях ввода текста.
Первичный выбор не требует нажатия клавиш, поэтому он полезен для копирования небольших фрагментов между окнами, которые находятся рядом друг с другом. Эта функция в основном специфична для Unix, но я видел putty, trillian и некоторые приложения gtk, эмулирующие ее в ОС Windows. Также в firefox есть функция «Вставить и перейти», когда щелкаешь средней кнопкой мыши по пустому неинтерактивному пространству страницы.
Чтобы оптимизировать вещи, это буферы на стороне приложения: вместо того, чтобы отправлять весь буфер обмена/выборку на сервер каждый раз, когда он изменяется, приложение просто сообщает серверу: «Я владею им». Чтобы получить буфер, вы просите владельца предоставить вам его содержимое. Таким образом, даже большой буфер не требует ресурсов до тех пор, пока он действительно не будет запрошен.
При запросе буфера вы спрашиваете владельца о конкретном формате, который вам нужен. Например, изображение, скопированное из браузера seamonkey (щелкните изображение правой кнопкой мыши и нажмите «Копировать изображение»), может быть представлено в различных форматах. Он будет отображаться как URL-адрес изображения, если вы вставите его в терминал. Это станет картинкой, загруженной с этого URL-адреса, если вы вставите ее в писатель libreoffice. И это будет само изображение, если его вставить в GIMP. Это работает, потому что seamonkey умен и предоставляет каждому приложению формат, который оно запрашивает: текстовую строку для терминала, html для libreoffice и данные изображения для gimp. Чтобы запросить текстовый формат, вы должны запросить формат UTF8_STRING
с откатом к STRING
.
Поскольку вы просите другое приложение подготовить буфер, и это может занять некоторое время, запрос является асинхронным: владелец подготавливает буфер, сохраняет его в указанном месте (свойство окна используется как временное хранилище ) и уведомляет вас событием SelectionNotify
, когда это будет сделано.
Итак, чтобы получить буфер:
- выберите имя буфера (
CLIPBOARD
, PRIMARY
), формат (UTF8_STRING
, STRING
) и свойство окна для сохранения результата
- вызовите
XConvertSelection()
, чтобы запросить буфер
- дождаться
SelectionNotify
события
- читать содержимое буфера из свойства окна
Наивная реализация
// gcc -o xclipget xclipget.c -lX11
#include <stdio.h>
#include <limits.h>
#include <X11/Xlib.h>
Bool PrintSelection(Display *display, Window window, const char *bufname, const char *fmtname)
{
char *result;
unsigned long ressize, restail;
int resbits;
Atom bufid = XInternAtom(display, bufname, False),
fmtid = XInternAtom(display, fmtname, False),
propid = XInternAtom(display, "XSEL_DATA", False),
incrid = XInternAtom(display, "INCR", False);
XEvent event;
XConvertSelection(display, bufid, fmtid, propid, window, CurrentTime);
do {
XNextEvent(display, &event);
} while (event.type != SelectionNotify || event.xselection.selection != bufid);
if (event.xselection.property)
{
XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, False, AnyPropertyType,
&fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
if (fmtid == incrid)
printf("Buffer is too large and INCR reading is not implemented yet.\n");
else
printf("%.*s", (int)ressize, result);
XFree(result);
return True;
}
else // request failed, e.g. owner can't convert to the target format
return False;
}
int main()
{
Display *display = XOpenDisplay(NULL);
unsigned long color = BlackPixel(display, DefaultScreen(display));
Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0, 1,1, 0, color, color);
Bool result = PrintSelection(display, window, "CLIPBOARD", "UTF8_STRING") ||
PrintSelection(display, window, "CLIPBOARD", "STRING");
XDestroyWindow(display, window);
XCloseDisplay(display);
return !result;
}
Это будет работать для многих простых случаев. Одной вещи здесь не хватает, так это поддержки инкрементного чтения больших буферов. Давайте добавим!
Большие буферы
Некоторым приложениям может понадобиться скопировать/вставить 100 гигабайт текстовых журналов. И X11 позволяет это! Но данные нужно передавать инкрементно, разбивая на куски.
Если запрошенный буфер слишком велик, вместо того, чтобы сохранять его в свойстве окна, владелец устанавливает свойство формата INCR
. Если вы удалите его, владелец предполагает, что вы его прочитали, и помещает следующий фрагмент в то же свойство. Это продолжается до тех пор, пока последний фрагмент не будет прочитан и удален. Наконец, владелец устанавливает свойство размера 0, чтобы отметить конец данных.
Таким образом, чтобы прочитать большой буфер, вы удаляете свойство INCR
и ждете, пока свойство появится снова (событие PropertyNotify
, состояние == PropertyNewValue
), читаете и удаляете его, ждете, пока оно снова не появится, и так далее, пока оно не появится с нулевым размером.
// gcc -o xclipget xclipget.c -lX11
#include <stdio.h>
#include <limits.h>
#include <X11/Xlib.h>
Bool PrintSelection(Display *display, Window window, const char *bufname, const char *fmtname)
{
char *result;
unsigned long ressize, restail;
int resbits;
Atom bufid = XInternAtom(display, bufname, False),
fmtid = XInternAtom(display, fmtname, False),
propid = XInternAtom(display, "XSEL_DATA", False),
incrid = XInternAtom(display, "INCR", False);
XEvent event;
XSelectInput (display, window, PropertyChangeMask);
XConvertSelection(display, bufid, fmtid, propid, window, CurrentTime);
do {
XNextEvent(display, &event);
} while (event.type != SelectionNotify || event.xselection.selection != bufid);
if (event.xselection.property)
{
XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, True, AnyPropertyType,
&fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
if (fmtid != incrid)
printf("%.*s", (int)ressize, result);
XFree(result);
if (fmtid == incrid)
do {
do {
XNextEvent(display, &event);
} while (event.type != PropertyNotify || event.xproperty.atom != propid || event.xproperty.state != PropertyNewValue);
XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, True, AnyPropertyType,
&fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
printf("%.*s", (int)ressize, result);
XFree(result);
} while (ressize > 0);
return True;
}
else // request failed, e.g. owner can't convert to the target format
return False;
}
int main()
{
Display *display = XOpenDisplay(NULL);
unsigned long color = BlackPixel(display, DefaultScreen(display));
Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0, 1,1, 0, color, color);
Bool result = PrintSelection(display, window, "CLIPBOARD", "UTF8_STRING") ||
PrintSelection(display, window, "CLIPBOARD", "STRING");
XDestroyWindow(display, window);
XCloseDisplay(display);
return !result;
}
Например, инструмент xsel
использует передачу INCR
для буферов размером более 4000. Согласно ICCCM, приложение может выбрать разумный предел размера.
Тот же код работает для выбора PRIMARY
. Замените «БУФЕР ОБМЕНА» на «ПЕРВИЧНЫЙ», чтобы напечатать PRIMARY
содержимого выделенного фрагмента.
использованная литература
person
x11user
schedule
09.07.2017