30.24 拖放

拖放传输的数据通常为纯文本或标识文件及其他资源的 URL 列表。文本被拖放时会插入到释放位置,若无法插入则保存至删除环。

被拖放的 URL 会交由变量 dnd-protocol-alist 中对应的 DND 处理函数(DND handler functions),或由变量 browse-url-handlersbrowse-url-default-handlers 设置的 “URL 处理器(URL handlers)” 处理;若无匹配处理器,则按纯文本插入缓冲区。

Variable: dnd-protocol-alist

该变量是一个关联列表,键为匹配 URL 的正则表达式,值为拖放匹配 URL 时调用的 DND 处理函数。

若处理函数为符号,且其 dnd-multiple-handler 属性(see 符号属性)已设置,则拖放时会将所有匹配其正则表达式的 URL 以列表形式传入;若无此属性,则对每个 URL 单独调用一次。第一个参数之后会传入标识操作类型的符号:copymovelinkprivateask

actionprivate,发起拖放的程序不要求接收方执行特定行为;此时合理的操作是打开 URL 或将其内容复制到当前缓冲区。action 的其他取值含义与 dnd-begin-file-dragaction 参数基本一致。

处理函数完成工作后必须返回一个符号,标识实际执行的操作:可以是传入的操作,或 private(告知拖放源未执行其指定操作)。

若多个处理器匹配拖放中的重叠项目子集,匹配项目最多的处理器会被调用处理该子集,且这些项目不会再交由其他处理器处理。

Emacs 不支持接收文本与 URL 之外的数据,因为实现该功能的各窗口系统接口差异过大,无法统一抽象。同时,部分拖放协议不允许接收方控制操作行为,因此 DND 处理器也无法干预预期行为。X11 拖放实现基于多个底层协议,这些协议使用选区传输且共性较强,以下函数与变量提供了对其底层访问能力:

Variable: x-dnd-test-function

该函数用于判断 Emacs 是否接收此次拖放。调用时传入三个参数:

  • 被拖动项目下方的窗口,即接收拖放的缓冲区所属窗口。若项目位于框架的非窗口组件(如滚动条、工具栏等)上方,则传入框架本身。
  • 表示拖放源建议操作类型的符号:movecopylinkask,含义与 x-begin-drag 中一致。
  • 项目提供的选区数据类型向量(see X 选择区)。

该函数必须返回 nil 以拒绝拖放,或返回一个 cons,其中包含将要执行的操作(如传递给 DND 处理器)与请求的选区数据类型。cons 中返回的操作也可以是符号 private,表示尚未确定具体行为。

Variable: x-dnd-known-types

通常无需修改 x-dnd-test-function,因其默认的拖放接收规则可通过修改此选区数据类型列表调整。列表中每个元素为字符串,若默认 “测试函数” 在数据类型列表中找到对应符号名,则会接收此次拖放。

仅向此列表添加新项并无实际意义,除非同时向 x-dnd-types-alist 添加对应的处理函数。

Variable: x-dnd-types-alist

该变量是一个关联列表,键为标识选区数据类型的字符串,值为对应类型数据被拖放时调用的函数。

每个函数接收三个参数:第一个为拖放位置下方的窗口或框架,与 x-dnd-test-function 一致;第二个为将要执行的操作,可以是测试函数返回的任意操作;第三个为选区数据本身(see 选择区访问)。

