【问题标题】:AppleScript - Get the Bounds of Every open WindowAppleScript - 获取每个打开窗口的边界
【发布时间】:2018-09-12 18:52:51
【问题描述】:

我整天都在玩弄这个。目标是生成一个 AppleScript 来生成更多 AppleScript。我会更详细地解释。

理想的最终结果:按照您喜欢的方式安排窗口后,启动此脚本。这会将必要的脚本复制到剪贴板,以根据当前配置自动启动、定位和调整应用程序窗口的大小。这样我就可以将脚本发送给其他人,然后他们可以在启动此脚本时设计自己的自定义布局,然后可以将其粘贴到脚本编辑器中,或者可能制作成服务并使用 Automator 绑定到热键。

我目前正在尝试克服的问题:我似乎无法让它列出每个窗口的边界。我目前正在运行这个脚本。

  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"

我并不是要求有人为我解决整个问题。尽管对此事的任何建议总是值得赞赏的。当前的hurdle 只是将每个窗口的边界设置为变量,以便在脚本的其他地方使用。

【问题讨论】:

  • 大小和位置就足够了。使用 Bounds 是我知道的唯一实现此目的的方法,但如果您有其他建议,我愿意接受。

标签: macos applescript


【解决方案1】:

此答案主要针对 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 processsizeposition 属性列表。下面是对脚本出错的位置和原因的相当冗长的解构;然后是在考虑其他同样可行的解决方案之后提出的解决方案,然后再确定上面的基本代码。改天当我不那么累的时候,我会尝试减少这个答案的冗长,但现在,我希望更深入的见解有所帮助。

问题

▸ 从你的脚本抛出的特定错误开始,有必要指出,一般来说,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 windowFinder 绝不只是背景,但有时没有打开的窗口。因此,即使您遇到的错误已得到修复,如果没有打开的 Finder 窗口,这也是下一个出现的错误。

解决方案

虽然您无法获得属于不可编写脚本的应用程序的窗口的bounds 属性,但系统事件 可以检索属于application process 对象的某些属性。这与进程所属的应用程序本身是否可编写脚本无关,因为系统事件是我们的目标应用程序,可编写脚本的,并且恰好有访问与每个进程的 window 对象相关的类似信息(NB. 请参见下面的脚注,但属于 processwindow 对象相同作为属于applicationwindow 对象,因此不能互换使用,它们的属性也不能互换使用。

虽然系统事件processes 拥有的window 对象没有bounds 属性,但还有另外两个属性,它们合起来等价于boundspositionsizeposition 给出了窗口左上角相对于屏幕左上角的{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 字典,但仍不相互共享它们的术语以供一般使用。我特别想的是 SafariChrome,它们都有被称为 tabs 的对象,因此很容易忘记 Safari tab 仍然是 Chrome tab不同类对象,任何将通用代码写入脚本的任何尝试都将失败。

【讨论】:

  • 我参加这个聚会迟到了,但是您将如何使用这种方法告诉 windows 去哪里?假设您记录了每个应用程序的位置,并且可以按照您使用参考的方式读出它。有没有一种简单的方法来记录这些信息,然后再次使用它来告诉每个窗口用一行代码去哪里?还是必须使用某种重复循环?
  • @PatrickHennessey 抱歉回复晚了。如果您希望您的窗口转到不同的位置,您将需要一个重复循环,因为无法将用户生成的坐标列表转换为具有whose 结构的语法或逻辑意义的东西单线。所以答案是否定的。但是,如果您想将所有窗口发送到同一个坐标,这似乎可行:tell app id "sevs" to tell process "Safari" to set the position of every window to [0, 0]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
相关资源
最近更新 更多