我如何 return 我的 Apple 脚本输出到 macOS 中的状态栏?

How do I return the output of my Apple Script to the Status Bar in macOS?

我正在编写一个脚本,该脚本会在一个应用中查找您花在某个 activity 上的时间,然后在 Mac 的状态栏中显示该数字,就像右上角不断递增的时钟。我见过其他类似的东西可以向您展示您在同一区域的 IP,这与我想要实现的目标很接近。

我想我的脚本一直在运行 运行 直到我正在工作的应用程序完全退出,但是,我不确定如何在顶部显示该数字无需打开所述应用程序即可看到的状态栏。

我一直在研究 AppleScriptObjC 作为一个选项,然而,这对我来说是新的领域,我想知道在我完全投入之前是否应该使用它。

我用 Python 创建了一个菜单栏小程序,但是,我了解到可能根本不需要使用 Python,而且我不确定如何将 AppleScript 与什么结合起来我创建于 Python。

tell application "System Events"
    set appName to "App I'm Using"
    tell process "App I'm Using"
        set activityState to value of menu button 1 of group 1 of group 4 of toolbar 1 of window of application process "App I'm Using" of application "System Events" as list
        return first item of activityState as string
    end tell
end tell

repeat
    tell application "System Events"
        if "App I'm Using" is not in (name of application processes) then exit repeat
    end tell
    delay 5
end repeat

到目前为止,我没有遇到任何错误消息;我只是不知道如何继续在顶部的状态栏中返回脚本的连续输出。

如果您想要一个显示文本的简单状态项,此脚本(另存为保持打开的脚本应用程序)应该可以解决问题。这样做:

  1. 将下面的脚本复制到脚本编辑器中
  2. 保存它,从 文件格式 下拉弹出窗口中选择 'Application',然后单击 'Stay open after run handler' 复选框。
  3. 运行 小程序正常(您必须授予它控制应用程序的权限)。
use framework "AppKit"
use scripting additions

property ca : current application
property NSStatusBar : class "NSStatusBar"

property appName : "App Name"

global statusItem

