【问题标题】:How do I return the output of my Apple Script to the Status Bar in macOS?如何将 Apple 脚本的输出返回到 macOS 的状态栏?
【发布时间】:2023-03-13 22:09:01
【问题描述】:

我正在编写一个脚本,该脚本会在一个应用程序中查看您在某项活动上花费的时间,然后在 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

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

【问题讨论】:

  • 我猜您想根据应用程序激活/停用来启动/停止计时器?您在使用什么来设置计时器和状态菜单项,或者您是否正在考虑使用 AppleScript/AppleScriptObjC 完成所有操作?
  • 它将类似于计时器。有问题的应用程序有一个计时器,它会一直计时,直到您切换到另一个活动状态。例如,我可能会在空闲状态下花费 20:32,然后切换到活动状态。我希望在状态栏中显示该州的名称,以及在其中花费的时间,这样我就不必进入应用程序来查看我在那里呆了多长时间。该应用程序有一个计时器来显示这一点,但让状态栏显示它会更方便。我在 Swift 中做了一个状态栏图标,但是不知道怎么添加 AS。

标签: macos applescript statusbar


【解决方案1】:

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

  1. 将下面的脚本复制到脚本编辑器中
  2. 保存它,从文件格式拉出弹出窗口中选择“应用程序”,然后单击“运行处理程序后保持打开”复选框。
  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 应用程序。

【讨论】:

  • 这正是我想要的,谢谢@Ted Wrigley!我认为我的下一个努力将是在状态栏中的文本中添加某种条件突出显示,以便当我在这些状态下经过一段时间时。再次感谢!
  • 您对这个错误有什么建议吗? "NSWindow drag regions should only be invalidated on the Main Thread!" number -10000 我知道当脚本到达脚本中的第一个on run 语句时它就会出现。
【解决方案2】:

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

您可以创建一个 NSStatusItem 并将其按钮标题设置为您希望显示经过的时间,并设置几个观察者以在所需的应用程序停止/启动时获取通知,以便暂停和继续经过的时间时间。请注意,应该使用类似 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

您不需要将应用程序添加到隐私设置中,因为它不控制任何内容,但请注意,常规 AppleScript 会将属性和全局变量保存在脚本文件中,因此您需要代码签名或制作脚本资源只读以避免重新添加更改的应用程序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    • 1970-01-01
    • 1970-01-01
    • 2021-07-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多