桌面应用程序之间用户发起数据传输的传统方法是剪贴板或拖放。GTK+ 从一开始就支持这些方法,但在 GTK3 之前,我们用于这种数据传输的 API 是对相应的 X11 API 的简单伪装:选择、属性和原子。这并不奇怪,因为整个 GDK API 都是以 X11 为模型的。不幸的是,该实现包括诸如增量传输和字符串格式转换之类的弊端。
对于 GTK4,我们正在抛弃这些东西,因为我们正在将 GDK 中的内容移动得更接近 Wayland API。数据传输是迫切需要这种现代化的领域之一。值得庆幸的是,它现在几乎完成了,因此值得看看发生了哪些变化,以及未来事物将如何运作。
概念
如果您的应用程序想要发送的数据不是字符串,它可能是一些对象,例如 GFile、GdkTexture 或 GdkRGBA。接收端的应用程序可能不使用 GTK 或 GLib,因此不会知道这些类型。即使它知道,也没有办法将对象从一个进程完整地传递到另一个进程。
核心上,数据传输的工作原理是,发送应用程序发送一个文件描述符,目标应用程序从该文件描述符读取字节流。剪贴板和 DND 的协议使用诸如 text/uri-list、image/png 或 application/x-color 之类的 MIME 类型来标识字节流的格式。
发送对象涉及协商双方都支持的数据格式,将源端的对象序列化为该格式的字节流,传输数据,并在目标端反序列化对象。
本地情况
在进入具体的 API 之前,值得停下来考虑一下另一种常见的数据传输情况:在单个应用程序内部。使用相同的剪贴板和 DND 机制将数据从应用程序的一侧发送到另一侧是很常见的。由于在这种情况下我们没有跨越进程边界,因此我们可以避免字节流以及相关的序列化和反序列化开销,而只需传递对该对象的引用即可传输数据。
API
我们在上一节中提到的对象由 GType(例如 G_TYPE_FILE 或 GDK_TYPE_TEXTURE)描述,而正如所提到的,Wayland 和 X11 协议中的数据交换格式由 MIME 类型描述。
我们引入的第一个用于处理这些类型的 API 是 GdkContentFormats 对象。它可以包含格式列表,这些格式可以是 GType 或 MIME 类型。我们使用 GdkContentFormats 对象来描述应用程序可以提供数据采用的格式,以及应用程序可以接收数据的格式。
您可能想知道为什么我们在同一个对象中混合使用 GType 和 MIME 类型。答案是我们希望使用相同的 API 来处理跨进程和本地情况。虽然我们需要为跨进程情况匹配 MIME 类型,但我们需要本地情况的 GType。
转换
我们仍然需要一种方法来关联 GType 和 MIME 类型,以便我们可以相互转换。这由 GdkContentSerializer 和 GdkContentDeserializer API 处理。这些本质上是转换函数的注册表:GdkContentSerializer 知道如何将 GType 转换为 MIME 类型,而 GdkContentDeserializer 处理相反的方向。
GDK 具有用于常见类型的预定义转换,但该系统可以通过使用 gdk_content_register_serializer 和 gdk_content_register_deserializer 来扩展。
内容
现在我们知道如何描述格式以及在它们之间进行转换,但为了将所有这些整合在一起,我们仍然需要一个方便的 API,该 API 在一侧接收对象,并在另一侧提供字节流。为此,我们添加了 GdkContentProvider API。
GdkContentProvider 允许您将对象与输入端的格式描述结合使用,并在输出端提供异步写入器 API,该 API 可以连接到我们想要在其上发送数据的文件描述符。
典型的内容提供程序是这样创建的
gdk_content_provider_new_for_value (gvalue) gdk_content_provider_new_for_bytes (gbytes, mimetype)
它接收的 GValue 包含对象及其类型的信息,因此如果我们提供对象作为 GBytes(本质上只是一段内存),则不需要额外的类型信息,我们需要单独提供类型信息。
剪贴板
GTK3 有一个 GtkClipboard 对象,它为复制/粘贴操作提供实现。在 GTK 中拥有此对象并不理想,因为它需要在 GTK 支持的平台上进行不同的实现。因此,GTK4 将该对象移动到 GDK,并因此将其重命名为 GdkClipboard。它也已被移植到上面描述的新数据传输 API。要在 GTK4 中将数据放入剪贴板,请使用“set” API 之一
gdk_clipboard_set_content() gdk_clipboard_set_value() gdk_clipboard_set_text()
最终,所有这些函数都会将 GdkContentProvider 与剪贴板关联起来。
要在 GTK4 中从剪贴板读取数据,请使用异步“read”API 之一
gdk_clipboard_read_async() gdk_clipboard_read_value_async() gdk_clipboard_read_text_async()
拖放
GTK3 拖放 API 涉及侦听 GtkWidget 上的多个信号,并为拖动源和目标调用一些特殊的设置函数。它很灵活,但通常被认为是令人困惑的,我们在这里不会详细描述它。
在 GTK4 中,拖放 API 已围绕内容提供程序和事件控制器的概念进行重组。要启动拖放操作,您可以创建一个 GtkDragSource 事件控制器来响应拖动手势(您也可以通过仅调用 gdk_drag_begin 来启动“一次性”拖放操作),并为要传输的数据提供一个 GdkContentProvider。要接收拖放操作,您可以创建一个 GtkDropTarget 事件控制器,并在它发出 ::drop-done 信号时调用异步读取方法
gdk_drop_read_value_async() gdk_drop_read_text_async()