on run
    set statusItem to NSStatusBar's systemStatusBar's statusItemWithLength:(ca's NSVariableStatusItemLength)
    set statusItem's button's title to "Initializing"
end run

on idle
    -- Update the status item's text here.
    tell application "System Events"
        if not (exists process appName) then
            display alert "Application " & appName & " is not running" as warning giving up after 6
            quit me
        end if
        tell process appName
            tell first window's first toolbar's fourth group's first group's first menu button
                set activityState to first item of (value as list) as text
            end tell
        end tell
    end tell

    set statusItem's button's title to activityState

    (*
      The return value gives the idle time, so if you want the menu item 
      to update (say) every half second, use 'return .5'
    *)
    return 1
end idle

on quit
    -- remove status item and quit
    NSStatusBar's systemStatusBar's removeStatusItem:statusItem
    continue quit
end quit

如果你想要更复杂的行为——比如状态项的功能菜单,或者可点击的项目——我认为你必须转向 cocoa-applescript 应用程序。

也可以从脚本编辑器创建 AppleScriptObjC 应用程序,因为这很简单,不需要 Cocoa-AppleScript 模板(请注意,应使用已保存的应用程序进行试验,因为菜单和观察器也会被添加到编辑器中)。

您可以创建一个 NSStatusItem 并将其按钮标题设置为您想要显示经过的时间,并设置几个观察器以在所需应用程序 stopped/started 暂停时收到通知并继续流逝的时间。请注意,应该使用 NSTimer 之类的东西而不是重复循环,否则您将阻塞计时器应用程序的用户界面。当您在 Finder 中时,一个重要的示例如下:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Cocoa" -- Foundation, AppKit, and CoreData
use scripting additions -- just in case

# Watch for specified application activation and update status item timer while it is active.
# Add LSUIElement key to Info.plist to make an agent (no app menu or dock tile).

property watchedApp : "Finder" -- the name of the application to watch/time
property statusItem : missing value -- the status bar item
property statusMenu : missing value -- the status bar item's menu
property timer : missing value -- a repeating timer for updating elapsed time
property updateInterval : 1 -- time between updates (seconds)
property colorIntervals : {30, 60} -- green>yellow and yellow>red color change intervals (seconds)

global elapsed, paused -- total elapsed time and a flag to pause the update
global titleFont
global greenColor, yellowColor, redColor

on run -- set stuff up and start timer
    set elapsed to 0
    set paused to true

    # font and colors
    set titleFont to current application's NSFont's fontWithName:"Courier New Bold" |size|:16 -- boldSystemFontOfSize:14
    set greenColor to current application's NSDictionary's dictionaryWithObjects:{current application's NSColor's systemGreenColor} forKeys:{current application's NSForegroundColorAttributeName}
    set yellowColor to current application's NSDictionary's dictionaryWithObjects:{current application's NSColor's systemYellowColor} forKeys:{current application's NSForegroundColorAttributeName}
    set redColor to current application's NSDictionary's dictionaryWithObjects:{current application's NSColor's systemRedColor} forKeys:{current application's NSForegroundColorAttributeName}

    # status item and menu
    set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
    statusItem's button's setFont:titleFont
    statusItem's button's setTitle:formatTime(0)
    set my statusMenu to current application's NSMenu's alloc's initWithTitle:""
    statusMenu's addItemWithTitle:(watchedApp & " Elapsed Time") action:(missing value) keyEquivalent:""
    (statusMenu's addItemWithTitle:"Reset Time" action:"reset:" keyEquivalent:"")'s setTarget:me
    (statusMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"")'s setTarget:me
    statusItem's setMenu:statusMenu

    # notification observers
    set activateNotice to current application's NSWorkspaceDidActivateApplicationNotification
    set deactivateNotice to current application's NSWorkspaceDidDeactivateApplicationNotification
    tell current application's NSWorkspace's sharedWorkspace's notificationCenter
        its addObserver:me selector:"activated:" |name|:activateNotice object:(missing value)
        its addObserver:me selector:"deactivated:" |name|:deactivateNotice object:(missing value)
    end tell

    # add a repeating timer
    set my timer to current application's NSTimer's timerWithTimeInterval:updateInterval target:me selector:"updateElapsed:" userInfo:(missing value) repeats:true
    current application's NSRunLoop's mainRunLoop's addTimer:timer forMode:(current application's NSDefaultRunLoopMode)
end run

on activated:notification -- notification when app is activated
    set appName to (notification's userInfo's NSWorkspaceApplicationKey's localizedName()) as text
    if appName is watchedApp then set paused to false -- resume elapsed count
end activated:

on deactivated:notification -- notification when app is deactivated
    set appName to (notification's userInfo's NSWorkspaceApplicationKey's localizedName()) as text
    if appName is watchedApp then
        set paused to true -- pause elapsed count
        statusItem's button's setTitle:formatTime(elapsed)
    end if
end deactivated:

to updateElapsed:sender -- called by the repeating timer to update the elapsed time display
    if paused then return -- skip it
    set elapsed to elapsed + updateInterval
    try
        set attrText to current application's NSMutableAttributedString's alloc's initWithString:formatTime(elapsed)
        if elapsed ≤ colorIntervals's first item then -- first color
            attrText's setAttributes:greenColor range:{0, attrText's |length|()}
        else if elapsed > colorIntervals's first item and elapsed ≤ colorIntervals's second item then -- middle color
            attrText's setAttributes:yellowColor range:{0, attrText's |length|()}
        else -- last color
            attrText's setAttributes:redColor range:{0, attrText's |length|()}
        end if
        attrText's addAttribute:(current application's NSFontAttributeName) value:titleFont range:{0, attrText's |length|()}
        statusItem's button's setAttributedTitle:attrText
    on error errmess -- for experimenting
        display alert "Error" message errmess
    end try
end updateElapsed:

to reset:sender -- reset the elapsed time
    set elapsed to 0
    statusItem's button's setTitle:formatTime(elapsed)
end reset:

to formatTime(theSeconds) -- return formatted string (hh:mm:ss) from seconds
    if class of theSeconds is integer then tell "000000" & ¬
        (10000 * (theSeconds mod days div hours) ¬
            + 100 * (theSeconds mod hours div minutes) ¬
            + (theSeconds mod minutes)) ¬
            to set theSeconds to (text -6 thru -5) & ":" & (text -4 thru -3) & ":" & (text -2 thru -1)
    return theSeconds
end formatTime

to terminate() -- quit handler not called from normal NSApplication terminate:
    current application's NSWorkspace's sharedWorkspace's notificationCenter's removeObserver:me
    tell me to quit
end terminate

您不需要将应用程序添加到隐私设置,因为它不控制任何东西,但请注意,常规 AppleScripts 会在脚本文件中保存属性和全局变量,因此您需要代码签名或制作脚本资源 read-only 避免 re-add 更改的应用程序。