Skip to content

其他应用的窗口 ​

DesktopEngine.windows 告诉内容其他应用的窗口在哪里、哪个在前面,例如做一个在窗口标题栏上走的桌面伙伴:窗口最小化时跳下来,窗口被拖走时掉下来。它需要清单里的 window-positions 权限和 "apiVersion": 6:

json
"apiVersion": 6,
"permissions": ["window-positions"]
js
const windows = DesktopEngine.windows; // 没有权限时为 null

let list = windows ? windows.list() : [];
if (windows) windows.onchange = () => { list = windows.list(); };

list() 按从前到后的顺序给出窗口,第一个在其他所有窗口前面:

js
[
  { id: 4182, frame: { x: 120, y: 64, width: 900, height: 640 }, isActiveApp: true, isFullScreen: false, isFloating: false },
  { id: 391, frame: { x: 0, y: 120, width: 1728, height: 997 }, isActiveApp: false, isFullScreen: false, isFloating: false },
]
字段
id窗口存在期间不变,移动或移到最前面时也不变;关闭后重新打开的窗口、应用重新启动后的窗口是新的窗口,id 也不同
frame位置,和 DesktopEngine.Window、Screen 一样是全局坐标(点):原点在主显示器左上角,y 向下。标题栏在 frame 的顶部
isActiveApp属于用户正在用的应用(最前面的那个),例如优先站在用户正看着的窗口上
isFullScreen在全屏空间里占满所在的显示器:独自全屏,或者和另一个窗口一起分屏浏览(Split View)
isFloating浮在普通窗口上面:面板、画中画、应用保持置顶的窗口。浮动窗口排在列表前面
  • 只有每个显示器上当前显示的空间里的窗口,宽和高都至少 40 点。不包括你自己的窗口(以及 DesktopEngine 的任何窗口)、菜单栏、程序坞和桌面图标。最小化和隐藏的窗口不列出,正在关闭的窗口淡出到不透明度只剩一半时就离开列表,大约在关闭后十分之一秒。
  • 不包括窗口里显示的内容、它的标题和它属于哪个应用:用户允许的只是位置。
  • 窗口移动、改变大小、打开、关闭、最小化、移到最前面,别的应用成为当前应用,或者切换了空间时触发 change。它不带数据:调用 list() 读取。来得比内容处理得快的变化会合并成一次。只有窗口真的变了才会触发:加上监听(或者移除后再加上)不会触发它。
  • 调度中心打开期间,窗口显示为缩小的样子,list() 和 visibleTopEdges() 一直给出调度中心打开之前的列表,直到它关闭,不管有没有监听 change。系统暂时给不出窗口位置时也一样:保留上一次的列表。
  • 窗口的动画(最小化、进入全屏)期间 frame 跟着动画变化,所以站在上面的伙伴会先被带着走,然后才掉下来。
  • 没有权限时(没有声明,或者用户拒绝了)DesktopEngine.windows 为 null,在 API 版本低于 6 的应用里是 undefined:为这种情况准备好,比如伙伴只在屏幕底部活动。应用详情栏里的预览里它也是 null(那里的 launchOptions.permissions 不含 window-positions)。

频率和能耗 ​

只有内容监听 change 时应用才会查看窗口,大约每秒 10 次;在某个窗口上按着鼠标键时(用户可能在拖动或缩放它),每秒最多 60 次,这时只重新读指针处的窗口。在监听的内容全部停止绘制或被暂停时(窗口都看不见、用户选了「暂停所有内容」、显示器睡眠,见性能和暂停规则),应用每秒只查看一次。不需要时移除监听(windows.onchange = null、removeEventListener、destroy())。没有监听时,list() 和 visibleTopEdges() 在调用时读取窗口,在你的代码返回之前一直给出同一个列表。

只在需要跟着窗口变化时监听。伙伴在地面上时不需要:决定要不要跳上去时、用户把它拖动后松手时,调用一次 visibleTopEdges() 就够了。站在窗口上或正跳向窗口时才监听,好跟着窗口走、窗口没了就掉下来,回到地面就停止监听。没有内容监听时,用户拖动窗口完全没有开销。没有监听时每次读取都要询问系统,所以在要做决定时读,不要每帧都读。

不要每次 change 都单独重绘:保存列表,在自己的动画里使用,这样伙伴静静站在一个不动的窗口上时就没有开销。

站在标题栏上 ​

桌面伙伴在所有窗口之上,所以它如果站在一个被前面窗口挡住的标题栏上,看起来就像站在前面那个窗口上。visibleTopEdges() 只给出顶边没被前面窗口挡住的部分,按从前到后的顺序:

js
const edges = windows.visibleTopEdges();
// [{ id: 4182, x: 120, y: 64, width: 900 }, { id: 391, x: 0, y: 120, width: 120 }, { id: 391, x: 1020, y: 120, width: 708 }]

被前面窗口截成两段的顶边有两个部分,比如这里的 391;全屏窗口(包括分屏浏览中的)没有这样的部分:它的顶边就是显示器的顶边。然后在动画的每一步:

  • 站在窗口上:按 id 重新找到它在伙伴脚下的那一段。窗口移动了就跟着移动;找不到了(关闭、最小化、被挡住,或者伙伴走过了头),就开始下落。
  • 下落:落在上一步脚的位置和现在脚的位置之间最高的那一段上,或者显示器 visibleFrame 的底部。

在网页上 ​

浏览器看不到 Mac 的窗口。desktopengine dev --web 会给有这个权限的内容在页面上放一个模拟的应用窗口:拖标题栏移动它,拖右下角改变大小,点红色圆点关闭,用菜单栏的 New Window 再开几个。点一下模拟的窗口,它会移到最前面,成为当前应用的窗口(isActiveApp);点一下桌面或桌面上的小组件后,这些窗口都不属于当前应用;点浮在所有窗口之上的小组件或桌面伙伴不会改变这一点。商店的预览里没有这些窗口:list() 是空的。

SDK 以 Apache License 2.0 发布