Hi everyone, I wanted to take the time to write an article about some voiceover optimizations I've made. These have sped up voiceover tremendously and make my workflow amazingly fast.
running the shell scripts mentioned below:
To run any of the scripts described in the next section, copy the code into a new txt file. If using text edit, make sure you open the preferences and under the "Open and Save" tab, you select the format as plain text.
If you had a document open before completing the step above, make sure to close it and open a new document.
after you copy the code into your blank file, do the following: (note, these instructions apply to Text Edit)
- type a useful name for the command file you made.
- make sure to add the extension ".command" to your document.
- if you find your document and it says ".command.txt", feel free to just delete the ".txt" part, and then move on to the next step.
- now, for each of your documents, open terminal and type the following command:
chmod +RWX path/to/file
This gives the system permission to execute your script. - now, you should have everything set up for the command scripts mentioned below.
Shell scripts
The first optimizations I wanted to talk about were the several shell scripts I have made. All of these scripts have been made in shell script, so whether your mac uses zsh or bash, you should be fine. You can find out what shell your mac uses by doing the following:- Open a terminal on your mac. You can do this from spotlight or open the utilities folder in your applications folder.
- once in a new terminal window, type the following command:
echo $SHELL
You may either hear /bin/bash, or /bin/zsh depending on your mac. Either is fine.
Voiceover speed booster
About a year ago I tried to make an app using automator that would give voiceover max priority to the system. Let's just say I didn't know then what I know now, and sorry for the confusion about that previous article.
This code when run, will automatically renice (give a specified process priority to) the voiceover process.
In this case, this code will set it to the max process priority (-20). The lower the number the more cpu the process can use. The max number (20, the equivalent of the slowest setting) would cause voiceover to probably run slower than usual.
navigationif you are just looking for code snippets, navigate at heading level 6.
Voiceover speed booster codecode begins here:
#!/bin/bash id=$(pgrep VoiceOver) if [ -n "$id" ]; then sudo renice -n -20 -p "$id" else say "VoiceOver is not running." fi killall Terminal
app speed booster
Same basic thing here, except this time, the script asks you for the name of the app. Then, it performs the same task as performed in the code above. the script also checks to see if you have entered an invalid app name and will let you know about that.
One thing to note: Some times with some apps (from my testing messages and safari), you may get a "Bad PID Argument". The cause of this is the "pgrep" command I use to get the process id returns multiple values. When passed to the renice command, it throws that error. If anyone knows how to fix that and pass all values to the renice command to have there priorities changed, please let me know. At the end of the program, the app kills terminal and returns you to the previous app you were using.
App booster codecode begins here:
#!/bin/bash echo "Which app do you want to boost? Type the name exactly as it is written:" read app id=$(pgrep "$app") # Check if the PID was found if [ -n "$id" ]; then sudo renice -n -20 -p "$id" echo "boosted $app." sleep 1.5 killall Terminal else echo "Invalid app name. The specified app is not running." sleep 10 killall terminal fi
Does this fix the dreaded "safari not responding" bug?
I wanted to answer this, because I would assume someone would be posting on here asking that same question.
from my testing, I have not had that bug as bad as some people have. I can't give you a definitive yes or no answer, but I can tell you this. You will not know until you try.
setting these scripts up for one click actions
we can assign these scripts to useful keyboard commander keys.
For those unfamiliar, the keyboard commander allows you to hold the option key, and then press another key on your mac, and it will perform a specified action. There are many default ones that come set up by default, including
- option+t: announce time and date
- option+M, open mail
- Option+u, read unread mail message count
Here is how you can set up keyboard commander to work with the previously mentioned scripts, as well as open apps and or files you access on a daily basis.
Here's how to set it up.
- To open voiceover utility, Press VO+F8(function8)
- Now, navigate to the "Utility categories" table.
- Now find the "commanders" category.
- Once in the commanders category, find the "Keyboard Commander" tab.
- Here, you will find a table of all of your currently assigned keyboard command keys, as well as an "add" button, a "remove" button, and a "help" button which brings you to the Voiceover User guide
- Find the add button and add all of the letters, numbers, and special symbols(shift+number row) you want to use as commands.
- Next to each key text field is a pop-up where you can choose the command you want to run. I would highly encourage you to take a look in all of the categories, because you might find a few hidden gems, such as the ability to quickly fast forward or rewind in read all. Yes, the mac can do that.
after you have taken advantage of the shell scripts and keyboard commander, there are a few things you can do to further enhance the Voiceover Experience.
portable preferences on line in iCloud!
yep, you heard that right, portable preferences in iCloud.
For those unfamiliar, "portable preferences" is a voiceover feature that allows you to sink your preferences from one computer to another by storing your preferences on a flash drive or other external storage media.
There is a way to store these preferences in iCloud. This involves tricking the system into thinking you have a drive connected that it can use for portable preferences. This is relatively easy to do.
set up online portable preferences
First, we need to open disc utility to create a disc image that can be used for voiceover Preferences.
- Navigate to the utilities folder in your applications folder. alternatively, press command+shift+u to jump directly there.
- Now, navigate to disc utility. once in disc utility, do the following:
- press command+n to create a new image.
- Give your disc image a name. (for the script that can run at startup and prevent it from unmounting (see later section), name the disc image
VO_Prefs.dmg
inside of the folder /applications/Voiceover_Preference_sinker). - leave the format and partition information alone.
- Make sure the disk image format is set to read/write so Voiceover can write to it.
- unless you want to add inscription, leave inscription set to none.
- the size of 100MB is fine. The voiceover preferences only use up about 5MB. I would say 20MB at a minimum just to be safe.
- Now, enter the name for the volume itself once it is mounted. For that script that keeps the dmg mounted, choose "Portable_Preferences" without quotes.
autostart sinking
I have been teasing that there is a script that I made which keeps the dmg from accidentally getting ejected or unmounted. You can put this apple script in an automator app and have it autolaunch at login.
contributions to whom they are due:This was a tedious process finding out how to make a script that does something seemingly so simple. All the script does is check to see if the specified dmg is mounted. If it isn't, remount it.
I assumed that this was going to only take me a few hours to complete, but man was I wrong.
Several hours later, I had a semi-working script that was more advanced. This more advanced version showed a dialog asking if you wanted to restart sinking if the volume was ejected. This turned out to have several implications, such as the dialog showing up even if the dmg was mounted. Thus, I have removed that from this version, because it works.
I will point out, that Chat GPT did help with a lot of the debugging. I put it together after doing some googling and finding nothing much as far as this exact situation. I made sure that my work was original, in a since that I wanted the script to do exactly what I wanted it to do, and that is what it does.
I felt it was my obligation to at least mention that Chat GPT did help with this particular script.
instructions for keeping the dmg mounted at all timesFirst, open auto-mater and create a new application.
- search for the "AppleScript" action and press enter on it.
- in the script textfield, delete everything that was there and enter the following script. on run set diskImagePath to "/Applications/Voiceover_preference_sinker/VO_Prefs.dmg" repeat try set isMounted to isDMGMounted(diskImagePath) if not isMounted then set quotedDiskImagePath to quoted form of diskImagePath do shell script "hdiutil attach " & quotedDiskImagePath delay 5 -- Add a delay to allow the DMG to mount end if delay 0 on error -- Handle errors here end try end repeat end run on isDMGMounted(diskImagePath) try do shell script "hdiutil info | grep -q " & quoted form of diskImagePath return true on error return false end try end isDMGMounted
Other resources
This article has probably gone on for way too long, so I should better wrap it up. ;-)
There is an app called Hammerspoon which can allow you to run several different automations including some that can control Voiceover.
You can find a complete list of hammerspoon modules that make the accessibility better as well as a few others here.
Some of my favorites include triggrd which can allow you to monitor system events, including making custom sounds for when certain actions happen like apps opening, usb connected/disconnected, etc. This one is so useful I don't know how I lived without it.
some others I really enjoy are Speech History which brings speech history to voiceover which I always forget I have, and Recmon which is a resource monitoring tool.
Conclusion/closing remarksI hope you have found these scripts and tips useful to get the most out of Voiceover. Some of those especially the voiceover speed booster and the app speed booster really make a difference. My favorite way to use the app speed booster is if my download manager is being a pain, just use the booster on it and it starts going faster. If you have any improvements or want to add to any of my scripts, please feel free to let me know and maybe we could improve voiceover even more!
Thanks again for taking the time to read this (and sorry for the extremely long article), and I will be here to answer any questions and help people if they need it.
Thanks,
Levi
Comments
Really useful!
Thanks for this interesting post, Levi.
I especially like the script that "re-nices" VoiceOver.
Thank you for your hard work
Thank you, Levi, for your hard work. Smile. You are contributing a lot to our little community. I hope you get all the recognition and thanks you've earned.
Joy!
Bruce
Thank you for the…
Thank you for the hammerspoon tip. I've never heard of this application yet, seems to be very useful. Definitely want to look deeper into the docs and getting started with some spoons in the future.
you're welcome.
I'm glad you have injured these scripts.
If you have any questions, please don't hesitate to ask.
Got the speech history installed but the sound link is broken
I got speech history to work without a problem. I wanted to install the sound enhancements on the page you linked to, but awhen I press download the link throws up a 404 not found.
Use triggrd
you can download triggrd and use sound note. That is what I use.
image description script?
hello. I've been hearing a lot about this hammerspoon script which interphases with the gpt4vision API but I cannot seem to find the actual file. Does anyone have any idea how I can find this? I've seen an NVDA addon which does this and the use cases for this type of quick access are endless.
I had a friend tell me about that
I recently had a friend tell me about that, but I can’t seem to find it when looking around.
If you could find it, or anyone else for that matter, that would be amazing to have!
Thanks for this
I have not delved into the world of HammerSpoon yet, but the VO and App boost scripts I am going to take for a test drive. One thing I would add, is that you can also get which shell you are in by typing "echo $0".
Just an alternative to the $SHELL command. 😎
As soon as you said that I remembered that! 😜
When I wrote the article, I forgot what the command was, so a quick Google search led me to what I wrote, but then I remembered afterwards that you can do echo $0 and that works too.
They both do the same command, so whatever one you want to use is fine.
Hammerspoon
Using Hammerspoon and Triggrd now. Loving the little sound queues. By the by, the VO and App Boost scripts are amazing. I will admit I had to tinker with the app boost script to make it work with Safari, but alas I am on an old intel-based MBP, so that may have played a role.
Nevertheless the boost scripts are very nice. 🤠👍🏼
How did you get it to work with Safari?
I’ve been trying to figure that out as well. Right before the app closes, it says bad PID argument I don’t know how to fix that, because when running the underlying command to get the name of the app (pgrep app name) in the script, it gives multiple values.
Is this what it was doing for you?
If you could tell me how you fix that, that would be great
Modified script
So your App Boost script does not like Safari. At all. What I did was modify the VO Boost script to work with Safari.
Is it perfect? I cannot really say, but for my needs it works.
What I have noticed when I have both VO and Safari boosted, is that Safari runs with VO equivalent to how Microsoft Edge and NVDA run on my Bootcamp of Windows 10.
Does it fix the "not responding" issue? Hell no. As that seems to be Webkit related, and is sadly beyond my experience and knowledge. However, it does appear to run better. I do not know if this matters, but I am also running Ventura (13.6.1).
HTH. 🤷🏼♂️
resilio sync foler
hey all. just thought this could be a nice way to share scripts. I have put a read and write key for a resilio sync folder specifically for them. A6C4T6YOOEYHXNQBBINKOAPIZ7EW5SWA5
Is it an exact copy of the script?
Did you just duplicate the script and replace the word voiceover with Safari?
That’s basically what my app boosting script does, except it enters into the same variable the name of the app.
The reason I ask this, is when I run my app boosting script, I get this error:
renice: pid argument 614 619 687 707 2769 2983 2990 2991 3000 3147 is invalid.
These numbers are separated by new lines, but replaced them with spaces to clean up the way Voiceover would read that on this website.
Is this what your script does if you remove the killall terminal line?
Just curious because I think a boosted Safari would be greatly helpful on my M1 MacBook Pro.
Just in case you’re curious, it is the same thing for me on both Ventura and Sonoma.
It also does this with other apps like Google Chrome and other system processes.
I’m assuming the different numbers shown in the error of our different sub processes of the app, and it doesn’t like the way the numbers are getting passed to the renice command. I don’t know how to fix this, so that if it sees a bunch of process IDs like that, it could just boost them all.
Terminal Output
Below is a copy of my terminal from launching Terminal, to typing out the command (see: Copy/Paste) into Terminal and pressing Enter.
Please note: I have a custom terminal "boot sequence" so just disregard the info above the Safari boost code.
Finally yes, I did replace VoiceOver with Safari, and purposefully removed the killall command just to show you that I do NOT get those errors.
// Terminal Output starts here
ProductName: macOS
ProductVersion: 13.6.1
BuildVersion: 22G313
PythonVersion: Python 3.9.6
ActiveShell: -bash
SystemTime: 12:00:26 AM CST
SystemDate: 3-Dec-2023
~>
~> #!/bin/bash id=$(pgrep Safari) if [ -n "$id" ]; then sudo renice -n -20 -p "$id" else say "Safari is not running." fi
~>
//Terminal Output ends
HTH.
Almost forgot!
I do get the "Safari is not running" message when I try this and Safari is, of course, not running. So, the code is working, at least in principal. 😅
Safari 17.2.1 update
As of this update, I get the following error when I try to optimize Safari:
• /bin/echo: /bin/echo: cannot execute binary file
All I can say is, "Blah!" It used to work. Ah well. 🫤
that's... weird...
I've never seen that before. Did you perform a macOS update as well? maybe it was a macOS update. If you find out what it was, please let me know.
Thanks
13.6.3
I was running 13.6.1 before, I do believe, and of course, Safari 16.6. 🤷🏼♂️