X11 拖放协议提供的选区数据类型有时与 ICCCM 及兼容剪贴板或主选区所有者提供的类型不同。通常会使用 MIME 类型名称(如 "text/plain;charset=utf-8,其中 “utf-8” 大小写可能不一致)替代标准 X 选区名称(如 UTF8_STRING)。

X 直接保存(XDS)协议允许程序将拖放文件的命名职责委托给接收方。发生此类拖放时,DND 处理器与前述 X 专用接口基本会被绕过,由另一函数响应拖放。

Variable: x-dnd-direct-save-function

该变量应设置为一个函数,用于以两步流程注册并命名通过 XDS 协议拖放的文件。函数接收两个参数:need-namefilename

  1. 拖放文件的源程序请求 Emacs 提供保存文件的完整路径。此时直接保存函数会被调用,第一个参数 need-name 为非 nil,第二个参数 filename 为待保存文件的基本名称。函数应返回用于保存文件的完整扩展绝对路径。例如,若文件被拖至 Dired 窗口,默认目录应为拖放位置显示文件所在目录。若因某种原因无法保存文件,函数应返回 nil 以取消拖放操作。
  2. 拖放文件的源程序会按照第一次调用直接保存函数返回的名称保存文件。若保存成功,该函数会被再次调用,此时第一个参数 need-namenil,第二个参数 filename 为已保存文件的完整绝对路径。函数应执行文件保存后的必要操作,例如 Dired 应刷新显示目录以显示新文件。

该变量的默认值为 x-dnd-save-direct

Function: x-dnd-save-direct need-name filename

need-name 参数为非 nil 时,该函数会提示用户输入保存文件的绝对路径。若指定文件已存在,会额外询问是否覆盖,并仅在用户确认后返回绝对路径。

need-name 参数为 nil 时,若当前缓冲区为 Dired 模式或其子模式,则刷新 Dired 列表;否则调用 find-file 访问该文件(see 访问文件的函数)。

Function: x-dnd-save-direct-immediately need-name filename

该函数功能与 x-dnd-save-direct 类似,但当 need-name 为非 nil 时,不会提示用户输入文件完整名称,而是直接返回相对于当前缓冲区默认目录扩展后的 filename 路径(see 文件名展开相关函数)。(若默认目录下存在同名文件,仍会请求确认。)

若当前窗口系统支持,也可将内容从 Emacs 拖放至其他程序。实现该功能的函数如下:

Function: dnd-begin-text-drag text &optional frame action allow-same-frame

该函数从 frame 发起至其他程序(称为 拖放目标(drop target))的拖放操作,并在 text 被释放或操作取消后返回。

action 必须为符号 copymovecopy 表示拖放目标应插入文本;move 含义与 copy 相同,但调用方需按下文说明从源位置删除文本。

frame 为当前鼠标按下所在的框架,若为 nil 则使用选中框架。由于未按下鼠标按键时该函数可能立即返回,因此仅应在响应 down-mouse-1 或类似事件时调用(see 鼠标事件),并将 frame 设置为事件生成所在框架(see 点击事件)。

allow-same-framenil,则忽略在 frame 自身上的释放操作。

返回值表示拖放目标实际执行的操作,也即调用方应执行的后续操作,为下列符号之一:

copy

拖放目标已插入拖放文本。

move

拖放目标已插入拖放文本,调用方应从源缓冲区删除对应 text(若适用)。

private

拖放目标执行了其他未指定操作。

nil

拖放操作已取消。

Function: dnd-begin-file-drag file &optional frame action allow-same-frame

该函数从 frame 发起至其他程序(称为 拖放目标(drop target))的文件拖放操作,并在 file 被释放或操作取消后返回。

file 为远程文件,会创建本地临时副本。

action 必须为符号 copymovelinkcopy 表示拖放目标应打开或复制文件;move 表示拖放目标应将文件移至其他位置;link 表示拖放目标应为 file 创建符号链接。若 file 为远程文件,指定 link 为操作类型会报错。

frameallow-same-frame 的含义与 dnd-begin-text-drag 一致。

返回值为拖放目标实际执行的操作,为下列符号之一:

copy

拖放目标已打开文件或将其复制至其他位置。

move

拖放目标已将文件移至其他位置。

link

拖放目标(通常为文件管理器)已为文件创建符号链接。

private

拖放目标执行了其他未指定操作。

nil

拖放操作已取消。

Function: dnd-begin-drag-files files &optional frame action allow-same-frame

该函数与 dnd-begin-file-drag 类似,区别在于 files 为文件列表。若拖放目标不支持多文件拖放,则仅使用第一个文件。

Function: dnd-direct-save file name &optional frame allow-same-frame

该函数行为与 dnd-begin-file-drag(使用默认操作 copy 时)类似,区别在于它接受一个用于保存副本的目标名称。

上述高级接口基于底层原语实现。底层接口 x-begin-drag 也可用于拖放文本与文件之外的内容,但要求调用方详细了解各平台程序支持的数据类型与操作。

Function: x-begin-drag targets &optional action frame return-frame allow-current-frame follow-tooltip

该函数从 frame 开始拖放,并在拖放操作结束(成功释放或被拒绝)后返回。当所有鼠标按键在 frame 之外的 X 窗口(拖放目标(drop target))上方释放时发生拖放;若 allow-current-frame 为非 nil,则可在任意 X 窗口释放。若拖放开始时未按下任何鼠标按键,函数可能立即返回 nil

targets 为字符串列表,表示拖放目标可从 Emacs 请求的选区目标,与 gui-get-selectiondata-type 参数类似(see 窗口系统选择)。

action 为推荐给目标的操作符号,可以是 XdndActionCopyXdndActionMove;两者均表示将 XdndSelection 选区内容复制到拖放目标,但后者还承诺在复制完成后删除选区内容。

action 也可以是一个关联列表,将可用操作符号与拖放目标展示给用户选择的字符串关联。

action 也可以是一个关联列表,将可用操作符号与拖放目标展示给用户选择的字符串关联。

return-frame 为非 nil,且鼠标离开 frame 后移至另一 Emacs 框架,则会立即返回该框架。若 return-frame 为符号 now,则会直接返回鼠标指针下方的任意框架,无需等待鼠标离开原框架。return-frame 适用于需要特殊处理框架间拖放同时向其他程序拖放的场景,但不保证在所有系统与窗口管理器下生效。

follow-tooltip 为非 nil,拖放过程中任意提示框(如 tooltip-show 显示的提示框)位置会跟随鼠标指针移动,所有鼠标按键释放后提示框会隐藏。

若拖放被拒绝或未找到目标,函数返回 nil。否则返回表示目标选择执行的操作符号,若目标不支持指定操作,返回值可能与 action 不同。除 XdndActionCopyXdndActionMove 外,XdndActionPrivate 也是合法返回值,表示目标执行了未定义操作,调用方无需进一步处理。

调用方需配合目标完成所选操作。例如,若函数返回 XdndActionMove,应删除被拖放的缓冲区文本,其他拖放数据同理。

函数 x-begin-drag 在 “后台(behind the scenes)” 使用了多种拖放协议。当拖动的内容已知不被某种特定拖放协议支持时,可以通过修改下列变量的值来禁用该协议:

Variable: x-dnd-disable-motif-protocol

当该变量非 nil 时,Motif 拖放协议将被禁用,拖放到仅支持该协议的程序上将无法生效。

Variable: x-dnd-use-offix-drop

当该变量为 nil 时,OffiX(旧版 KDE)拖放协议将被禁用。当该变量为符号 files 时,仅当 "FILE_NAME" 是传递给 x-begin-drag 的目标之一时,才会使用 OffiX 协议。其他任何值均表示使用 OffiX 协议拖放所有受支持的内容。

Variable: x-dnd-use-unsupported-drop

当传递给 x-begin-drag 的目标列表中包含 "STRING""UTF8_STRING""COMPOUND_TEXT""TEXT" 中的任意一个时,如果拖放目标完全不支持任何拖放协议,Emacs 将尝试使用合成鼠标事件与主选区来插入文本。

副作用是,在执行此类拖放操作时,Emacs 会成为主选区的所有者。将该变量设为 nil 可禁用这种模拟行为。