LaunchAgent - Pass Parameters to Application - applescript

I have a LaunchAgent which invokes an application when the user logs in. The application loads a website.
LaunchAgent
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>LaunchAgent.Test</string>
<key>RunAtLoad</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>/Applications/MyApp.app/Contents/MacOS/applet</string>
</array>
</dict>
</plist>
Application
display alert set_cookies_for_URL("http://www.apple.com")
(* AppleScript's handlers all seem to become unusable after importing frameworks.
* To compensate, I'm relegating AppleScript/ObjC calls to the end of the file
*)
use framework "WebKit"
on set_cookies_for_URL(URL)
set web_view to current application's WebView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 500, 500)) frameName:"tempFrame" groupName:"tempGroup"
set web_view's mainFrameURL to URL
#delay to avoid release
repeat while ((web_view's isLoading) as boolean) is true
delay 1
end repeat
return "done"
end set_cookies_for_URL
How would I go about passing the website parameter into the application from the LaunchAgent?

You can use this. The second argument (after the executable) should be "-" then the third should be "http://www.apple.com" or whatever you want it to be:
use framework "WebKit"
use scripting additions
set argv to (current application's NSProcessInfo's processInfo()'s arguments as list)
if (count of argv) is 3 then
set_cookies_for_URL(item 3 of argv)
end if
quit
on set_cookies_for_URL(URL)
set web_view to current application's WebView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, 500, 500)) frameName:"tempFrame" groupName:"tempGroup"
set web_view's mainFrameURL to URL
#delay to avoid release
repeat while ((web_view's isLoading) as boolean) is true
delay 1
end repeat
end set_cookies_for_URL
EDIT: Oh yes, and I figured out why AppleScript wasn't recognizing "display alert". You have to declare "use scripting additions" to enable full AppleScript when importing a framework..

Related

Nokogiri: parse and update value in Plist

I'm new to Ruby and need to process some plist for a book store, say if I have a plist of books,
<plist>
<array>
<dict>
<key>Name</key>
<string>An Old Man and the Sea</string>
<key>Available</key>
<true/>
</dict>
<dict>
<key>Name</key>
<string>The Hitchhiker's Guide To Galaxy</string>
<key>Available</key>
<false/>
</dict>
</array>
</plist>
I need to switch the availability of books, and I have read the plist and parsed the array of books into books:
books.map do |book|
book.xpath("key/text()") # e.g. ["Name", "Available"]
book.xpath("string/text()") # e.g. ["An Old Man and the Sea"]
end
Questions:
How do I read the value <true/> and <false/>?
How do I update the value and save the plist?
Thanks!
If you don't mind using a gem, there is plist_lite.
ruby -r plist_lite -e 'p PlistLite.load($stdin.read)' <<EOS
<plist>
<array>
<dict>
<key>Name</key>
<string>An Old Man and the Sea</string>
<key>Available</key>
<true/>
</dict>
<dict>
<key>Name</key>
<string>The Hitchhiker's Guide To Galaxy</string>
<key>Available</key>
<false/>
</dict>
</array>
</plist>
EOS
output:
[{"Name"=>"An Old Man and the Sea", "Available"=>true},
{"Name"=>"The Hitchhiker's Guide To Galaxy", "Available"=>false}]
If you want to do it yourself with nokogiri, you can refer to plist_lite's source code here, it also uses nokogiri:
https://github.com/tonytonyjan/plist_lite/blob/2de0506/lib/plist_lite.rb#L37-L57
To answer your question:
How do I read the value <true/> and <false/>?
parsed = PlistLite.load(plist_string)
parsed.map{ _1['Available'] }
How do I update the value and save the plist?
parsed = PlistLite.load(plist_string)
parsed.first['Available']
IO.write 'test.plist', PlistLite.dump(parsed)

Nokogiri for reading and editing Plist

I need to read and edit part of a plist file, it looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Bookstore</key>
<dict>
<key>Public</key>
<dict>
<key>Books</key>
<array>
<dict>
<key>Name</key>
<string>The Old Man and the Sea</string>
<key>Author</key>
<string>Hemingway</string>
</dict>
<dict>
<key>Name</key>
<string>A Brief History of Time</string>
<key>Author</key>
<string>Stephen Hawkin</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
What I've done so far:
require 'nokogiri'
doc = doc = Nokogiri::XML(File.open("books.plist"))
doc.xpath("//dict[key=\"Bookstore\"]/dict[key=\"Public\"]/dict[key=\"Books\"]")
If I want to get an array of hashes (in Ruby) of elements under "Book", what should I do next?
Thanks!
I think you can expand your selection to grab exactly the elements you need:
require 'nokogiri'
doc = Nokogiri::XML(File.open("books.plist"))
books = doc.xpath("//dict[key='Bookstore']/dict[key='Public']/dict[key='Books']/array/dict")
Then you can convert this array of book elements into the shape you want by sub-selecting, or traversing, but sub-selecting might look like this:
final = books.map do |book|
keys = book.xpath("key/text()").map(&:to_s)
values = book.xpath("string/text()").map(&:to_s)
Hash[keys.zip(values)]
end
You can use Ruby gems like plist_lite
To answer your question:
If I want to get an array of hashes (in Ruby) of elements under "Book", what should I do next?
ruby -rplist_lite -e 'pp PlistLite.load($stdin.read)["Bookstore"]["Public"]["Books"]' <<EOS
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Bookstore</key>
<dict>
<key>Public</key>
<dict>
<key>Books</key>
<array>
<dict>
<key>Name</key>
<string>The Old Man and the Sea</string>
<key>Author</key>
<string>Hemingway</string>
</dict>
<dict>
<key>Name</key>
<string>A Brief History of Time</string>
<key>Author</key>
<string>Stephen Hawkin</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
EOS
output
[{"Name"=>"The Old Man and the Sea", "Author"=>"Hemingway"},
{"Name"=>"A Brief History of Time", "Author"=>"Stephen Hawkin"}]
If you don't want to require extra dependency, you can still refer to it's source code, plist_lite uses nokogiri for parsing as well:
https://github.com/tonytonyjan/plist_lite/blob/master/lib/plist_lite.rb#L37-L57
To update the plist file, use PlistLite#dump:
obj = PlistLite.load(IO.read('test.plist'))
# modify obj...
IO.write('test.plist', PlistLite.dump(obj))

Carbon - Get labels for CopySymbolicHotkeys?

This answer about CopySymbolicKeys() gets you the keyboard combinations for System Shortcuts in OS X, but is there any way to get at the labels associated with those combinations?
For example, I can get ⌥⌘D from CopySymbolicKeys() but I want to get "Turn Dock Hiding On/Off", its associated label in System Preferences > Keyboard > Shortcuts.
I'm think it's unlikely, but remain hopeful.
As far as I know, there is no API for that, but it is possible to reconstruct what you want. It will need some work from you though.
You can get the names of the symbolic keys here:
Earlier macOS versions: System/Library/PreferencePanes/Keyboard.prefPane/Contents/Resources/en.lproj/DefaultShortcutsTable.xml
macOS 13 Ventura: /System/Library/ExtensionKit/Extensions/KeyboardSettings.appex/Contents/Resources/en.lproj/DefaultShortcutsTable.xml
You need to parse this XML file and then find the corresponding hotkey here:
~/Library/Preferences/com.apple.symbolichotkeys.plist
The matching must be done between key sybmolichotkey (yes, the key is misspelled like that!) in the aforementioned XML file with key called key containing the same number in the latter plist file.
Example taken from the DefaultShortcutsTable.xml file:
<dict>
<key>name</key>
<string>DO_NOT_LOCALIZE: Dashboard and Dock</string>
<key>ax_description</key>
<string>DO_NOT_LOCALIZE: Dashboard and Dock shortcuts (AX_DESCRIPTION)</string>
<key>identifier</key>
<string>dock</string>
<key>iconname</key>
<string>category_dock</string>
<key>icon-bundle-path</key>
<string>/System/Library/CoreServices/Dock.app</string>
<key>canRestoreDefaults</key>
<true/>
<key>elements</key>
<array>
<dict>
<key>name</key>
<string>DO_NOT_LOCALIZE: Turn Dock Hiding On/Off</string>
<key>key</key>
<integer>2</integer>
<key>modifier</key>
<integer>1572864</integer>
<key>sybmolichotkey</key> <--- look for this key
<integer>52</integer> <--- and its value
<key>charKey</key>
<integer>100</integer>
</dict>
<dict>
<key>name</key>
<string>DO_NOT_LOCALIZE: Show Launchpad</string>
<key>key</key>
<integer>65535</integer>
<key>modifier</key>
<integer>0</integer>
<key>sybmolichotkey</key> <--- look for this key
<integer>160</integer> <--- and its value
</dict>
</array>
</dict>
And then look for the same number in com.apple.symbolichotkeys.plist:
<key>52</key>
<dict>
<key>enabled</key>
<true/>
<key>value</key>
<dict>
<key>parameters</key>
<array>
<integer>65535</integer>
<integer>2</integer>
<integer>1572864</integer>
</array>
<key>type</key>
<string>standard</string>
</dict>
</dict>
Major caveat: the latter plist is in binary form. To convert it to text, you need to do this in Terminal:
plutil -convert xml1 ~/Library/Preferences/com.apple.symbolichotkeys.plist

Error after using Mac OS bless command: "no bootable device"

I've created an applescript to boot from snow leopard to lion, but the bless command is failing. Here's the command:
do shell script "bless -mount /Volumes/bootdrive/ -legacy -setBoot -nextonly" password "mypassword" with administrator privileges
On reboot, I get the black screen with 'No bootable device' error. I've run the command directly in the terminal as root (rather than as an applescript) and have gotten the same result. And yes, I've triple-checked that the disk path I'm using is correct and is bootable.
Any idea what the issue could be?
I don't have an appropriate setup to test with, but the -legacy option looks very suspicious to me. According to the man page, that's used to support BIOS-based operating systems, which OS X isn't. Try removing -legacy and see if it works better.
Working solution
The SIP was the first problem I've encountered on Big Sur. Turned it off looks like a bad idea. The second problem was target volumes list items doesn't have actions. Which make impossible to click on them via click or "click at" functions perhaps because of some new additional protections on Big Sur. Click with AST and other scripts also doesn't works due to new MacOS restrictions. The only way I found is using python click(but this leads to a slight delay while script selects target volume).
So here is a fully automated switching:
property targetVolume : "BOOTCAMP" # find name of required volume inside System Preference > Startup Disk
property passwordValue : "yourSystemPassword" # Can be empty
tell application "System Events"
tell application "System Preferences"
set current pane to pane id "com.apple.preference.startupdisk"
activate
end tell
tell application process "System Preferences"
tell window "Startup Disk"
set volumePosition to {0, 0}
set lockFound to false
# Check if auth required
set authButtonText to "Click the lock to make changes."
if exists button authButtonText then
click button authButtonText
# Wait for auth modal
set unlockButtonText to "Unlock"
repeat
if (exists sheet 1) and (exists button unlockButtonText of sheet 1) then exit repeat
end repeat
# Autofill password if setted
if passwordValue is not equal to "" then
set value of text field 1 of sheet 1 to passwordValue
click button unlockButtonText of sheet 1
end if
# Wait for auth success
repeat
if exists button "Click the lock to prevent further changes." then exit repeat
end repeat
end if
# Wait until loading volumes list
repeat
if exists group 1 of list 1 of scroll area 1 then exit repeat
end repeat
# Click on target volume (posible a slight delay because of shell script executing)
repeat with m in (UI element of list 1 of scroll area 1)
if (value of first static text of m = targetVolume) then
tell static text targetVolume of m
set volumePosition to position
end tell
end if
end repeat
set volumePositionX to item 1 of volumePosition
set volumePositionY to item 2 of volumePosition
my customClick(volumePositionX, volumePositionY)
click button "Restart…"
# Wait for restart modal appears
repeat
if (exists sheet 1) and (exists value of first static text of sheet 1) then exit repeat
end repeat
click button "Restart" of sheet 1
end tell
end tell
end tell
# shell script to make click work on target volume
on customClick(x, y)
do shell script "
/usr/bin/python <<END
import sys
import time
from Quartz.CoreGraphics import *
def mouseEvent(type, posx, posy):
theEvent = CGEventCreateMouseEvent(None, type, (posx,posy), kCGMouseButtonLeft)
CGEventPost(kCGHIDEventTap, theEvent)
def mousemove(posx,posy):
mouseEvent(kCGEventMouseMoved, posx,posy);
def mouseclick(posx,posy):
mouseEvent(kCGEventLeftMouseDown, posx,posy);
mouseEvent(kCGEventLeftMouseUp, posx,posy);
ourEvent = CGEventCreate(None);
currentpos=CGEventGetLocation(ourEvent); # Save current mouse position
mouseclick(" & x & "," & y & ");
mousemove(int(currentpos.x),int(currentpos.y)); # Restore mouse position
END"
end customClick
on simpleEncryption(_str)
set x to id of _str
repeat with c in x
set contents of c to c + 100
end repeat
return string id x
end simpleEncryption
on simpleDecryption(_str)
set x to id of _str
repeat with c in x
set contents of c to c - 100
end repeat
return string id x
end simpleDecryption
You just need to change two properties targetVolume and passwordValue. Password can be empty and in that case you can provide it manually. Then just copy this script, paste it to the Script Editor and export via File -> Export -> file format - Application, select Run-only -> Save. You can do the same process for all systems you have, for example Big Sur 1, Big Sur 2, Bootcamp.

In BASH, how do I use defaults write on a plist for a existing element in an array?

I want to change the value of DEFAULT_VALUE_PLACEHOLDER in the following plist using the command line tool defaults
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>DefaultValue</key>
<string>DEFAULT_VALUE_PLACEHOLDER</string>
<key>Type</key>
<string>PSTitleValueSpecifier</string>
<key>Title</key>
<string>Version</string>
<key>Key</key>
<string>prefs_item_version_title</string>
</dict>
</array>
<key>StringsTable</key>
<string>Root</string>
</dict>
</plist>
I realise that a simple find and replace will do it (e.g. sed), however, I want a more robust way of doing it.
I think is something like this, but the documentation for the syntax isn't good enough.
defaults write $PLIST_PATH 'PreferenceSpecifiers { 1 = { DefaultValue = $NEW_DETAULT_VALUE; }; }'
I don't think there's any way to do this with defaults (that isn't completely ugly) -- you're better off doing things like this with PlistBuddy instead:
/usr/libexec/PlistBuddy -c "set :PreferenceSpecifiers:0:DefaultValue '$NEW_DEFAULT_VALUE'" "$PLIST_PATH"
Note that unlike defaults, PlistBuddy expects the filename you give it to include the ".plist"; also, (as seen above), array indexes start at 0.

Resources