Making a dialog that bugs me in Applescript - applescript

I know this question is kinda messy. I don't know how to make it more specific. I want to make an applescript that asks me every hour what I've been doing for the past hour.
The issue I'm having is that I want it to pop up, make a sound, and wait for my response. If I do a normal dialog, and I'm busy, it will go behind the other windows on my mac. I thought maybe having a persistent banner notification would be great, but applescript doesn't allow for much control over banners.
I want something to float over all windows so that I can see that the applescript has been waiting for a response from me until I fill out the dialog.

What you're looking for is a global floating window, similar to what LittleSnitch does. You can do that, but not with pure AppleScript. You'd have to write a Cocoa app.

Adding an activate command just before a display dialog command in your AppleScripts, will ensure the pop-up dialog window will become frontmost and visible no matter what other apps and documents are currently opened (until another app or document gets activated, thus bringing that item to the front).
This following AppleScript may be of some use to you.
Save this following AppleScript code as a "stay open application" in Script Editor.app.
When running your new AppleScript applet, it will remain running (because it has an idle handler) until you press "Cancel" in any of the dialog pop-ups or choosing to quit the app in the Dock.
This code also logs everything it receives in the dialog pop-ups, to file for you.
property myComputerActivitiesLog : (path to desktop as text) & "My_Computer_Activities.log"
property theDialog : missing value
property theDate : missing value
property insertTime : missing value
property logContent : missing value
on idle
set theDate to (current date)
set insertTime to " ------ " & theDate & " ------ "
beep 5
say "It's time to log your activities"
activate
try
set theDialog to display dialog ¬
"Itemize my activities." default answer ¬
"Itemize my activities for yhe past hour." & linefeed & linefeed ¬
buttons {"Cancel", "OK"} default button 2 cancel button 1 ¬
with title "Account For My Activities" with icon 1
end try
if theDialog = missing value then
quit me
else
set logContent to insertTime & linefeed & (text returned of theDialog) & linefeed
do shell script "echo " & quoted form of logContent & ¬
" >> " & quoted form of POSIX path of myComputerActivitiesLog
set theDialog to missing value
return 3600 -- in seconds
end if
end idle
on quit -- Executed when the script quits
-- Additional code to perform (if any) goes here
continue quit -- allows the script to quit
end quit

Related

How to make application written using AppleScript to open the dialog each time the icon is clicked?

