news 2026/9/19 17:32:16

NW.js Screen API 完全指南:屏幕信息查询、显示事件监听与桌面捕获

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NW.js Screen API 完全指南:屏幕信息查询、显示事件监听与桌面捕获

NW.js Screen API 完全指南:屏幕信息查询、显示事件监听与桌面捕获

【免费下载链接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.项目地址: https://gitcode.com/gh_mirrors/nw/nw.js

nw.Screen是 NW.js 暴露给 Node.js/浏览器混合环境的屏幕管理单例对象,用于读取物理屏幕与工作区信息、监听屏幕增删与分辨率变化事件,并通过chooseDesktopMediaDesktopCaptureMonitor两条路径实现桌面/窗口捕获(屏幕共享)。读完本文,你将掌握从初始化、事件订阅到getUserMedia采集桌面的完整实战链路,并理解其底层与 Chromiumgfx::Display及 WebRTC Desktop Capture 的对接原理。

概述:Screen 是一个单例 EventEmitter

在 NW.js 中,nw.ScreenEventEmitter的实例,因此可以使用nw.Screen.on(...)响应系统原生屏幕事件(显示器拔插、分辨率/排列变化等)。

与大多数 NW.js API 不同,Screen是一个单例对象,必须在调用任何nw.Screen方法之前,先通过nw.Screen.Init()初始化一次。从 screen.js 的实现可以看到:Init()内部用screenInstance缓存实例,只有第一次调用才会真正new Screen(),之后exports.Screen被替换为实例对象,重复调用是幂等的:

exports.Screen = { Init: function() { if (screenInstance == null) { screenInstance = new Screen(); } exports.Screen = screenInstance; return screenInstance; } };

Screen对象通过nw.allocateObject分配原生对象句柄,并继承exports.Base;其on/addListener被重写,只允许监听四个事件:displayBoundsChangeddisplayAddeddisplayRemoved(以及内部使用的chooseDesktopMedia),监听其他事件名会直接抛出TypeError

Screen.prototype.on = Screen.prototype.addListener = function(ev, callback) { if ( ev != "displayBoundsChanged" && ev != "displayAdded" && ev != "displayRemoved" && ev != "chooseDesktopMedia") throw new TypeError('only following event can be listened: displayBoundsChanged, displayAdded, displayRemoved'); ... };

同时该重写实现了懒注册:第一个监听器挂上时才调用nw.callStaticMethodSync('Screen', 'AddScreenChangeCallback', [this.id])向原生侧注册屏幕变化回调,最后一个监听器移除时自动调用RemoveScreenChangeCallback注销,避免空转的系统监听开销。

基本用法(Synopsis)

文档给出的标准启动与监听示例:

// init must be called once during startup, before any function to nw.Screen can be called nw.Screen.Init(); var screenCB = { onDisplayBoundsChanged: function(screen) { console.log('displayBoundsChanged', screen); }, onDisplayAdded: function(screen) { console.log('displayAdded', screen); }, onDisplayRemoved: function(screen) { console.log('displayRemoved', screen) } }; // listen to screen events nw.Screen.on('displayBoundsChanged', screenCB.onDisplayBoundsChanged); nw.Screen.on('displayAdded', screenCB.onDisplayAdded); nw.Screen.on('displayRemoved', screenCB.onDisplayRemoved);

提示:nw.Screen.Init()建议放在应用启动阶段(如package.jsonmain脚本或dom-ready事件中)执行一次即可,无需也不应重复初始化。仓库的冒烟测试 screen-events/index.html 演示了先Init()再绑定三个显示事件的标准流程。

Screen.Init()

初始化Screen单例对象,整个应用生命周期只需调用一次。任何nw.Screen的方法或属性访问(尤其是screens与事件监听)都应在Init()之后进行。

Screen.screens

nw.Screen.screens返回连接到计算机的显示器数组(数组长度即屏幕数量)。在 JS 侧这是一个 getter,内部通过nw.callStaticMethodSync('Screen', 'GetScreens', [])同步获取并JSON.parse得到对象数组(见 screen.js)。

