news 2026/9/7 23:08:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Electron PrinterInfo 对象详解:通过 getPrintersAsync 获取与解析系统打印机列表

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 个字段:

字段类型说明
namestring操作系统层面识别的打印机名称(system-defined name)
displayNamestring在打印预览(Print Preview)中展示的打印机名称
descriptionstring对打印机类型的更长描述,例如机型信息
optionsObject包含数量不定的、平台相关的打印机信息键值对

namedisplayName的区别是使用中最容易踩坑的一点: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 字段:平台相关的扩展信息

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 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'); }); });

两个细节值得注意:

  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选项,实现完整的程序化打印流程。

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 23:08:06

CLI与JSON-RPC:AI系统集成的高效接口方案

1. CLI 作为 AI 连接世界的终极接口&#xff1a;为什么 OpenClaw 作者认为应该忘掉 MCP最近在 AI 开发工具领域&#xff0c;OpenClaw 作者提出的"CLI 才是 AI 连接世界的终极接口"这一观点引发了广泛讨论。作为一名长期工作在 AI 工程化一线的开发者&#xff0c;我深…

作者头像 李华
网站建设 2026/9/7 23:07:45

Spring Data Sort转QueryDSL OrderSpecifier通用工具类实现

做后端的朋友应该都有这种经历&#xff1a;接口明明收的是Pageable/Sort&#xff0c;走 JPA Repository 的时候一切正常&#xff0c;但是一旦切到 QueryDSL 自定义查询&#xff0c;Sort就使不上劲了。Spring Data 的Sort和 QueryDSL 的OrderSpecifier是两套排序模型&#xff0c…

作者头像 李华
网站建设 2026/9/7 23:03:16

移动端适配基石:彻底搞懂 Viewport 与视口单位

做移动端页面调试时&#xff0c;你一定遇到过这样的场景&#xff1a;PC 上用 DevTools 模拟手机一切正常&#xff0c;真机一打开&#xff0c;字小到要双指放大才能看清&#xff0c;页面横向还多出一截&#xff0c;底部按钮被地址栏遮得严严实实。为了这些移动浏览问题&#xff…

作者头像 李华
网站建设 2026/9/7 23:02:39

AI Agent安全沙箱:容器与微虚拟机隔离技术解析

1. AI Agent代码执行沙箱的核心挑战 在AI Agent开发领域&#xff0c;代码执行沙箱是确保系统安全的关键组件。我经历过多次由于沙箱隔离不足导致的安全事故&#xff0c;最严重的一次是恶意代码通过AI Agent逃逸到宿主系统&#xff0c;删除了整个数据库。这种惨痛教训让我深刻认…

作者头像 李华
网站建设 2026/9/7 23:02:15

COMSOL多物理场仿真从建模到交付:耦合、网格与不收敛排查全攻略

1. 多物理场仿真为什么成了工程师的硬需求&#xff0c;代做市场又从哪来接触COMSOL仿真代做这个圈子&#xff0c;已经有年头了。最开始只是因为自己在课题组里用COMSOL做项目&#xff0c;后来陆陆续续有人找过来问"能不能帮我算个东西"&#xff0c;再后来干脆形成了一…

作者头像 李华