桌面应用程序之间用户发起的数据传输的传统方法是剪贴板或拖放。GTK+ 从一开始就支持这些方法,但在 GTK3 之前,我们用于此类数据传输的 API 只是对相应的 X11 API 的简单伪装:选择、属性和原子。这并不奇怪,因为整个 GDK API 都是以 X11 为模型构建的。不幸的是,该实现包括增量传输和字符串格式转换等令人头疼的问题。
对于 GTK4,我们正在抛弃这些东西,因为我们正在将 GDK 中的东西向 Wayland API 靠拢。数据传输是迫切需要这种现代化的领域之一。值得庆幸的是,它目前已接近完成,因此值得了解一下发生了哪些变化,以及未来事情将如何运作。
概念
如果你的应用程序想要发送的数据不是字符串,它可能是一些对象,例如 GFile、GdkTexture 或 GdkRGBA。接收端的应用程序可能不使用 GTK 或 GLib,因此不会知道这些类型。即使它知道,也没有办法将对象从一个进程完整地传递到另一个进程。
核心而言,数据传输的工作原理是从源应用程序发送一个文件描述符,目标应用程序从该描述符读取字节流。剪贴板和拖放的协议使用 MIME 类型(例如 text/uri-list、image/png 或 application/x-color)来标识字节流的格式。
发送对象涉及协商双方都支持的数据格式,在源端将对象序列化为该格式的字节流,传输数据,并在目标端反序列化对象。
本地情况
在继续讨论具体的 API 之前,值得花一点时间考虑另一种常见的数据传输情况:在单个应用程序内部。使用相同的剪贴板和拖放机制将数据从应用程序的一侧发送到另一侧是很常见的。在这种情况下,由于我们没有跨越进程边界,我们可以避免字节流以及相关的序列化和反序列化开销,只需传递对该对象的引用即可传输数据。
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,它可以在一侧获取一个对象,并在另一侧提供字节流。为此,我们添加了 GdkContentProvider API。
GdkContentProvider 允许你将对象与输入侧的格式描述结合起来,并在输出侧提供一个异步写入 API,该 API 可以连接到我们想要通过其发送数据的文件描述符。
典型的 content provider 是这样创建的
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 已围绕 content provider 和事件控制器的概念进行了重组。要启动拖放操作,你可以创建一个 GtkDragSource 事件控制器,该控制器对拖动手势做出反应(你也可以通过调用 gdk_drag_begin 自己启动“一次性”拖放操作),并为其提供一个 GdkContentProvider 用于你要传输的数据。要接收拖放操作,你可以创建一个 GtkDropTarget 事件控制器,并在它发出 ::drop-done 信号时调用异步读取方法
gdk_drop_read_value_async() gdk_drop_read_text_async()