Electron PrinterInfo 对象详解:通过 getPrintersAsync 获取与解析系统打印机列表
在 Electron 中构建“打印到指定设备”“打印机状态监控”或“打印设置面板”类功能时,核心数据结构就是 PrinterInfo 对象。它由 webContents.getPrintersAsync() 返回,封装了操作系统认识的打印机名称、打印预览中显示的名称、设备描述,以及一大块平台相关的 options 键值对。读完本文,你将掌握 PrinterInfo 各字段的准确语义、各平台(Windows / Linux / macOS)下 options 与状态码的差异、以及 Electron 从 JS 层到 Chromium 打印后端的完整实现链路,能直接在自己的应用中落地打印机枚举与选择逻辑。
PrinterInfo 对象结构
PrinterInfo 是一个纯数据对象,共 4 个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 操作系统层面识别的打印机名称(system-defined name) |
displayName |
string | 在打印预览(Print Preview)中展示的打印机名称 |
description |
string | 对打印机类型的更长描述,例如机型信息 |
options |
Object | 包含数量不定的、平台相关的打印机信息键值对 |
name 与 displayName 的区别是使用中最容易踩坑的一点:name 是操作系统内部名称,通常是带下划线、无空格的机器标识;displayName 才是给用户看的友好名称。当你调用 webContents.print({ deviceName }) 指定打印设备时,传入的必须是 name(系统定义名),而不是 displayName(friendly name)。这一约束在官方文档的 contents.print() 一节中同样有强调:
deviceNamestring (optional) - Set the printer device name to use. Must be the system-defined name and not the 'friendly' name, e.g'Brother_QL_820NWB'and not'Brother QL-820NWB'.
options 字段:平台相关的扩展信息
options 是 PrinterInfo 中信息量最大的部分,其键值数量和内容因平台而异。官方示例如下,展示了一台 Linux/CUPS 环境下网络打印机的典型输出:
{
name: 'Austin_4th_Floor_Printer___C02XK13BJHD4',
displayName: 'Austin 4th Floor Printer @ C02XK13BJHD4',
description: 'TOSHIBA ColorMFP',
options: {
copies: '1',
'device-uri': 'dnssd://Austin%204th%20Floor%20Printer%20%40%20C02XK13BJHD4._ipps._tcp.local./?uuid=71687f1e-1147-3274-6674-22de61b110bd',
finishings: '3',
'job-cancel-after': '10800',
'job-hold-until': 'no-hold',
'job-priority': '50',
'job-sheets': 'none,none',
'marker-change-time': '0',
'number-up': '1',
'printer-commands': 'ReportLevels,PrintSelfTestPage,com.toshiba.ColourProfiles.update,com.toshiba.EFiling.update,com.toshiba.EFiling.checkPassword',
'printer-info': 'Austin 4th Floor Printer @ C02XK13BJHD4',
'printer-is-accepting-jobs': 'true',
'printer-is-shared': 'false',
'printer-is-temporary': 'false',
'printer-location': '',
'printer-make-and-model': 'TOSHIBA ColorMFP',
'printer-state': '3',
'printer-state-change-time': '1573472937',
'printer-state-reasons': 'offline-report,com.toshiba.snmp.failed',
'printer-type': '10531038',
'printer-uri-supported': 'ipp://localhost/printers/Austin_4th_Floor_Printer___C02XK13BJHD4',
system_driverinfo: 'T'
}
}
从这份示例可以提炼出几个跨平台开发时需要知道的要点:
- 所有值都是字符串。即使是布尔语义(
'printer-is-accepting-jobs': 'true')或数值语义('job-priority': '50'、'printer-state': '3')也保持字符串形态,使用前必须自行转换。 - CUPS 语义的键名。在 Linux/macOS 上,
options中大量出现device-uri、job-cancel-after、printer-state-reasons、printer-make-and-model这类 IPP/CUPS 标准属性名,说明该平台的打印机属性直接透传自 CUPS 服务。 - 状态值含义随平台变化。示例中的
printer-state: '3'在 CUPS 语义里表示"空闲但离线"(idle, but offline),配合printer-state-reasons: 'offline-report,...'可以进一步判断不可打印的原因。官方文档明确提示:这些数字在不同平台含义不同——Windows 上的取值含义对应 Win32 打印 API 的打印机信息结构定义,Linux 与 macOS 上的取值含义则遵循 CUPS 打印机监控接口的规范。因此跨平台代码不应硬编码状态数字,而应按平台分支处理。 - 厂商私有属性也可能出现。示例中的
com.toshiba.ColourProfiles.update等printer-commands内容是厂商私有的 IPP 扩展命令,说明options是"可变键"的开放容器,代码中应按需取用、容错缺省,而不是假设固定键集合。
获取方式:webContents.getPrintersAsync()
获取 PrinterInfo[] 的入口是 webContents.getPrintersAsync():
contents.getPrintersAsync()
// Returns Promise<PrinterInfo[]> - Resolves with a PrinterInfo[]
该方法位于 web-contents.md 文档中,返回一个解析为 PrinterInfo[] 的 Promise。一个典型的"列出并选择打印机"用法如下:
const { BrowserWindow } = require('electron')
async function pickPrinter() {
const win = new BrowserWindow({ width: 800, height: 600 })
await win.loadURL('about:blank')
const printers = await win.webContents.getPrintersAsync()
// name 用于 print() 的 deviceName,displayName 用于展示给用户
console.table(printers.map((p) => ({
name: p.name,
displayName: p.displayName,
state: p.options['printer-state'] ?? 'N/A'
})))
// 静默打印到指定系统打印机(deviceName 必须是 name,而非 displayName)
win.webContents.print({
silent: true,
deviceName: printers[0].name
}, (success) => console.log('print success:', success))
}
这里有一个值得注意的历史演进:早期的 webContents.getPrinters() 同步方法已被废弃并最终移除,迁移方式在 breaking-changes.md 中有明确记载:
// 旧写法(已移除)
console.log(w.webContents.getPrinters())
// 新写法
w.webContents.getPrintersAsync().then((printers) => {
console.log(printers)
})
因此在当前仓库对应的版本中,枚举打印机应一律使用异步版本。
源码实现链路:从 JS Promise 到 Chromium 打印后端
结合仓库源码,getPrintersAsync() 的完整调用链可以分为三层:
第一层:JS 包装层。 lib/browser/api/web-contents.ts 中,WebContents.prototype.getPrintersAsync 直接委托给内部 native binding printing.getPrinterListAsync():
WebContents.prototype.getPrintersAsync = async function () {
// TODO(nornagon): this API has nothing to do with WebContents and should be
// moved.
if (printing.getPrinterListAsync) {
return printing.getPrinterListAsync()
} else {
console.error('Error: Printing feature is disabled.')
return []
}
}
从源码结构看,这一层还揭示了一个重要前提:整个打印功能受 ENABLE_PRINTING 构建开关控制。当 Electron 以禁用打印的方式构建时,printing.getPrinterListAsync 不存在,getPrintersAsync() 会打印 Error: Printing feature is disabled. 并返回空数组——这解释了为什么某些 Electron 构建(尤其是部分 Linux 打包场景)下打印机列表为空。
第二层:Native 异步枚举层。 shell/browser/api/electron_api_printing.cc 中的 GetPrinterListAsync 是该 API 的核心实现:
v8::Local<v8::Promise> GetPrinterListAsync(v8::Isolate* isolate) {
gin_helper::Promise<printing::PrinterList> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce([]() {
printing::PrinterList printers;
auto print_backend = printing::PrintBackend::CreateInstance(
g_browser_process->GetApplicationLocale());
printing::mojom::ResultCode code =
print_backend->EnumeratePrinters(printers);
if (code != printing::mojom::ResultCode::kSuccess)
LOG(INFO) << "Failed to enumerate printers";
return printers;
}),
base::BindOnce(
[](gin_helper::Promise<printing::PrinterList> promise,
const printing::PrinterList& printers) {
promise.Resolve(printers);
},
std::move(promise)));
return handle;
}
这段代码解释了 API 异步语义的由来:打印机枚举涉及与系统打印服务(Windows 的 spooler、CUPS 守护进程等)的交互,可能阻塞,因此被投递到线程池中执行(USER_VISIBLE 优先级、MayBlock 属性),完成后在主线程 resolve Promise。同时可以看到,枚举直接复用 Chromium 的 printing::PrintBackend(按浏览器进程 locale 创建实例)并调用 EnumeratePrinters——这也是为什么 Linux/macOS 上 options 会呈现 CUPS/IPP 属性名:平台差异发生在 Chromium 的打印后端内部,Electron 只负责透传。
第三层:结构体到 JS 对象的映射。 同文件中注册了 printing::PrinterBasicInfo 的 gin Converter,逐字段映射出 PrinterInfo 对象:
template <>
struct Converter<printing::PrinterBasicInfo> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const printing::PrinterBasicInfo& val) {
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
dict.Set("name", val.printer_name);
dict.Set("displayName", val.display_name);
dict.Set("description", val.printer_description);
dict.Set("options", val.options);
return dict.GetHandle();
}
};
这四行 dict.Set 与文档中定义的 4 个字段一一对应,options 则整体取自 PrinterBasicInfo 的平台相关属性表。
打印流程中的关联工具
PrinterInfo 不只是"列表展示"用途,它还与打印执行路径紧密关联。shell/browser/printing/printing_utils.h 中定义了若干围绕打印机选择的工具函数,从注释可以看出 Electron 对 deviceName 的健壮性处理:
IsDeviceNameValid:Chromium 本身不对device_name做有效性检查,传入不存在的设备名会导致崩溃,因此 Electron 会先校验;GetDeviceNameToUse:用户传了deviceName则校验后使用;未传则优先取系统默认打印机,没有默认打印机时退化为取列表中的第一台,列表为空则失败——这正是getPrintersAsync()返回结果可直接用于兜底选择的原因;GetPrinterDefaultPaperSize/GetDefaultPrinterDPI:为print({ usePrinterDefaultPageSize: true })等选项提供具体打印机的默认纸张与 DPI。
此外 shell/browser/printing/print_view_manager_electron.cc 接管了打印任务的生命周期(打印开始、结束、取消等事件),是 webContents.print() 回调机制的底层所在。
测试验证
仓库的规格测试印证了该 API 的行为契约。spec/api-web-contents-spec.ts 中:
ifdescribe(features.isPrintingEnabled())('getPrintersAsync()', () => {
afterEach(closeAllWindows);
it('can get printer list', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } });
await w.loadURL('about:blank');
const printers = await w.webContents.getPrintersAsync();
expect(printers).to.be.an('array');
});
});
两个细节值得注意:
- 测试块被
features.isPrintingEnabled()条件包裹,再次证明该 API 仅在启用了打印构建开关的环境下可用、可测; - 测试断言返回类型是数组,而非检查具体元素——由于打印机列表强依赖运行环境的实际硬件/驱动,这是此类系统 API 合理的测试粒度。
使用要点小结
- 字段分工:程序化选择打印机用
name,界面展示用displayName,机型说明参考description; - 平台差异:
options键集合与状态值语义因平台而异(Windows 对应 Win32 打印 API 定义,Linux/macOS 对应 CUPS 规范),跨平台逻辑需按平台分支并容忍缺键; - 值类型:
options内全部为字符串,数值/布尔需自行解析; - 构建前提:功能受
ENABLE_PRINTING编译开关保护,禁用打印的构建中getPrintersAsync()会返回空数组并输出错误日志; - 历史迁移:同步版
webContents.getPrinters()已移除,统一使用getPrintersAsync()。
掌握以上要点后,你可以在 Electron 应用中可靠地枚举系统打印机、解析平台相关的打印机状态,并将 name 正确接入 webContents.print() 的 deviceName 选项,实现完整的程序化打印流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00