C++ 从 Linux 上的剪贴板获取字符串

C++ Get string from Clipboard on Linux

本文关键字:剪贴板 获取 字符串 Linux C++      更新时间:2023-10-16

您好,我正在编写一个c ++程序,我需要将剪贴板上的内容放入字符串变量中。我找到了很多解决方案,但所有这些解决方案都是为Windows编写的。有什么方法不使用QT库吗?我发现了与 X11 相关的东西,但也不是很明确。

谢谢

X11 使用灵活的多缓冲区多格式异步应用程序端剪贴板协议。

大多数工具包都实现了它(GTK的gtk_clipboard_get(),Qt的QApplication::clipboard(),Tk的clipboard_get(。但是,您可以使用 X11 API 手动执行此操作,例如,如果您不使用工具包,或者您必须通过剪贴板缓冲区传递大量数据而不同时将其全部保存在内存中。

理论

可能有很多缓冲区,但您只需要知道两个:

  • CLIPBOARD是通常的显式缓冲区:您可以使用"编辑/复制"菜单将内容复制到那里,然后使用"编辑/粘贴"菜单粘贴它。
  • PRIMARY选择是一种隐式鼠标选择功能:使用鼠标光标选择文本时,文本会进入其中,并在文本输入字段中单击中键时从中粘贴。

主选择不需要按键,因此它对于在彼此相邻的窗口之间复制小片段非常有用。这个功能主要是特定于Unix的,但我见过腻子,trillian和一些gtk应用程序在Windows操作系统上模拟它。此外,Firefox 在中键单击页面的空白非交互空间时具有"粘贴和转到"功能。

为了优化内容,这些是应用程序缓冲区:而不是每次更改时都将整个剪贴板/选择推送到服务器,应用程序只是告诉服务器"我拥有它"。要获取缓冲区,您需要要求所有者为您提供其内容。这样,即使是大型缓冲区也不会占用任何资源,直到实际请求为止。

请求缓冲区时,您要求所有者提供所需的特定格式。例如,从seamonkey浏览器复制的图像(右键单击图像并按"复制图像"(可以用不同的格式表示。如果您将其粘贴到终端中,它将显示为图像 URL。如果您将其粘贴到libreoffice编写器中,它将成为从该URL加载的图片。如果粘贴在 gimp 中,它将是图像本身。这是有效的,因为seamonkey很聪明,它为每个应用程序提供了它要求的格式:终端的文本字符串,libreoffice的html和gimp的图像数据。要请求文本格式,您需要UTF8_STRING格式,并回退到STRING

当您要求另一个应用程序准备缓冲区时,这可能需要一些时间,请求是异步的:所有者准备缓冲区,将其保存在指定位置(window 属性用作临时存储(,并在完成后通知您SelectionNotify事件。

因此,要获取缓冲区:

  • 选择缓冲区名称(CLIPBOARDPRIMARY(、格式( UTF8_STRINGSTRING ( 和用于存储结果的窗口属性
  • 调用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 GB 的文本日志。而 X11 允许这样做!但是数据必须以增量方式传递,拆分为块。

如果请求的缓冲区太大,则所有者不会将其存储到 window 属性中,而是设置格式为 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该工具对大于 4000 的缓冲区使用INCR传输。根据ICCCM的说法,由应用程序选择合理的大小限制。

相同的代码适用于PRIMARY选择。将"剪贴板"替换为"主要"以打印PRIMARY选择内容。

引用

  • X选择摘要由杰米扎温斯基
  • Xlib 编程手册 - 选择
  • ICCCM - 大数据传输和 INCR 协议
  • https://github.com/exebook/x11clipboard - 最少的XCopy()XPaste()实现
  • xselxclip来源
  • 二次选择——查尔斯·林赛的历史和思想

您是否尝试先找到的不是代码,而是带有实现的程序?我为您做了这件事,并发现了很多使用直接 X11 调用的实现。我认为最有价值的是这个,但你也可以读这个。只需找到任何程序并查找来源。尝试在维基百科上查看哪些应用程序使用x11剪贴板/选择系统。

以下程序专门用于数据传输 机制:

xcutsel将数据从选区传输到剪切缓冲区,反之亦然

xclipboardglipper(Gnome(、parcellite(LXDE(和klipper(KDE(是 剪贴板管理器,也许wmcliphistxcb显示的内容 剪切缓冲区并允许用户操作它们 x选择,

xclipxselxcopy是将数据复制到 或 的命令行程序 从 X 选择。xcopy 有一个详细选项,可以帮助调试 X 选择问题。Parcellite还具有读取和 从命令行写入特定的 X 选择。

synergy是一个跨平台工具,允许您共享剪贴板 运行多个操作系统的多台计算机

xfce4-clipman-plugin是"Xfce4的剪贴板历史插件"。 面板",还有一个剪贴板管理器xtranslate在 多语言词典中的X选择自动切割同步剪切缓冲区 和选择缓冲区

简而言之,理论上,X11 有 2 个"剪贴板":实际上是一个键盘和用于选择 - 您立即选择的文本可以通过按鼠标中键粘贴到您想要的任何位置,而实际的"键盘"用于主要/默认剪贴板目的作为不同类型对象的交换。

附言在我的经验之后,我不会再使用 x11 了。享受:)