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

Posted

技术标签:

【中文标题】C ++从Linux上的剪贴板获取字符串【英文标题】:C++ Get string from Clipboard on Linux 【发布时间】:2014-12-09 11:52:10 【问题描述】:

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

非常感谢

【问题讨论】:

***.com/questions/6271177/…的可能重复 嗯,我一直在寻找我可以编写和理解的代码,而不是它背后的整个理论。例如,关于剪贴板的 Windows 问题很容易用简单的 Windows 函数处理,我听说它对 linux 来说有点复杂,但我找不到任何代码示例。顺便说一句,我已经在 Windows 端做了这个(工作)。 那么您应该更准确地指定您想要处理的抽象级别。从技术上讲,我提供的答案提供了尽可能低的信息。然而在实践中,所有这些无聊和血腥的细节并没有直接使用,只能通过各种包装器/工具包,如 Qt 或 Gtk+ 顺便说一句,这是Gtk.Clipboard的例子 【参考方案1】:

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

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

理论

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

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

主选择不需要按键,因此对于在相邻的窗口之间复制小片段很有用。此功能主要是特定于 unix 的,但我已经看到 putty、trillian 和一些 gtk 应用程序在 Windows 操作系统上模拟它。当中键单击页面的空白非交互空间时,Firefox 还具有“粘贴 & Go”功能。

为了优化那些是应用程序端缓冲区的东西:应用程序不会在每次更改时将整个剪贴板/选择推送到服务器,而是告诉服务器“我拥有它”。要获得缓冲区,您要求所有者给您其内容。这样,即使是大缓冲区,在实际请求之前也不会占用任何资源。

请求缓冲区时,您需要向所有者询问您需要的特定格式。例如,从 seamonkey 浏览器复制的图像(右键单击图像并按“复制图像”)可以用不同的格式表示。如果您将其粘贴到终端中,它将显示为图像 URL。如果您将其粘贴到 libreoffice writer 中,它将成为从该 URL 加载的图片。如果粘贴在 gimp 中,它将是图像本身。这是因为 seamonkey 很聪明,它为每个应用程序提供了它所要求的格式:终端的文本字符串、libreoffice 的 html 和 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 GB 的文本日志。 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 工具对大于 4000 的缓冲区使用 INCR 传输。根据 ICCCM,由应用程序选择合理的大小限制。

相同的代码适用于PRIMARY 选择。将“CLIPBOARD”替换为“PRIMARY”以打印PRIMARY 选择内容。

参考文献

X Selections summary Jamie Zawinski Xlib Programming Manual - 选择 ICCCM - 大数据传输和 INCR 协议 https://github.com/exebook/x11clipboard - 最小的 XCopy()XPaste() 实现 xselxclip 来源 The Secondary Selection - Charles Lindsey 的历史和思想

【讨论】:

这个答案很漂亮。 X11 大师是如此罕见。你是谁? 从来没有感谢你在这里的努力!谢谢!【参考方案2】:

您是否尝试过首先找到的不是代码,而是具有实现的程序?我为你做了,发现很多使用直接 X11 调用的实现。我认为最有价值的是this,但您也可以阅读this。只需找到任何程序并寻找来源即可。尝试在 wikipedia 上查看哪些应用程序使用 x11 剪贴板/选择系统。

以下程序专门对数据传输进行操作 机制:

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

xclipboardglipper (Gnome)、parcellite (LXDE) 和 klipper (KDE) 是 剪贴板管理器,也许 wmcliphist 以及 xcb 显示的内容 剪切缓冲区并允许用户操作它们 xselection,

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

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

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

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

附:根据我的经验,我不再使用 x11。享受:)

【讨论】:

是的,我确实在寻找实现,但实际上我正在尝试一些但没有成功 这就是我说“我不再使用 X11”的原因 :) Qt 和一些库可能有针对已知和未修复的 x11-/xlibs 错误的解决方法,因此您可能需要尝试并度过几个晚上为了得到一些工作。这就是我所说的“享受:)”。 啊啊是的,我明白你的意思了:P 如果对您有帮助,您能否将答案标记为“答案”? :) 我会将另一篇文章标记为答案,因为这篇文章只有指向其他代码的链接,没有解释或内部结构。 @x11user 的帖子真的很好。

以上是关于C ++从Linux上的剪贴板获取字符串的主要内容,如果未能解决你的问题,请参考以下文章

C++ 字符串 - 如何避免获取无效指针?

golang依次替换字符串中相同的值

linux下应用mysql数据库c编程,出现段错误求解!

从字符串中获取特定数据? C#

如何从目标C中的数组中获取当前字符串值

C#:按字符串获取枚举值?