每个screen对象具有如下结构:

screen { // unique id for a screen id: int, // physical screen resolution, can be negative, not necessarily start from 0,depending on screen arrangement bounds: { x: int, y: int, width: int, height: int }, // useable area within the screen bound work_area: { x: int, y: int, width: int, height: int }, scaleFactor: float, isBuiltIn: bool, rotation: int, touchSupport: int }

字段说明:

  • id:屏幕的唯一标识。
  • bounds:物理屏幕分辨率(逻辑像素)。注意坐标可以为负、不一定从 0 开始——取决于多显示器的排列方式(例如副屏位于主屏左侧时x为负值)。
  • work_area:屏幕边界内的可用区域(扣除任务栏/ Dock 等系统 UI 后的区域)。
  • scaleFactor:设备缩放因子(如 Retina 屏为 2.0),对应高分屏缩放。
  • isBuiltIn:是否为内置屏幕(如笔记本自带面板)。
  • rotation:屏幕旋转角度(度)。
  • touchSupport:触摸支持级别。

这些字段在原生侧由 screen.cc 的DisplayToJSON()从 Chromiumgfx::Display直接序列化而来:bounds()/work_area()取自gfx::RectscaleFactor对应device_scale_factor()isBuiltIn对应IsInternal()rotation对应RotationAsDegree()GetScreens则遍历gfx::Screen::GetNativeScreen()->GetAllDisplays()拼接 JSON 数组(见 screen.cc)。Display的完整字段定义也可在 nw_screen.idl 中查看。

Screen.chooseDesktopMedia (sources, callback)

通过系统原生选择器让用户选择要共享的屏幕或窗口,然后回调返回可用于getUserMediastreamId

  • sources{String[]}:源类型数组,本 API 支持两种取值:"screen""window"
  • callback{Function}:携带所选streamId的回调函数;若执行失败或已存在活动的选择会话,则streamIdfalse

注意:该功能依赖系统级选择器 GUI,目前仅在 Windows、OS X 以及部分 Linux 发行版上可用

示例:

nw.Screen.Init(); // you only need to call this once nw.Screen.chooseDesktopMedia(["window","screen"], function(streamId) { var vid_constraint = { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: streamId, maxWidth: 1920, maxHeight: 1080 }, optional: [] }; navigator.webkitGetUserMedia({audio: false, video: vid_constraint}, success_func, fallback_func); } );

在拿到streamId后,将其填入getUserMedia视频约束的chromeMediaSource: 'desktop'chromeMediaSourceId: streamId,即可把屏幕/窗口画面作为媒体流交给success_func使用(如渲染到<video>标签、录制或推流)。

底层实现要点(见 desktop_capture_api.cc):

  • 原生侧遍历sources数组,识别"window""screen"分别置位show_windows/show_screens;若两者都不满足,直接返回错误"At least one source type must be specified."
  • 通过 WebRTC 的webrtc::ScreenCapturerwebrtc::WindowCapturer构建NativeDesktopMediaList,再创建DesktopMediaPicker弹出选择器。DesktopMediaPicker只在TOOLKIT_VIEWS(Aura Linux/Windows)或OS_MAC下实现,其他平台返回"Desktop Capture API is not yet implemented for this platform."——这正是文档注明平台限制的原因。
  • 回调结果经ChooseDesktopMediaCallbackchooseDesktopMedia事件形式派发给 JS 侧(见 screen.cc),screen.jschooseDesktopMedia返回true并注册一次性this.once('chooseDesktopMedia', callback)监听(见 screen.js)。
  • 若上一次选择会话(gpDCCDMF)尚未结束,再次调用会直接返回false,JS 侧chooseDesktopMedia也随之返回false而不触发回调(见 screen.cc)。

显示事件

以下三个事件均通过 Chromium 的gfx::DisplayObserver驱动:原生侧JavaScriptDisplayObserver在屏幕指标变化、新增、移除时把DisplayToJSON序列化后的屏幕对象以事件形式派发到 JS(见 screen.cc)。

Event: displayBoundsChanged(screen)

屏幕分辨率或排列(布局)发生变化时触发。回调携带 1 个参数screen,格式同 Screen.screens。

Event: displayAdded (screen)

检测到新的屏幕接入时触发。回调携带 1 个参数screen,格式同上。

Event: displayRemoved (screen)

已有屏幕被移除时触发。回调携带 1 个参数screen(被移除屏幕的信息),格式同上。

Screen.DesktopCaptureMonitor

Screen.DesktopCaptureMonitor提供了与chooseDesktopMedia类似的能力,但不带系统 GUI——适合希望自行实现选择 UI 的场景(如自定义缩略图选择面板)。它同样是EventEmitter实例,可用Screen.DesktopCaptureMonitor.on()监听事件。

Synopsis

var dcm = nw.Screen.DesktopCaptureMonitor; nw.Screen.Init(); dcm.on("added", function (id, name, order, type) { //select first stream and shutdown var constraints = { audio: { mandatory: { chromeMediaSource: "system", chromeMediaSourceId: dcm.registerStream(id) } }, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: dcm.registerStream(id) } } }; // TODO: call getUserMedia with contraints dcm.stop(); }); dcm.on("removed", function (id) { }); dcm.on("orderchanged", function (id, new_order, old_order) { }); dcm.on("namechanged", function (id, name) { }); dcm.on("thumbnailchanged", function (id, thumbnail) { }); dcm.start(true, true);

事件与原生侧的对应关系可在 nw_screen_api.cc 中找到:added对应OnSourceAddedremoved对应OnSourceRemovedorderchanged对应OnSourceMovednamechanged对应OnSourceNameChangedthumbnailchanged对应OnSourceThumbnailChanged;事件名与参数定义见 nw_screen.idl。

Screen.DesktopCaptureMonitor.started

布尔值,表示DesktopCaptureMonitor是否已处于启动监控状态。

Screen.DesktopCaptureMonitor.start(should_include_screens, should_include_windows)

  • should_include_screens{Boolean}:是否监控屏幕源。
  • should_include_windows{Boolean}:是否监控窗口源。

启动系统监控并开始触发事件。注意:监控运行期间屏幕画面可能出现闪烁,因此应尽量缩短监控窗口期。

Screen.DesktopCaptureMonitor.stop()

停止监控。选定一个流之后应立即调用stop(),避免持续占用捕获资源与引起画面闪烁。

Screen.DesktopCaptureMonitor.registerStream(id)

将事件回调中拿到的源id注册为有效的流 ID,并返回可直接填入getUserMedia约束chromeMediaSourceId的字符串。用法见上文 Synopsis。

Event: added (id, name, order, type, primary)

警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。

当新增一个可捕获源时触发。参数:

  • id{String}:媒体 ID。需调用registerStream(id)得到可用于getUserMedia()的合法流 ID。
  • name{String}:窗口标题或屏幕名称。
  • order{Integer}:窗口的 Z 轴顺序;若选择了屏幕,屏幕源会排在最前。
  • type{String}:流类型,取值为"screen""window""other""unknown"
  • primary{Boolean}仅 Windows 有效,源为主显示器时该值为true

Event: removed (order)

当某个源不再可捕获(被移除)时触发。参数:

  • order{Integer}:被移除媒体源在列表中的顺序。

Event: orderchanged (id, new_order, old_order)

警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。

当某个源的 Z 轴顺序改变(例如窗口被聚焦/失焦导致层级变化)时触发。参数:

  • id{String}:Z 轴顺序发生变化的屏幕或窗口的媒体 ID。
  • new_order{Integer}:新的 Z 轴顺序。
  • old_order{Integer}:旧的 Z 轴顺序。

Event: namechanged (id, name)

警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。

当源名称改变(如窗口标题变化)时触发。参数:

  • id{String}:名称发生变化的屏幕或窗口的媒体 ID。
  • name{String}:该屏幕或窗口的新名称。

Event: thumbnailchanged (id, thumbnail)

警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。

当源的缩略图更新时触发。参数:

  • id{String}:缩略图发生更新的屏幕或窗口的媒体 ID。
  • thumbnail{String}:Base64 编码的 PNG 缩略图数据(可直接赋给<img src="data:image/png;base64,...">渲染)。

最佳实践与注意事项

  1. Init()再使用:所有nw.Screen操作都依赖单例初始化;Init()幂等,可放心在启动阶段调用一次。
  2. 事件监听白名单nw.Screen.on只接受displayBoundsChangeddisplayAddeddisplayRemoved三个显示事件,传入其他事件名会抛TypeError;桌面捕获相关事件请挂在nw.Screen.DesktopCaptureMonitor上。
  3. 多屏坐标boundswork_area的坐标原点不一定是(0, 0),拼接多屏布局时应以实际返回的x/y为准(可为负)。
  4. chooseDesktopMedia的一次性约束:选择会话期间再次调用会返回false,回调不会触发;应避免重复发起选择。
  5. 监控后及时stop()DesktopCaptureMonitor运行期间屏幕可能闪烁,选定流后应立即停止;同时可先通过thumbnailchanged拿到缩略图渲染自定义选择 UI。
  6. 平台限制chooseDesktopMedia的桌面选择器仅在 Windows、macOS 与部分 Linux(Aura)发行版上可用;跨平台应用需做好降级或平台分支。

延伸阅读

  • 本文主题的官方参考文档:Screen.md
  • JS 绑定实现:src/api/screen/screen.js
  • 原生GetScreens/事件派发实现:src/api/screen/screen.cc
  • 桌面捕获(chooseDesktopMedia)实现:src/api/screen/desktop_capture_api.cc
  • API 类型定义(IDL):src/api/nw_screen.idl
  • 屏幕事件绑定的冒烟测试:test/sanity/screen-events/index.html
  • 0.12 到 0.13 的迁移说明(涉及桌面捕获行为变更):From 0.12 to 0.13

【免费下载链接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.项目地址: https://gitcode.com/gh_mirrors/nw/nw.js

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

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

视觉伺服控制结构解析:IBVS、PBVS与2.5D混合方法的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 17:24:34

项目经历怎么写面试官才记得住?简历与面试表达核心技巧

1. 为什么你的项目经历&#xff0c;面试官总是记不住先讲个真实的场景。我前几年参与校招和社招面试&#xff0c;一天面七八个人&#xff0c;每个人的简历都差不多厚。说实话&#xff0c;到下午三四点的时候&#xff0c;大部分候选人的学校、专业、实习公司我已经完全混淆了&am…

作者头像 李华
网站建设 2026/9/19 17:24:27

零基础学AI:从Python基础到大模型应用与部署的完整路线

作为一直在一线折腾AI的人&#xff0c;我经常被问到同一个问题&#xff1a;“我想学AI&#xff0c;该从哪里开始&#xff1f;”每次看到那种“三个月从入门到精通”的广告&#xff0c;我都替读者捏把汗。AI学习这条路&#xff0c;信息量太大&#xff0c;热点换得太快&#xff0…

作者头像 李华
网站建设 2026/9/19 17:23:56

Vibe Coding实战:从零到上架App Store的完整工作流

2024年底&#xff0c;我决定把躺在我备忘录里一年多的想法做成一个真正的应用。过去我试过好几次&#xff0c;每次都是打开Xcode、新建工程、写几个页面之后就搁置了&#xff0c;原因出奇一致&#xff1a;白天上班已经写了大量代码&#xff0c;回到家实在没有精力再为一个“小玩…

作者头像 李华
网站建设 2026/9/19 17:23:26

微信小程序测试报告:从数据采集到防坑验证的实践指南

简介&#xff1a;微信小程序测试报告&#xff08;Word文档&#xff09;为小程序开发、测试及项目管理人员提供了一套可直接使用或参考的测试文档范例。报告以某商城微信小程序为对象&#xff0c;系统梳理了功能、性能、安全、兼容性、UI与用户体验等测试维度的完整流程。资源仅…

作者头像 李华