AppleScript Prompt User for Clicks - applescript

For my studies I use a lot the screencapture feature as a terminal command. Recently I started to use AppleScript to automate some of those screen captures. But now I would like to push the thing a bit further.
Is it possible to use a command such as "display dialog", but to query 2 clicks, of which the coordinates would be assigned to the screencapture command so that it takes the picture of the screen?
I was thinking of something like this:
set click1 to "0,0"
set click2 to "0,0"
display dialog "Click where the screen area to grab begins" & click1
display dialog "Click where the screen area to grab begins" & click2
do shell script "screencapture -R click1,click2 /Users/user/Desktop/name_of_the_file.png"

Applescript does not have any mechanism for capturing mouse clicks, or even getting the position. You can get the mouse position using a call to the framework.
But, you will have to resort to a cocoa app solution to capture screen clicks.
One idea is you could use an applescript flow that gives you 2 seconds to place the first mouse position, then it can beep to tell you it has captured it, and then you have two more seconds to place the pointer in the second position before it captures it.
Something like this:
use framework "Foundation"
use scripting additions
display dialog "Place your mouse pointer in the first position within 2 seconds, then after the beep, place it in the second position within 2 seconds."
delay 2
set theList to current application's NSEvent's mouseLocation()
set xCoord1 to theList's x
set yCoord1 to theList's y
beep
delay 2
set theList to current application's NSEvent's mouseLocation()
set xCoord2 to theList's x
set yCoord2 to theList's y
beep
display dialog (((xCoord1 as string) & ", " & yCoord1 as string) & return & xCoord2 as string) & ", " & yCoord2 as string

Related

Making a dialog that bugs me in 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

Is there a way to set the output of an AppleScript to a certain color, and change depending on conditions?

I have a functioning script that looks at the activity state in a specific app, and time spent in that state, then shows it in the status bar of macOS. It works as expected, but I'd like to add some coloring so that if you go over a certain amount of time in a certain state, the text will turn yellow or red. For example, I might be in the Idle status for 8 minutes, which shows in the status bar, but when it reaches 10 minutes, I'd like for the text to change from white to red.
I've done some research on the 'attribute range' function, but I'm unsure of how that might be applied (or if it can be applied) to my script since I'm not working with text in Pages, Microsoft Word, Text Edit, or something similar, just a value that is returned to the status bar.
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
-- assume the window and toolbar are always going to be there
repeat until exists of first window's first toolbar's fourth group's first group's first menu button
delay 0.2
end repeat
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 time in seconds
*)
return 1
end idle
I'm wondering if I can use a command to set the 'activityState' attribute to a certain color since that variable has been defined to the appropriate area of that affected app's GUI, then to set conditions for that to change depending on the type of activity state, and time spent there.
An attributed string can be used for the status item title:
set greenAttr to current application's NSDictionary's dictionaryWithObjects:{current application's NSColor's greenColor()} forKeys:{current application's NSForegroundColorAttributeName}
set redAttr to current application's NSDictionary's dictionaryWithObjects:{current application's NSColor's redColor()} forKeys:{current application's NSForegroundColorAttributeName}
set attrText to current application's NSMutableAttributedString's alloc's initWithString:"whatever"
attrText's setAttributes:greenAttr range:{0, attrText's |length|()}
statusItem's button's setAttributedTitle:attrText
Then at some point the attributes can be changed or replaced and the title set with the new attributed string:
if elapsedTime > someTime then attrText's setAttributes:redAttr range:{0, attrText's |length|()}
statusItem's button's setAttributedTitle:attrText

AppleScript to click Wi-Fi icon with option down

I would like to click on the Wi-Fi icon with the option key down to reveal extra options available on Mac. How can I automate it using AppleScript?
I tried using key down option and click menu item but no luck in revealing special options.
Is there any way I can achieve this?
It's currently not possible to click with a key held down using AppleScript. Key down actions only apply to other key press actions, since the AppleScript click action doesn't actually perform a ‘click’, but rather directly actions the element.
If you don't mind using a 3rd party utility, here's an example AppleScript script that uses cliclick:
tell application "System Events"
tell application process "SystemUIServer"
set theWiFiProperties to item 1 of (get properties of every menu bar item of menu bar 1 whose description starts with "Wi-Fi")
end tell
set theXpos to (item 1 of position in theWiFiProperties) + ((item 1 of size in theWiFiProperties) / 2) as integer
set theYpos to (item 2 of position in theWiFiProperties) + ((item 2 of size in theWiFiProperties) / 2) as integer
end tell
tell current application
do shell script "/path/to/cliclick kd:alt c:" & theXpos & "," & theYpos & " ku:alt"
end tell
Note: Change /path/to/cliclick to the actual pathname of the cliclick executable.
How it works:
The theWiFiProperties variable gets set to the properties of the Wi-Fi menu extra and then the variables theXpos and theYpos get set to a position that together is the center of the Wi-Fi menu extra on the menu bar.
This info is then used in a do shell script command using cliclick to press the option key down, click at the designated x,y coordinates and let the option key up.
You can use Automator and record the process using “Watch me do” and then save the automated workflow as an application or a dictation command.
In Automator, I saved the watch me do action as an application. I named this new application “Extended_Wifi.app”. Then I had to add this application in system preferences to be able to control my computer.
Personally, I prefer to use Scripteditor rather than Automator because a huge part of me feels like using Automator is cheating. But at the end of the day, I was able to save the Automator action as an application and it functions perfectly however in Scripteditor, I Could not get the AppleScript version of the action to function correctly.
Here is a quick .gif showing the Automator application working correctly.

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