AppleScript - 获取每个打开的边界 Window
AppleScript - Get the Bounds of Every open Window
我整天都在玩弄这个。目标是生成一个生成更多 AppleScript 的 AppleScript。我会更详细地解释。
预期的最终结果:在安排好您的 windows 之后,您可以按照自己的喜好启动此脚本。这会将必要的脚本复制到您的剪贴板,以自动启动、定位和调整应用程序 windows 到当前配置。这样我就可以将脚本发送给其他人,然后他们可以在启动此脚本后设计自己的自定义布局,然后可以将其粘贴到脚本编辑器中,或者可能制作成服务并使用 Automator 绑定到热键。
我目前正在努力克服的问题:我似乎无法列出每个 window 的界限。我目前运行这个脚本。
tell application "System Events"
set openApps to name of every process whose background only is false
repeat with theItem in openApps
set checkApp to theItem
tell application checkApp to get the bounds of the front window
end repeat
end tell
这每次都无一例外地吐出以下错误:
error "System Events got an error: Can’t get application \"Finder\"." number -1728 from application "Finder"
我并不是要有人为我解决整个问题。尽管对此事的任何建议总是受到赞赏。当前的难题只是将每个 window 的边界设置为变量,以便在脚本的其他地方使用。
此回答主要针对 What I'm Currently Trying To Overcome
下所述的问题。我将 The Desired End Result
解释为背景信息,为您的紧迫问题提供背景信息(这确实是 interesting/useful 提供的,谢谢)。
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
属性列表。以下是对脚本出错的位置和原因的相当详细的解构;在确定上面的基本代码之前,在考虑其他同样可行的解决方案之后提出了一个建议的解决方案。我会在改天尝试 trim 这个答案的冗长,当我不那么累的时候,但现在,我希望更深入的洞察力有所帮助。
问题
▸ 从您的脚本抛出的特定错误开始,有必要指出,一般来说,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
的进程都必然具有 windows,因此 front window
。 Finder 永远不会只有后台,但有时没有打开 windows。因此,即使您得到的错误已得到修复,如果没有打开 Finder windows.
,这也是下一个出现的错误
一个解决方案
虽然您无法获得属于非脚本应用程序的 window 的 bounds
属性,但 System Events 可以检索属于 application process
对象的一些属性。这与进程所属的应用程序本身是否可编写脚本无关,因为 System Events 是我们的目标应用程序, 是 可编写脚本,并且恰好可以访问与每个进程的 window
对象有关的类似信息(NB。 请参阅下面的脚注,但 window
对象属于 process
与属于 application
的 window
对象 不 相同,因此不能互换使用,它们的属性也不能互换)。
虽然 System Events 的 processes
拥有的 window
个对象没有 bounds
属性,但是有另外两个属性,它们一起等同于 bounds
:position
和 size
。 position
给出 window 的左上角相对于屏幕左上角的 {X, Y}
坐标(在此上下文中定义为 {0, 0}
); size
给出 {X, Y}
对尺寸,分别表示 window 的宽度和高度。
因此,给定特定 window 的假设 bounds
属性 值 {, , , }
,与 size: {, }
和 position: {, ℎ}
的关系可以这样表示:
{, , , } = {, , + , + ℎ}
另一个考虑因素是获取实际具有 windows 的进程列表。有多种方法可以做到这一点,每种方法都有优点和缺点,包括代码的简洁性、执行时间和 windows.
检索列表的准确性。
您检索由 background only
区分的进程列表的原始方法是最快的方法之一,只有少数情况下漏报会导致列表遗漏(即,菜单栏应用程序注册为 background only
但显然有一个 window
;Instagram 应用 Flume 就是一个例子)。
您可以通过 visible
属性 来区分,这同样快,但我觉得在隐藏应用程序并且需要在记录其 [ 之前取消隐藏的情况下不太合适=228=]属性;或者,再一次,一些菜单栏应用程序注册为 background only
,而不是 visible
,但显然 是 visible_ with a window in the foreground.
遗憾的是,在任何情况下检索所有 windows 的最可靠方法非常慢,但确实会生成一个易于使用且不需要进一步处理的列表。
然而,在我们目前的情况下,我认为选择提供速度并且适用于大多数应用程序的选项是明智的,这就是您的 background only
过滤器。由于由此产生的列表会产生一些误报(例如 Finder),我们需要稍微处理一下列表才能可靠地使用它。
这是检索包含 a) 命名进程列表的嵌套列表的代码; b) 每个指定进程的 window 大小列表;和 c) 每个命名进程的 window 个位置列表:
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 windows,您会看到它仍按名称出现在第一个列表中,但第二个和第三个列表为空列表{}
其 windows' 的大小和位置在其他情况下。因此,请务必在尝试引用每个子列表的项目之前进行一些检查。
将其与这个较慢但更准确的解决方案进行比较和对比:
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
在我的系统上 运行 花费的时间是 运行 的二十倍,产生了一个虽然可以识别菜单栏应用程序、常规应用程序和具有 windows 的隐藏应用程序的解决方案,但最终可能会对您的最终目标至关重要。但如果你经常使用更常规的应用程序,那么哪种方法更合适就很清楚了。
†更普遍的潜在问题——它并不真正适用于您目前的脚本,但对于了解未来的脚本是否有用您尝试使用类似的技术 - 是使用变量作为引用编译时具有未确定名称的 application
的方法(在脚本为 运行 之前)。
bounds
是所有可编写脚本的应用程序 windows
中相当普遍的 属性,这就是为什么您(几乎)在这里摆脱这种技术的原因。但是,如果您选择 Script Editor 明确 不 包含的 属性 或对象 class,AppleScript 将不会识别术语并假设它只是一个变量名。为了识别特定于应用程序的术语,需要以某种形式引用该特定应用程序,可以通过 tell application "Finder" to...
或将相关行括在 using terms from application "Finder"
块中。
一个好的经验法则是应用程序通常需要在编译时知道并指定以便接收 AppleScript 命令。如果不为每个可能的应用程序使用 if...then...else if...
系列条件块,就没有简单的方法来满足不同的选择。
这是令人沮丧的根源,尤其是当涉及到看似本质上相似并且具有相似的 AppleScript 词典,但仍不彼此共享其术语的应用程序时一般使用。我特别想到 Safari 和 Chrome,它们都有被称为 tabs
的对象,因此很容易忘记 Safari tab
仍然是 不同的 class 对象 Chrome tab
,任何将通用代码编写到脚本中的任何一个或两个的尝试都将失败。
我整天都在玩弄这个。目标是生成一个生成更多 AppleScript 的 AppleScript。我会更详细地解释。
预期的最终结果:在安排好您的 windows 之后,您可以按照自己的喜好启动此脚本。这会将必要的脚本复制到您的剪贴板,以自动启动、定位和调整应用程序 windows 到当前配置。这样我就可以将脚本发送给其他人,然后他们可以在启动此脚本后设计自己的自定义布局,然后可以将其粘贴到脚本编辑器中,或者可能制作成服务并使用 Automator 绑定到热键。
我目前正在努力克服的问题:我似乎无法列出每个 window 的界限。我目前运行这个脚本。
tell application "System Events"
set openApps to name of every process whose background only is false
repeat with theItem in openApps
set checkApp to theItem
tell application checkApp to get the bounds of the front window
end repeat
end tell
这每次都无一例外地吐出以下错误:
error "System Events got an error: Can’t get application \"Finder\"." number -1728 from application "Finder"
我并不是要有人为我解决整个问题。尽管对此事的任何建议总是受到赞赏。当前的难题只是将每个 window 的边界设置为变量,以便在脚本的其他地方使用。
此回答主要针对 What I'm Currently Trying To Overcome
下所述的问题。我将 The Desired End Result
解释为背景信息,为您的紧迫问题提供背景信息(这确实是 interesting/useful 提供的,谢谢)。
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
属性列表。以下是对脚本出错的位置和原因的相当详细的解构;在确定上面的基本代码之前,在考虑其他同样可行的解决方案之后提出了一个建议的解决方案。我会在改天尝试 trim 这个答案的冗长,当我不那么累的时候,但现在,我希望更深入的洞察力有所帮助。
问题
▸ 从您的脚本抛出的特定错误开始,有必要指出,一般来说,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
的进程都必然具有 windows,因此 front window
。 Finder 永远不会只有后台,但有时没有打开 windows。因此,即使您得到的错误已得到修复,如果没有打开 Finder windows.
一个解决方案
虽然您无法获得属于非脚本应用程序的 window 的 bounds
属性,但 System Events 可以检索属于 application process
对象的一些属性。这与进程所属的应用程序本身是否可编写脚本无关,因为 System Events 是我们的目标应用程序, 是 可编写脚本,并且恰好可以访问与每个进程的 window
对象有关的类似信息(NB。 请参阅下面的脚注,但 window
对象属于 process
与属于 application
的 window
对象 不 相同,因此不能互换使用,它们的属性也不能互换)。
虽然 System Events 的 processes
拥有的 window
个对象没有 bounds
属性,但是有另外两个属性,它们一起等同于 bounds
:position
和 size
。 position
给出 window 的左上角相对于屏幕左上角的 {X, Y}
坐标(在此上下文中定义为 {0, 0}
); size
给出 {X, Y}
对尺寸,分别表示 window 的宽度和高度。
因此,给定特定 window 的假设 bounds
属性 值 {, , , }
,与 size: {, }
和 position: {, ℎ}
的关系可以这样表示:
{, , , } = {, , + , + ℎ}
另一个考虑因素是获取实际具有 windows 的进程列表。有多种方法可以做到这一点,每种方法都有优点和缺点,包括代码的简洁性、执行时间和 windows.
检索列表的准确性。您检索由 background only
区分的进程列表的原始方法是最快的方法之一,只有少数情况下漏报会导致列表遗漏(即,菜单栏应用程序注册为 background only
但显然有一个 window
;Instagram 应用 Flume 就是一个例子)。
您可以通过 visible
属性 来区分,这同样快,但我觉得在隐藏应用程序并且需要在记录其 [ 之前取消隐藏的情况下不太合适=228=]属性;或者,再一次,一些菜单栏应用程序注册为 background only
,而不是 visible
,但显然 是 visible_ with a window in the foreground.
遗憾的是,在任何情况下检索所有 windows 的最可靠方法非常慢,但确实会生成一个易于使用且不需要进一步处理的列表。
然而,在我们目前的情况下,我认为选择提供速度并且适用于大多数应用程序的选项是明智的,这就是您的 background only
过滤器。由于由此产生的列表会产生一些误报(例如 Finder),我们需要稍微处理一下列表才能可靠地使用它。
这是检索包含 a) 命名进程列表的嵌套列表的代码; b) 每个指定进程的 window 大小列表;和 c) 每个命名进程的 window 个位置列表:
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 windows,您会看到它仍按名称出现在第一个列表中,但第二个和第三个列表为空列表{}
其 windows' 的大小和位置在其他情况下。因此,请务必在尝试引用每个子列表的项目之前进行一些检查。
将其与这个较慢但更准确的解决方案进行比较和对比:
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
在我的系统上 运行 花费的时间是 运行 的二十倍,产生了一个虽然可以识别菜单栏应用程序、常规应用程序和具有 windows 的隐藏应用程序的解决方案,但最终可能会对您的最终目标至关重要。但如果你经常使用更常规的应用程序,那么哪种方法更合适就很清楚了。
†更普遍的潜在问题——它并不真正适用于您目前的脚本,但对于了解未来的脚本是否有用您尝试使用类似的技术 - 是使用变量作为引用编译时具有未确定名称的 application
的方法(在脚本为 运行 之前)。
bounds
是所有可编写脚本的应用程序 windows
中相当普遍的 属性,这就是为什么您(几乎)在这里摆脱这种技术的原因。但是,如果您选择 Script Editor 明确 不 包含的 属性 或对象 class,AppleScript 将不会识别术语并假设它只是一个变量名。为了识别特定于应用程序的术语,需要以某种形式引用该特定应用程序,可以通过 tell application "Finder" to...
或将相关行括在 using terms from application "Finder"
块中。
一个好的经验法则是应用程序通常需要在编译时知道并指定以便接收 AppleScript 命令。如果不为每个可能的应用程序使用 if...then...else if...
系列条件块,就没有简单的方法来满足不同的选择。
这是令人沮丧的根源,尤其是当涉及到看似本质上相似并且具有相似的 AppleScript 词典,但仍不彼此共享其术语的应用程序时一般使用。我特别想到 Safari 和 Chrome,它们都有被称为 tabs
的对象,因此很容易忘记 Safari tab
仍然是 不同的 class 对象 Chrome tab
,任何将通用代码编写到脚本中的任何一个或两个的尝试都将失败。