Espanso 是一个花哨玩意儿,我尝试了数次研究出来一些有趣的玩法,记录一下。
区分是键入调用还是 search bar 调用
Espanso 的命令(其实叫 match,我习惯叫命令吧就)有两种调用方式——直接从键盘输入整个命令,或者通过 search bar,去查询和执行命令。
但 Espanso 没有提供任何方式来区分一个命令究竟是从键盘输入还是通过 search bar 调用。这可能是一个伪需求,但我就是想要试试。
解决方案利用 Espanso 的一个特性——命令内部的变量能够覆盖同名全局变量。正则表达式会引入变量,而如果通过 search bar 执行正则表达式命令,这个变量则不会被赋值。因此,我们这么干就行了——
| global_vars: - name: '_' type: echo params: echo: 'TRUE'
matches: - regex: '(?P<_>;)hello' replace: '{{_}}'
|
使用键盘输入时,_会被赋值为;,使用 search bar 执行时,_没有被赋值,espanso 就去全局变量里找了。
这还是在 search bar 中执行正则命令出错时意识到这个解决方法的。也尝试过能不能局部变量里也定义一个_,但发现行不通,会报一个莫名其妙的循环依赖,espanso 自己还崩了,笑死。
Windows 下执行 shell 脚本
shell 脚本在 win 下默认走的是 Powershell,而且不支持配置成 git bash。
但并非不能——espanso 自己不用 git bash,那我自己用。
对官网的 curl 示例:
| - trigger: ";ip" replace: "{{output}}" vars: - name: output type: shell params: cmd: "curl 'https://api.ipify.org'"
|
对应的改为:
| - trigger: ";ip" replace: "{{output}}" vars: - name: output type: script params: args: - env - bash - -c - | printf 'my ip: ' curl -s 'https://api.ipify.org'
|
这里必须要给定 bash 的完整路径,因为:
- espanso 执行 script 时,工作目录在
C:\Windows\System32
System32下有一个不能用的 bash,疑似是 WSL 用的
- 命令只写
bash的时候,因为工作目录的缘故,会优先访问System32下面的 bash,即使 PATH 设置的时候让 git 的 bash 优先仍旧如此
然而,有两个解决方案:
- 使用
sh,win 没有同名命令,但 sh 执行特定既有的脚本可能有问题
- (明显这个才是最优解)使用
env bash,win 没有 env 这个命令,大概吧
屏蔽变量的内容(只执行命令,不返回它的结果)
espanso 的变量是 lazy 的——不被引用就不执行。有时候我们想要执行一个命令,但不想要它的标准输出流被拷出来,这就很麻烦。
但变量之间是可以互相依赖的,一般来说 espanso 会根据实际的依赖关系建立依赖树去按顺序执行变量,但是可以使用depends_on去主动指明依赖,即使没有使用它。
| - trigger: ';some-test' replace: '{{var2}}' vars: - name: var1_hide type: script params: args: - python - D:\some\script.py - name: var2 depends_on: - var1_hide type: echo params: echo: hello
|
关于退格键
espanso 执行命令后,会使用剪切板或模拟输入去将命令的结果输出出来。不想要输出?使用上面的方法,然后变量的值设为空即可。
而在这个步骤之前,espanso 会模拟输入一定数量的退格键,先删除输入的命令的内容……这就出问题了。比如我用 espanso 当软件快捷键用,这时候的一系列退格键可能会造成非预期的问题。
我想到的最合适的方法就是直接使用 espanso 的 search bar 去搜索和执行命令,使用这个方法似乎是不会输入退格键的……再不然就是让在 espanso 输入退格键时保证应用窗口是失焦的,不知道有啥方法更优雅地做这个。
仔细看看 Espanso,我们能发现什么?
Alt+Space 唤起一个 Search Bar,能匹配和选择命令去执行
- 原生提供 Form,提供输入栏和选择框(下拉式或列表式),其中输入栏可以给定默认值,选择框可以使用脚本给定所有选项
- Form 可以和 Script 协同使用,Form 的值可以喂给 Script,反之亦然
- Script 的执行结果可以贴到当前输入框中
这就造成了一种非常轻量的类似 utools 的工具——我调用命令,我通过表单填写参数,我通过当前输入框获取命令的执行结果,但最重要的是,定义 Espanso 的一个新命令是非常容易的,不像 utools 或类似的工具,插件上来就是什么生命周期,还可能要写前端,麻烦不死你。
简单实现一个 base64 和文本互转的命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| - trigger: ';b64' replace: '{{result}}' vars: - name: form type: form params: layout: | 文本和 Base64 互相转换,只填一个 文本转 base64: [[original_text]] base64 转文本:[[encoded_text]] - name: result type: script params: args: - python - -c - | import sys import base64 def text_to_base64(text, encoding='utf-8'): """文本转 Base64""" text_bytes = text.encode(encoding) base64_bytes = base64.b64encode(text_bytes) return base64_bytes.decode('ascii') def base64_to_text(base64_string, encoding='utf-8'): """Base64 转文本""" try: base64_bytes = base64_string.encode('ascii') text_bytes = base64.b64decode(base64_bytes) return text_bytes.decode(encoding) except Exception as e: return f"Base64 解码失败:{e}" if sys.argv[1]: # 文本转 base64 print(text_to_base64(sys.argv[1])) elif sys.argv[2]: print(base64_to_text(sys.argv[2])) else: pass # 毛都没输入,就什么也不输出 - '{{form.original_text}}' - '{{form.encoded_text}}'
|
动态图片命令
某个 Match 使用image_path而非replace时,espanso 对它的处理方式有异——不执行任何变量,直接以image_path原始内容作为图像路径。这事儿文档里也不写。
相关 issue 中,官方的解决方案是使用 html 或 markdown 富文本来替代嵌套图片的需求,这个算是方便,但在许多时候不可用——富文本类型的剪贴板我试了手头的应用,只有 word 支持(需要使用 html 而非 markdown),qq,vscode 等都不主持,会忽略掉图片。
尝试了一下,找到一个不那么完美的解决方案,仅适用于 windows 且需要利用 nircmd:
- 通过某种手段获取图像路径
- 使用
nircmd clipboard将图像路径保存到剪切板
- 使用
nircmd sendkeypress ctrl+v模拟输入粘贴(这一步也可以省略,让用户自己去粘贴)
下面是一个文本生成二维码并粘贴的完整示例(文本使用表单输入以兼容中文问题):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| - trigger: ';qrcode' label: 文本生成二维码 replace: '{{clipboard}}' vars: - name: form type: form params: layout: | 文本内容:[[text]] - name: image_path type: script params: args: - python - -c - | import sys import qrcode import tempfile import os from pathlib import Path def generate_qrcode_to_tempfile(data: str, file_format: str = 'png') -> str: qr = qrcode.QRCode( version=1, # 控制二维码大小 (1-40) error_correction=qrcode.constants.ERROR_CORRECT_L, # 容错率 box_size=10, # 每个小格子的像素大小 border=4, # 边框大小 ) qr.add_data(data) qr.make(fit=True) with tempfile.NamedTemporaryFile( suffix=f'.{file_format}', prefix='qrcode_', delete=False, # 不自动删除,以便返回路径 dir=tempfile.gettempdir() ) as tmp_file: temp_path = tmp_file.name try: img = qr.make_image(fill_color="black", back_color="white") img.save(temp_path, format=file_format.upper()) return temp_path except Exception as e: # 如果出错,清理临时文件 if os.path.exists(temp_path): os.unlink(temp_path) raise RuntimeError(f"生成二维码失败:{str(e)}") sys.argv[1] and print(generate_qrcode_to_tempfile(sys.argv[1])) - '{{form.text}}' - name: write_clipboard type: script params: args: - nircmd - clipboard - copyimage - '{{image_path}}' - name: clipboard type: script depends_on: - write_clipboard params: args: - nircmd - sendkeypress - ctrl+v
|