此答案主要针对 What I'm Currently Trying To Overcome 中所述的问题。我已将 The Desired End Result 解释为背景信息,为您的紧迫问题提供背景信息(这真的很有趣/很有用,所以谢谢您)。
TL;DR
tell application "System Events"
set _P to a reference to (processes whose background only = false)
set _W to a reference to windows of _P
[_P's name, _W's size, _W's position]
end tell
这将为您提供每个application process 的size 和position 属性列表。下面是对脚本出错的位置和原因的相当冗长的解构;然后是在考虑其他同样可行的解决方案之后提出的解决方案,然后再确定上面的基本代码。改天当我不那么累的时候,我会尝试减少这个答案的冗长,但现在,我希望更深入的见解有所帮助。
问题
▸ 从你的脚本抛出的特定错误开始,有必要指出,一般来说,tell application 块不经常也不应该很少需要嵌套。您打开了一个tell 块来定位系统事件,这是获取process 名称所必需的;这就是您应该关闭 tell 块,或者在一行中使用 simple tell 命令的时候:
tell application "System Events" to set openApps to the name of every process...
(在这种情况下不需要end tell)。
但是,当您的 tell 块保持打开状态时,接下来的命令也会被定向到 系统事件。您的脚本显然找到的第一个 application process 属于 Finder,当您的脚本(在重复循环内)被指示到 tell application "Finder"(通过 checkApp 变量)时,错误被抛出是因为您实际上已经告诉 System Events 告诉 Finder 做某事,而 System Events 不了解如何与一个application 对象。
▸ 这将我们引向以下行,其中有几个与您的脚本相关的问题(加上一个更一般的值得注意的问题†,对此我留下了脚注):
tell application checkApp to get the bounds of the front window
此行仅适用于 (Apple)可编写脚本 的应用程序。并非所有应用程序都可以由 AppleScript 控制 - 这是应用程序制造商在为 macOS 开发软件时选择实施(或选择不实施,这种情况越来越常见)的一项功能。
那些可编写脚本的将(如果它们遵循 Apple 的指南)定义了 window 对象,每个对象都包含一个 bounds 属性。那些不可编写脚本的将没有这些。届时将引发另一个错误。
另一个“小”问题是并非所有background only 的进程都必须有窗口,因此front window。 Finder 绝不只是背景,但有时没有打开的窗口。因此,即使您遇到的错误已得到修复,如果没有打开的 Finder 窗口,这也是下一个出现的错误。
解决方案
虽然您无法获得属于不可编写脚本的应用程序的窗口的bounds 属性,但系统事件 可以检索属于application process 对象的某些属性。这与进程所属的应用程序本身是否可编写脚本无关,因为系统事件是我们的目标应用程序,是可编写脚本的,并且恰好有访问与每个进程的 window 对象相关的类似信息(NB. 请参见下面的脚注,但属于 process 的 window 对象不相同作为属于application 的window 对象,因此不能互换使用,它们的属性也不能互换使用。
虽然系统事件的processes 拥有的window 对象没有bounds 属性,但还有另外两个属性,它们合起来等价于bounds: position 和 size。 position 给出了窗口左上角相对于屏幕左上角的{X, Y} 坐标(在此上下文中定义为原点{0, 0}); size 给出了 {X, Y} 对,分别代表窗口的宽度和高度。
因此,给定特定窗口的假设bounds 属性值为{?, ?, ?, ?},与size: {?, ?} 和position: {?, ℎ} 的关系可以这样表示:
{?, ?, ?, ?} = {?, ?, ? + ?, ? + ℎ}
另一个考虑因素是获取实际具有窗口的进程列表。有多种方法可以做到这一点,每种方法都有优点和缺点,包括代码的简洁性、执行时间和检索到的窗口列表的准确性。
您检索由background only 区分的进程列表的原始方法是最快的方法之一,并且只有少数情况下漏报导致列表中的遗漏(即,注册为background only 的菜单栏应用程序但清楚有一个window;Instagram 应用程序 Flume 就是一个例子)。
您可以改为通过visible 属性进行区分,它同样快,但我觉得在应用程序被隐藏并且需要在记录其窗口属性之前取消隐藏的情况下不太合适;或者,同样,一些注册为background only,而不是visible,但显然是可见的菜单栏应用程序在前台有一个窗口。
遗憾的是,在任何情况下检索所有窗口最可靠的方法是相当慢,但确实会生成一个易于使用且不需要进一步处理的列表。
然而,在我们目前的情况下,我认为选择能够提高速度并适用于大多数应用程序的选项是明智的,即background only 过滤器。由于由此生成的列表会产生一些误报(例如,Finder),因此我们需要对列表进行一些处理,然后才能可靠地使用它。
这是检索包含 a) 命名进程列表的嵌套列表的代码; b) 每个指定进程的窗口大小列表; c) 每个命名进程的窗口位置列表:
tell application "System Events"
set _P to a reference to (processes whose background only = false)
set _W to a reference to windows of _P
[_P's name, _W's size, _W's position]
end tell
如果您关闭 Finder 窗口,您会看到它仍按名称显示在第一个列表中,但第二个和第三个列表有一个空列表 {},其中它的窗口大小和否则会是职位。因此,请务必在尝试引用每个子列表的项目之前进行一些检查。
将其与这个较慢但更准确的解决方案进行比较和对比:
tell application "System Events"
set _P to a reference to (processes whose class of window 1 is window)
set _W to a reference to windows of _P
[_P's name, _W's size, _W's position]
end tell
在我的系统上运行需要 20 倍的时间,产生的解决方案虽然可以识别菜单栏应用、常规应用和具有窗口的隐藏应用,但最终可能对您的最终目标至关重要。但如果您最常使用更常规的应用程序,那么很清楚哪种方法更合适。
†更普遍的潜在问题——它并不真正适用于你的脚本,但如果你尝试使用类似的技术,了解未来的脚本很有用— 使用变量作为引用 application 的一种方式,该 application 在编译时(在脚本运行之前)具有未确定的名称。
bounds 是所有可编写脚本的应用程序的windows 的一个相当普遍的属性,这就是为什么您(几乎)在这里摆脱这种技术。但是,如果您选择了Script Editor 明确不 包含的属性或对象类,AppleScript 将无法识别该术语并假定它只是一个变量名。为了识别特定于应用程序的术语,需要以某种形式引用该特定应用程序,或者通过tell application "Finder" to... 或将相关行包含在using terms from application "Finder" 块内。
一个好的经验法则是,应用程序通常需要在编译时知道并指定才能接收 AppleScript 命令。如果不为每个可能的应用程序使用if...then...else if... 系列条件块,就很难满足不同的选项。
这是一个令人沮丧的来源,尤其是当涉及到本质上看起来相似的应用程序时,而且具有类似的 AppleScript 字典,但仍不相互共享它们的术语以供一般使用。我特别想的是 Safari 和 Chrome,它们都有被称为 tabs 的对象,因此很容易忘记 Safari tab 仍然是 Chrome tab 的不同类对象,任何将通用代码写入脚本的任何尝试都将失败。