首页
/ Electron PrinterInfo 对象详解:通过 getPrintersAsync 获取与解析系统打印机列表

Electron PrinterInfo 对象详解:通过 getPrintersAsync 获取与解析系统打印机列表

2026-09-06 14:41:02作者:温艾琴Wonderful

在 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 包含数量不定的、平台相关的打印机信息键值对

namedisplayName 的区别是使用中最容易踩坑的一点:name 是操作系统内部名称,通常是带下划线、无空格的机器标识;displayName 才是给用户看的友好名称。当你调用 webContents.print({ deviceName }) 指定打印设备时,传入的必须是 name(系统定义名),而不是 displayName(friendly name)。这一约束在官方文档的 contents.print() 一节中同样有强调:

deviceName string (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 字段:平台相关的扩展信息

optionsPrinterInfo 中信息量最大的部分,其键值数量和内容因平台而异。官方示例如下,展示了一台 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-urijob-cancel-afterprinter-state-reasonsprinter-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.updateprinter-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');
  });
});

两个细节值得注意:

  1. 测试块被 features.isPrintingEnabled() 条件包裹,再次证明该 API 仅在启用了打印构建开关的环境下可用、可测;
  2. 测试断言返回类型是数组,而非检查具体元素——由于打印机列表强依赖运行环境的实际硬件/驱动,这是此类系统 API 合理的测试粒度。

使用要点小结

  • 字段分工:程序化选择打印机用 name,界面展示用 displayName,机型说明参考 description
  • 平台差异options 键集合与状态值语义因平台而异(Windows 对应 Win32 打印 API 定义,Linux/macOS 对应 CUPS 规范),跨平台逻辑需按平台分支并容忍缺键;
  • 值类型options 内全部为字符串,数值/布尔需自行解析;
  • 构建前提:功能受 ENABLE_PRINTING 编译开关保护,禁用打印的构建中 getPrintersAsync() 会返回空数组并输出错误日志;
  • 历史迁移:同步版 webContents.getPrinters() 已移除,统一使用 getPrintersAsync()

掌握以上要点后,你可以在 Electron 应用中可靠地枚举系统打印机、解析平台相关的打印机状态,并将 name 正确接入 webContents.print()deviceName 选项,实现完整的程序化打印流程。

登录后查看全文
热门项目推荐
相关项目推荐