I'm writing an applescript application that would say time every 5 mins. This will be in an infinite loop. My requirement is, although it is running 24x7 since start, next time when I click application icon, it should show the dialog to get user input via one of the 3 buttons.
I have 3 buttons.
Pause Notifications
Notifications ON
Notification OFF
When the code is in infinite loop, and I click the application icon, the dialog prompt doesn't come up to get the user input via above 3 buttons. How to fix that?
global isSoundEnabled, isNotifEnabled
on run
set isSoundEnabled to true
set isNotifEnabled to true
set theDialogText to "The curent date and time is " & (current date) & "."
display dialog theDialogText buttons {"Pause", "Notif ON", "Notif OFF"} default button "Notif ON" cancel button "Pause" with icon caution
if result = {button returned:"Notif ON"} then
set isSoundEnabled to true
set isNotifEnabled to true
loop()
else if result = {button returned:"Notif OFF"} then
set isSoundEnabled to false
set isNotifEnabled to false
end if
end run
on loop()
repeat while true
set min to getMin()
get min as integer
#if (min mod 5) = 0 then
if (min / 5) > 1 then
set timee to getTimeInHoursAndMinutes()
if isNotifEnabled then
display notification timee
end if
if isSoundEnabled then
say timee
end if
#delay 60
#if isSoundEnabled is not
end if
#exit repeat
end repeat
end loop
I haven't added getTimeInHoursAndMinutes() and getMin() implementation on purpose as it doesn't much value.
The application user interface (menus, etc) will be blocked if you don’t give the system time to process events, so you want to avoid long repeat loops. The standard dialogs are modal, so you also normally can't do other things while they are being shown.
Repeatedly showing the standard dialogs can also be intrusive, since the current application will switch if activating the application (to show the dialog), or the Dock icon will start bouncing if you don't. Some AppleScriptObjc could be used for other types of notifications, but for this example I stayed with the standard dialogs.
I did cheat a little in the following script by using some AppleScriptObjC for the speech synthesizer, since it can speak in the background (while a dialog is shown). I've also avoided using notifications, since those need to be allowed in the System Preferences.
The bottom line is that when saving an app as stay open, the idle and reopen handlers can be used - the idle handler is repeatedly called after the run handler finishes (it uses a timer), and the reopen handler is called when the app is double-clicked (or the Dock icon clicked), so you can avoid locking up the UI. For example:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "AppKit" -- for the speech synthesizer
use scripting additions
global isSoundEnabled, isNotifEnabled, defaultButton
on run -- initial setup
set isSoundEnabled to true
set isNotifEnabled to true
set defaultButton to "Notif OFF" -- the default is set opposite the current setting
getSettings()
end run
to getSettings()
set theDialogText to "The curent date and time is " & (current date) & "."
tell me to activate
set theButton to button returned of (display dialog theDialogText buttons {"Quit", "Notif ON", "Notif OFF"} default button defaultButton with icon caution giving up after 10)
if theButton is "Notif ON" then
set isSoundEnabled to true
set isNotifEnabled to true
set defaultButton to "Notif OFF"
else if theButton is "Notif OFF" then
set isSoundEnabled to false
set isNotifEnabled to false
set defaultButton to "Notif ON"
end if
end getSettings
on reopen -- application double-clicked or dock icon clicked
getSettings()
end reopen
on idle -- after the run handler completes, is called repeatedly
set giveup to 5 -- dialog will self-dismiss, although timing will be a little off if manually dismissed
set theTime to time string of (current date)
if isSoundEnabled then -- AppleScriptObjC is used to speak in the background
(current application's NSSpeechSynthesizer's alloc's initWithVoice:(missing value))'s startSpeakingString:theTime
end if
if isNotifEnabled then
tell me to activate
display dialog "The current time is " & theTime with title "Current time" buttons {"OK"} giving up after 5
end if
return 300 - giveup -- the number of seconds for next idle run (less the dialog giveup time)
end idle

Directing an app to run off of a different script in AppleScript.

I'm making an applescript to help with school. You type the subject you wish to access, and then it opens that folder (inside the app). However, some subjects have ebooks, so I want there to be another dialog box asking you wether you want to open the ebook or the folder. Unfortunately, a display dialog can't branch off of another display dialog.
So far, the only way I've found around this is to direct the app to run off of another script (in the "Scripts" folder of the app). I've tried
tell application "Script Editor" to run script (path to me as sting) & "Contents:Resources:Scripts:Subject.scpt"`
But it didn't work. Sorry if I'm barking up the wrong tree and seem completely stupid. Thanks
As far as I understood what you're trying to achieve, the following script would work.
set listOfSubjects to {"Math", "Biology", "History", "Languages"}
set subjectsWithEbook to {"Biology", "History"}
choose from list listOfSubjects with prompt "Select the subject"
set subject to item 1 of result
set openEbook to false
repeat with subjectItem in subjectsWithEbook
if subjectItem contains subject then
choose from list {"Open folder", "Open Ebook"} with prompt "What to open?"
set openType to item 1 of result
if openType contains "Open Ebook" then
set openEbook to true
end if
exit repeat
end if
end repeat
if openEbook then
--Open ebook
else
--Open folder
end if
I am guessing that you want to type the subject name? Here is an example that uses dialog boxes.
set subjectFolderPath to (path to me as string) & "Contents:Resources:Subjects:"
set subjectsWithEbook to {"English", "Spanish"}
display dialog "What subject would you like to access?" default answer ""
set theSubject to text returned of the result
if theSubject is in subjectsWithEbook then
display dialog "Would you like to open the Folder or the Book?" buttons {"Folder", "Book"} default button 1
set toOpen to button returned of the result
if toOpen is "Book" then
-- tell app "Preview" to open ???
beep
else
tell application "Finder" to open folder (subjectFolderPath & theSubject & ":")
end if
else
tell application "Finder" to open folder (subjectFolderPath & theSubject & ":")
end if
You might also want to have a list of subjects that are allowed and error check for them.

AppleScript: Get list of windows on all desktops

I need to get a count of all windows per application. Every way I've tried to do this, I only get a count of windows that are assigned to the current (Mission Control) Desktop. (I'm currently running Mac OS X 10.7, so post-Spaces.) Is there any way to get a per-application count of all windows across all Desktops?
The crux of what I've tried:
tell application "System Events"
repeat with _app in (every process whose visible is true)
tell _app
log (name as string) & ": " & (count of every window)
end tell
end repeat
end tell
Note that the whose visible is true clause isn't the problem. It finds all of the appropriate processes, but once I ask the processes for windows, they only count the ones in the active Desktop.
I've tried pulling the log line out of the tell and using name of _app and count of every window of _app, but there's no difference. I've tried grabbing things other than processes from System Events, but anything useful ends up effectively being just a different way to get the same object. I've tried iterating over UI elements, but no windows show up that aren't on the current Desktop, though I do get a menubar for each application.
I'm fine with iterating across all Desktops (though not actually switching to all of them), but I can't even find a way to get a list of Desktops. This answer claims to describe how to do that, but I only ever get a single element inside every desktop. Not that there's an obvious way to get windows once you have that Desktop object anyway.
It's also worth pointing out that desktops are controlled by the Dock, and not by Mission Control. I'm not aware of any way for AppleScript to talk to the Dock, so if you know of something, then an answer or comment about that might help point me in the right direction.
Am I trying to do something impossible?
I ran your code, setting up the Applescript version to 2.4 (do that as a habit); on the first run, your code presented me with the appropriate count of all the windows per application.
This code is what I tried, and the results seem to be satisfactory. Is there something I'm not seeing?
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
tell application "System Events"
set my_list to {}
repeat with _app in (every process whose visible is true)
tell _app
log (name as string) & ": " & (count of every window)
set my_list to my_list & {name as string, count of every window}
end tell
end repeat
log my_list
end tell
As the OP is over six years old and I am unable to test under OS X 10.7, which is what was being used at that time, nonetheless, the following example AppleScript code works for me under macOS Catalina and returned the correct window count across all Desktops/Spaces, with the exception of any application that does understand the given AppleScript command and why the try and on error statements are being used.
Example AppleScript code:
tell application "System Events" to ¬
set appBundleIdentifierList to ¬
the bundle identifier of ¬
(every process whose visible is true)
repeat with appBundleIdentifier in appBundleIdentifierList
try
tell application id appBundleIdentifier to ¬
set {appName, winCount} to {name, (count windows)}
log appName & ": " & winCount
on error errorMessage
log errorMessage
end try
end repeat
Sample output on my system that has multiple Desktops/Spaces with windows of the application on all or some of the Desktops/Spaces and the window count for each is correct across all Desktops/Spaces, not just the active Desktop/Space the script was run from.
(*Safari: 6*)
(*Terminal: 2*)
(*TextEdit: 4*)
(*Script Editor: 7*)
(*Finder: 3*)
(*BBEdit: 1*)
(*Norton Secure VPN got an error: every window doesn’t understand the “count” message.*)
(*Music: 2*)
Notes:
Not all applications are AppleScript scriptable, in that some do not contain an AppleScript dictionary within their application bundle.
Since the application process cannot return the correct number of windows across all Desktops/Spaces, this method relies on the application to return the number of windows across all Desktops/Spaces.
Update:
The following example AppleScript code does the following:
Gets the bundle identifier of every process whose visible is true.
For each bundle identifier, get its name and the window count by querying the application directly. If the application understands the AppleScript command, then if goes to the next item in the appBundleIdentifierList list. If it does not understand, then the window count is calculated by the following:
Attempts to get an invisible window count as they would not show up on the Window menu of an application.
Calculates the window count by the number of windows shown on the Window menu of the application.
Failing these methods it get the window count by querying the application process, with is only accurate fo the active Desktop/Space and is included only for completeness of trying to ascertain the window count just using basic vanilla AppleScript.
Goes to the the application in the appBundleIdentifierList list.
Example AppleScript code:
set menuName to "Window"
tell application id "com.apple.systemevents" to ¬
set appBundleIdentifierList to ¬
the bundle identifier of ¬
(every process whose visible is true)
repeat with appBundleIdentifier in appBundleIdentifierList
try
tell application id appBundleIdentifier to ¬
set {appName, winCount} to {name, (count windows)}
log appName & ": " & winCount & ¬
" -- By querying the application directly."
on error
set winCount to 0
set notVisibleWindowList to {}
set errAppName to ¬
name of application id appBundleIdentifier
tell application id "com.apple.systemevents"
try
tell application process errAppName
set notVisibleWindowList to ¬
(windows whose visible is false)
if notVisibleWindowList is {} then ¬
set winCount to ¬
length of notVisibleWindowList
end tell
end try
try
set theTargetMenuItemsList to ¬
the reverse of ¬
(get name of ¬
menu items of ¬
menu menuName of ¬
menu bar item menuName of ¬
menu bar 1 of ¬
application process errAppName)
on error
set theTargetMenuItemsList to {}
end try
end tell
if theTargetMenuItemsList is not {} then
repeat with anItem in theTargetMenuItemsList
if contents of anItem is ¬
missing value then exit repeat
set winCount to winCount + 1
end repeat
log errAppName & ": " & winCount & ¬
" -- By querying the Window menu of the application process."
else
try
tell application id "com.apple.systemevents" to ¬
set winCount to ¬
(count windows of ¬
application process errAppName)
log errAppName & ": " & winCount & ¬
" -- By querying the application process. " & ¬
"May not be accurate, verify as necessary."
end try
end if
end try
end repeat
Running both versions of the example AppleScript code to show the difference in output:
First version of example AppleScript code:
(*Safari: 6*)
(*TextEdit: 4*)
(*Finder: 3*)
(*BBEdit: 1*)
(*Norton Secure VPN got an error: every window doesn’t understand the “count” message.*)
(*Music: 2*)
(*Sublime Text 2 got an error: every window doesn’t understand the “count” message.*)
(*DiskCatalogMaker got an error: every window doesn’t understand the “count” message.*)
(*Script Editor: 7*)
(*System Preferences: 2*)
(*VMware Fusion got an error: every window doesn’t understand the “count” message.*)
(*Activity Monitor got an error: every window doesn’t understand the “count” message.*)
(*Terminal: 2*)
Second version of example AppleScript code:
(*Safari: 6 -- By querying the application directly.*)
(*TextEdit: 4 -- By querying the application directly.*)
(*Finder: 3 -- By querying the application directly.*)
(*BBEdit: 1 -- By querying the application directly.*)
(*Norton Secure VPN: 0 -- By querying the application process. May not be accurate, verify as necessary.*)
(*Music: 2 -- By querying the application directly.*)
(*Sublime Text 2: 4 -- By querying the Window menu of the application process.*)
(*DiskCatalogMaker: 2 -- By querying the Window menu of the application process.*)
(*Script Editor: 7 -- By querying the application directly.*)
(*System Preferences: 2 -- By querying the application directly.*)
(*VMware Fusion: 1 -- By querying the Window menu of the application process.*)
(*Activity Monitor: 0 -- By querying the Window menu of the application process.*)
(*Terminal: 2 -- By querying the application directly.*)
As you can see even Activity Monitor, a native default macOS application, the Window menu had to be queried as the application directly didn't understand the basic count windows AppleScript command.
Although the output of the second version of the code was accurate across all Desktops/Spaces at the time it was executed, any application that has "By querying the application process. May not be accurate, verify as necessary." as part of its output only includes the window count of the active Desktop/Space it was executed form. The bottom line is using basic vanilla AppleScript there is no guarantee to get a complete accurate window count of every visible application unless all the applications at that time cen be queried directly. Querying the Window menu by its application process should also be accurate.
With that said, I think other methods may need to be used to get an accurate count.

Smooth UI changes in cocoa-applescript (Xcode)

I'm trying to write a simple Xcode cocoa-applescript program that executes a bash script on each subfolder of a folder and shows a progressbar while doing it.
I have a NSProgressIndicator set up to be linked to barStatus and well a NSTextField to be linked to lblStatus to show some informative text:
property barStatus : missing value
property lblStatus : missing value
this is the loop where the main action takes place:
tell application "Finder" to set subfolders to every folder of folder macPath
barStatus's setIndeterminate_(false)
barStatus's setDoubleValue_(0)
barStatus's setMaxValue_(count of subfolders)
repeat with eachFolder in subfolders
set posixPath to (POSIX path of (eachFolder as text)) as text
-- set text of progress indicator text
lblStatus's setStringValue_("Resizing '" & ( do shell script "basename \"" & (posixPath) & "\" | sed \"s/^.*_//\"" ) & "'...")
delay 0.5
-- do the shell script
do shell script "bash ~/.uploader/image.sh \"" & posixPath & "\""
-- step the indicator
barStatus's incrementBy_(1)
end repeat
lblStatus's setStringValue_("Done!")
All seems to work properly, yet the UI is somewhat glitchy. Instead of just increasing smoothly, the progressbar disappears on each step and gets shown for a short while, then dissappears again. The text in lblStatus does get changed smoothly.
Things get totally lost when I remove the delay from the loop: no UI changes are made (even though the scripts get run properly) until the loop is finished. So the progressbar just dissappears and reappears filled out when the loop is done.
Here's a youtube video of the flickery UI.
What am I doing wrong? How can I have xcode draw the progressbar smoothly?
Note that this is the first time I write an Xcode app, and that my knowledge of Applescript is somewhat sketchy.
EDIT:
I found that calling the function that processes the same function does not have a flickery UI when called form a menu item (or with a key combo bound to that menu item).
I'm not sure why the progress bar hides itself. Did you bind the visible property of it to something that may make it hide?
Things get totally lost when I remove the delay from the loop: no UI
changes are made
In general though when "no UI changes are made" it's because the application is too busy on the main thread to update the interface items in real time. All of your code is running on the main thread. You need to make some of it happen on a background thread. As such I have 2 suggestions for you to try.
First try using the progress bar's method "setUsesThreadedAnimation:". Add this just above your repeat loop...
barStatus's setUsesThreadedAnimation_(true)
Second, if that doesn't help then try moving your repeat loop work onto a background thread (using NSThread's detachNewThreadSelector:)... but make the interface updates happen on the main thread. I don't know ApplescriptObjC language so the following code is probably completely wrong. You'll have to write it properly but it will show you the idea...
[NSThread detachNewThreadSelector:#selector(processFolderListOnBacgroundThread_) toTarget:self withObject:subfolders];
-- this will happen on the main thread
on updateProgressBarByOne_()
barStatus's' incrementBy_(1)
end updateProgressBarByOne_
-- this will happen on the main thread
on updateStatusTextWithText_(statusText)
lblStatus's' setStringValue_(statusText)
end updateInterfaceItemsOnMainThreadWithObject_
-- this will happen on a background thread
on processFolderListOnBacgroundThread_(subFolders)
repeat with eachFolder in subfolders
set posixPath to (POSIX path of (eachFolder as text)) as text
-- set text of progress indicator text
my updateStatusTextWithText_("Resizing '" & ( do shell script "basename \"" & (posixPath) & "\" | sed \"s/^.*_//\"" ) & "'...")
-- do the shell script
do shell script "bash ~/.uploader/image.sh \"" & posixPath & "\""
-- step the indicator
my updateProgressBarByOne_()
end repeat
my updateStatusTextWithText_("Done!")
end processFolderListOnBacgroundThread_
If you use the background thread approach you'll probably have to make the buttons in your interface inactive while the background thread is working (so the user can't press them and start a new task). So just set their enabled property to false prior to calling "detachNewThreadSelector:" and enable them again after the work is finished. You can do this by having another handler which enables them and call that from the end of the background thread code.

Applescript studio - how do I get every control in a window

I'm trying to enable or disable all the control in a window as the programme changes from interactive to non-interactive mode. How can I ask a window to give me all its contents?
every control of window "mainWindow"
doesn't work, nor does
contents of window "mainWindow"
Actually, I haven't been able to find any good documentation for interacting with menu items from interface builder at all. Things like how to set the contents of popups, and buttons and so on.
thanks
The way I do it at the moment is:
property onlineControls: {"maxLength", "speed", "accelerationSlider", "accelerationField", "showInfo"} --and so on, listing all the controls by name
on enableControls(theList, enableState)
tell window "mainWindow"
repeat with theControl in theList
set the enabled of control theControl to enableState
end repeat
end tell
enableControls(onlineControls, true)
I've made several lists of controls tht get turned on or off depending on the state the programme is in. But it has to be hard coded, which I don't see as being the best way.
tell application "System Events"
tell process "Adium"
get entire contents of window 1
end tell
end tell
This script will give you as result all contents of front window of Adium: butons of window, tool bars of window, buttons of tool bars, etc. Enjoy =]
I haven't been able to find a way to get all the controls in a window, but here's an example of interacting with the menu of a popup button:
tell menu of popup button "somePopupButton" of window "mainWindow"
delete every menu item
repeat with i in someItems
make new menu item at end of menu items ¬
with properties {title:i, enabled:true}
end repeat
end tell
Is the same script as "BoB1990" with the possibility of getting back the information given by get entire contents of window in a string of whom you can observe or modify all the items listed :
tell application "System Events" to tell process "Adium"
set this_info to {}
try
display alert ((get entire contents of window (x as integer)))
on error errMsg set theText to errMsg
set this_info to do shell script " echo " & theText & " | sed 's#System Events got an error: Can’t make ##g;s# into type string.##g'"
end try
set info to {}
set info to do shell script " echo " & this_info
display alert (info)
end tell

Resources