I encountered the same problem (the script infinitely spinning) in macOS Sierra. I spent some time using Automator and was able to solve it by modifying the script slightly:
ignoring application responses
tell application "System Events"
click menu bar item 1 of menu bar 1 of application process "Pomodoro One"
end tell
end ignoring
do shell script "killall System\\ Events"
delay 0.1
tell application "System Events"
click menu item "Start Pomodoro" of menu 1 of menu bar item 1 of menu bar 1 of application process "Pomodoro One"
end tell
It's possible with the say
command in a shell, not with the AppleScript say
command.
Info for the AppleScript say command:
- you can stop the speech of say command from the same script until the
script run, not after that the script exit.
- Example:
say "I want to recreate macOS's built-in Text To Speech" waiting until completion no
delay 0.5
say "" with stopping current speech -- this stop the first say command of this script
delay 1
say "Hello"
This script use the say
command in a shell to speak the contents of the pbpaste
command (the clipboard), and it put the PID of the say
command to a persistent property:
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property this_say_Pid : missing value -- the persistent property
if this_say_Pid is not missing value then -- check the pid of all 'say' commands, if exists then quit the unix process
set allSayPid to {}
try
set allSayPid to words of (do shell script "pgrep -x 'say'")
end try
if this_say_Pid is in allSayPid then -- the PID = an item in the list
do shell script "/bin/kill " & this_say_Pid -- quit this process to stop the speech
error number -128 -- quits the AppleScript
end if
end if
-- Back up original clipboard contents:
set savedClipboard to my fetchStorableClipboard()
-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.
-- Speak the clipboard:
-- pbpaste = the contents of the clipboard , this run the commands without waiting, and get the PID of the 'say' command
set this_say_Pid to do shell script "LANG=en_US.UTF-8 pbpaste -Prefer txt | say > /dev/null 2>&1 & echo $!"
-- Restore original clipboard:
my putOnClipboard:savedClipboard
on fetchStorableClipboard()
set aMutableArray to current application's NSMutableArray's array() -- used to store contents
-- get the pasteboard and then its pasteboard items
set thePasteboard to current application's NSPasteboard's generalPasteboard()
-- loop through pasteboard items
repeat with anItem in thePasteboard's pasteboardItems()
-- make a new pasteboard item to store existing item's stuff
set newPBItem to current application's NSPasteboardItem's alloc()'s init()
-- get the types of data stored on the pasteboard item
set theTypes to anItem's types()
-- for each type, get the corresponding data and store it all in the new pasteboard item
repeat with aType in theTypes
set theData to (anItem's dataForType:aType)'s mutableCopy()
if theData is not missing value then
(newPBItem's setData:theData forType:aType)
end if
end repeat
-- add new pasteboard item to array
(aMutableArray's addObject:newPBItem)
end repeat
return aMutableArray
end fetchStorableClipboard
on putOnClipboard:theArray
-- get pasteboard
set thePasteboard to current application's NSPasteboard's generalPasteboard()
-- clear it, then write new contents
thePasteboard's clearContents()
thePasteboard's writeObjects:theArray
end putOnClipboard:
It's possible that the first script will not work, if the value of this_say_Pid variable doesn't persist across runs, it depends how the script will be launched.
In that case, you must write the PID to a file, so use this script:
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
set tFile to POSIX path of (path to temporary items as text) & "_the_Pid_of_say_command_of_this_script.txt" -- the temp file
set this_say_Pid to missing value
try
set this_say_Pid to paragraph 1 of (read tFile) -- get the pid of the last speech
end try
if this_say_Pid is not in {"", missing value} then -- check the pid of all 'say' commands, if exists then quit the unix process
set allSayPid to {}
try
set allSayPid to words of (do shell script "pgrep -x 'say'")
end try
if this_say_Pid is in allSayPid then -- the PID = an item in the list
do shell script "/bin/kill " & this_say_Pid -- quit this process to stop the speech
error number -128 -- quits the AppleScript
end if
end if
-- Back up original clipboard contents:
set savedClipboard to my fetchStorableClipboard()
-- Copy selected text to clipboard:
tell application "System Events" to keystroke "c" using {command down}
delay 1 -- Without this, the clipboard may have stale data.
-- Speak the clipboard:
-- pbpaste = the contents of the clipboard , this run the commands without waiting, and it write the PID of the 'say' command to the temp file
do shell script "LANG=en_US.UTF-8 pbpaste -Prefer txt | say > /dev/null 2>&1 & echo $! > " & quoted form of tFile
-- Restore original clipboard:
my putOnClipboard:savedClipboard
-- *** Important *** : This script is not complete, you must add the 'putOnClipboard:' handler and the 'fetchStorableClipboard()' handler to this script.
Best Answer
I have a few different solutions:
Alternatively, you could have a single script that checks to see if the speech is running and will kill that particular speech, (basically simulates the shortcut for alt-esc, BUT you can add the rate function to the script). I found a script online a while back that does this. Here's the link. I also made my own version that changes the rate (at the bottom of the post). The rate control is where it says [[rate 800]]. This script copies text to the clipboard and adds '[[rate 800]]' which apple tts voices interpret as a command to change to that rate. The clipboard is read at the modified speed. You can adjust up to like 1000 or something.
However, if you really just want to make your mac's default text to speech rate faster (which is what I suspect you are using this script for), I highly recommend you just change the default rate using a little hack I came up with a while back (It allows you to go up to 720 WPM). I have a video that describes it all. Basically, it changes the default rate using a script.