С++ Получить строку из буфера обмена в Linux

Привет, я пишу программу на С++, и мне нужно получить то, что находится в буфере обмена, в строковую переменную. Я нашел много решений, но все они были написаны для Windows. Есть ли способ без использования библиотек QT? Я нашел кое-что, связанное с X11, но не очень явное.

Большое Вам спасибо


person RubenC    schedule 09.12.2014    source источник
comment
Хм, я искал код, который мог бы написать и понять, а не всю теорию, стоящую за ним. Например, вопросы Windows о буфере обмена действительно просты для решения с помощью простых функций Windows, и я слышал, что это немного сложнее для Linux, но я не могу найти ни одного примера кода. И, кстати, я уже сделал это (работает) на стороне Windows.   -  person RubenC    schedule 09.12.2014
comment
Кстати, вот пример Gtk.Clipboard   -  person user3159253    schedule 09.12.2014


Ответы (2)


Вы пробовали сначала найти не код, а программу с реализацией? Я сделал это за вас и нашел множество реализаций, использующих прямые вызовы X11. Я думаю, что наиболее ценным является это, но вы также можете прочитать это. Просто найдите любую программу и поищите исходники. Попробуйте посмотреть в Википедии, какие приложения используют систему буфера обмена/выделения x11.

Следующие программы специально работают с механизмами передачи данных:

xcutsel передает данные из выборок в буферы вырезов или наоборот

xclipboard, glipper (Gnome), parcellite (LXDE) и klipper (KDE) являются менеджерами буфера обмена, возможно, wmcliphist также xcb показывает содержимое вырезанных буферов и позволяет пользователю манипулировать ими xselection,

xclip, xsel и xcopy — это программы командной строки, которые копируют данные в или из выделения X. xcopy имеет параметр детализации, который помогает отлаживать проблемы с выбором X. посылка также имеет возможность считывать и записывать определенные выборки X из командной строки.

synergy — это кроссплатформенный инструмент, который позволяет вам совместно использовать буфер обмена на нескольких компьютерах с несколькими операционными системами.

xfce4-clipman-plugin — это «плагин истории буфера обмена для панели Xfce4», а также менеджер буфера обмена. xtranslate ищет слова в Xselection в многоязычном словаре.

Вкратце, теоретически, X11 имеет 2 «буфера обмена»: на самом деле клавиатура и для выбора — текст, который вы сразу же выбрали, можно вставить куда угодно, нажав среднюю кнопку мыши, в то время как фактическая «клавиатура» предназначена для основного буфера обмена / буфера обмена по умолчанию, как обмен различными видами объектов.

P.S. После моего опыта я больше не буду работать с x11. Наслаждаться :)

person Victor Polevoy    schedule 09.12.2014
comment
Да, я искал реализации, и я на самом деле пробую некоторые из них, пока безуспешно. - person RubenC; 09.12.2014
comment
Вот почему я сказал, что больше не буду работать с X11 :) Qt и некоторые библиотеки могут иметь обходные пути для известных и неисправленных ошибок x11-/xlibs, поэтому вам, возможно, придется попробовать и провести несколько ночей, чтобы что-то заработало. И это я сказал Наслаждайтесь :). - person Victor Polevoy; 09.12.2014
comment
аахах да я понимаю что ты имеешь в виду :P - person RubenC; 09.12.2014
comment
Не могли бы вы пометить ответ как ответ, если он вам помог? :) - person Victor Polevoy; 21.01.2015
comment
Я бы отметил другой пост как ответ, так как в нем есть только ссылки на другой код и нет объяснений или внутренней структуры. Пост @x11user действительно хорош. - person sdd; 21.08.2020

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
comment
Этот ответ был прекрасен. Гуру X11 очень редки. Кто ты? - person étale-cohomology; 10.07.2019
comment
Никогда не благодарил вас за ваши усилия здесь! Спасибо! - person RubenC; 18.12.2020