Electron PrinterInfo 对象详解:通过 getPrintersAsync 获取与解析系统打印机列表
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
在 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 bindingprinting.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选项,实现完整的程序化打印流程。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考