问题
I am working on a script that looks in one app for the amount of time you've spent doing a certain activity, then displays that number in the status bar of the Mac, just like the clock that continuously counts up in the upper right corner. I've seen others like it that can show you your IP in the same area, which is close to what I'm trying to accomplish.
I think I have the script functioning to where it will run continuously until the application where I'm working is fully quit, however, I'm unsure of how to display that number up top in the status bar where it can be seen without needing to open said application.
I've been looking into AppleScriptObjC as an option, however, that is new ground for me, and I'd like to know if that is what should be used before I completely dive in.
I created a menu bar applet with Python, however, I learned that using Python might not be needed at all, and I wasn't sure how I would combine the AppleScript with what I created in 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
As of now, I'm not encountering any error messages; I just don't know how to move forward with returning the continuous output of the script in the status bar up top.
回答1:
If you want a simple status item that displays text, this script (saved as a stay-open script application) should do the trick. Do this:
- Copy the script below into Script Editor
- Save it, choosing 'Application' from the File Format pull popup, and click the 'Stay open after run handler' checkbox.
- Run the applet like normal (you'll have to give it authorization to control apps).
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
If you want more complex behavior — like a functional menu for the status item, or a clickable item — you'll have to shift to a cocoa-applescript application, I think.
回答2:
An AppleScriptObjC application can also be created from the Script Editor, as this is simple enough to not need the Cocoa-AppleScript template (note that experimenting should be done with the saved application, as menus and observers would also be added to the editor).
You can create an NSStatusItem and set its button title to however you are wanting to display the elapsed time, with a couple of observers set up to get notifications when the desired application is stopped/started in order to pause and continue the elapsed time. Note that something like an NSTimer should be used instead of that repeat loop, otherwise you will block your timer app’s user interface. An example that counts while you are in the Finder would be something like:
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
You shouldn’t need to add the application to the privacy settings, since it doesn’t control anything, but note that regular AppleScripts save properties and globals in the script file, so you will need to code sign or make the script resource read-only to keep from having to re-add the changed applications.
来源:https://stackoverflow.com/questions/57421381/how-do-i-return-the-output-of-my-apple-script-to-the-status-bar-in-macos