├── Automating macOS with Javascript for Automation (JXA).md ├── Automating macOS with Javascript for Automation (JXA).pdf ├── README.md ├── media ├── gifs │ ├── applescript.gif │ └── javascript.gif ├── screenshots │ ├── 2011.jpg │ ├── Aperture.jpg │ ├── ExportApplet.jpg │ ├── FinderServices.jpg │ ├── MojaveSidebar.jpg │ ├── PhotosSdef.jpg │ ├── QLScpt.jpg │ ├── SafariDebugger.jpg │ ├── SafariPrefs.jpg │ ├── ScriptEditor1.jpg │ ├── ScriptEditor2.jpg │ ├── Scripts.jpg │ ├── Service.jpg │ ├── TouchBar.jpg │ └── scpt.jpg └── video │ └── REPL.mp4 └── src ├── args └── args.sh ├── calculator ├── Calculator.app │ └── Contents │ │ ├── Info.plist │ │ ├── MacOS │ │ └── applet │ │ ├── PkgInfo │ │ ├── Resources │ │ ├── Scripts │ │ │ └── main.scpt │ │ ├── applet.icns │ │ ├── applet.rsrc │ │ └── description.rtfd │ │ │ └── TXT.rtf │ │ └── _CodeSignature │ │ └── CodeResources ├── Calculator.js └── Calculator.scpt ├── crop image └── Crop Image.workflow │ └── Contents │ ├── Info.plist │ ├── QuickLook │ └── Preview.png │ └── document.wflow ├── define └── define.sh └── safari └── Create Tab.js /Automating macOS with Javascript for Automation (JXA).md: -------------------------------------------------------------------------------- 1 | # JavaScript for Automation (JXA) ⚡️💻 2 | ## @joshparnham 3 | 4 | --- 5 | 6 | ^ I'm Josh, a developer working on iOS and JS stuff at Xero. 7 | 8 | ^ Talk was originally scheduled for May, but moved a month later. Since some of the example code and copyright information hasn't been touched since 2011 I was really worried that Apple would deprecate the scripting architecture at WWDC - thankfully the opposite happened! 9 | 10 | # 😅 11 | 12 | --- 13 | 14 | ![inline](media/screenshots/2011.jpg) 15 | 16 | --- 17 | 18 | ^ Aperture was discontinued in 2014/15 19 | ^ A `.scptd` file is a "Script Bundle" 20 | 21 | ![inline](media/screenshots/Aperture.jpg) 22 | 23 | --- 24 | 25 | ## Mac Automation ❓ 26 | 27 | ^ macOS has set of rich built-in frameworks for automation. 28 | 29 | - Automating tasks on macOS 30 | - Scripting supported Applications 31 | - UI element automation 32 | - System APIs 33 | 34 | --- 35 | 36 | ^ Traditionally macOS scripting has been accomplished with AppleScript, which is a language developed by Apple in 1993 - its goal was to create a programming language that featured an "English-like" syntax. As such, it can be very verbose and can feature a large learning curve for those who are more familiar with traditional languages. 37 | 38 | # AppleScript 39 | 40 | --- 41 | 42 | # AppleScript 43 | 44 | > "If you're used to a programming language, AppleScript will drive you crazy" 45 | -- Sal Soghoian 46 | 47 | --- 48 | 49 | ^ That is not ideal. 50 | 51 | ![](media/gifs/applescript.gif) 52 | 53 | --- 54 | 55 | ^ However thankfully, you can use JavaScript as an alternative to AppleScript, and since you're all sitting in this room with me tonight I'll assume that you also think this is absolutely awesome. 56 | 57 | # JavaScript 58 | 59 | --- 60 | 61 | ![](media/gifs/javascript.gif) 62 | 63 | --- 64 | 65 | ## History 📚 66 | 67 | ^ This JavaScript scripting support was added in OS X Yosemite. 68 | 69 | - OS X 10.10 Yosemite (2014) 70 | 71 | ^ The OSA makes use of Apple Events, which is an IPC mechanism that encapsulates commands and arbitrary data. 72 | 73 | - Open Scripting Architecture 74 | - `osascript`/`osacompile` and friends 75 | 76 | ^ JavaScriptCore is a wrapper around WebKit's JavaScript engine 77 | 78 | - JavaScript implementation based on `JavaScriptCore` 79 | 80 | --- 81 | 82 | ## Basics 👩‍🏫 83 | 84 | ```js 85 | const Safari = Application('Safari') 86 | const firstWindow = Safari.windows[0] 87 | const tab = Safari.Tab({url: "https://joshparnham.com"}) 88 | firstWindow.tabs.push(tab) 89 | firstWindow.currentTab = tab 90 | ``` 91 | 92 | --- 93 | 94 | # Script Editor.app 📜 95 | 96 | ^ The main IDE that you'll be using to write your automation scripts is Script Editor, just make sure you've selected JavaScript as the language. 97 | 98 | --- 99 | 100 | ![inline fill 99%](media/screenshots/ScriptEditor1.jpg) 101 | 102 | --- 103 | 104 | ![inline fill 99%](media/screenshots/ScriptEditor2.jpg) 105 | 106 | --- 107 | 108 | # Documentation 📕 109 | 110 | ^ The Script Editor is also where you can read documentation for both the scriptable apps and the system scripting features. 111 | 112 | ^ Can drag an application onto Script Editor, and if it has a `.sdef` file, those contents will be displayed. A `sdef` file is an XML-based file type which maps OSA commands to cocoa methods. 113 | 114 | ^ Viewing the Library (Window > Library) also lists documentation about various applications and system libraries. 115 | 116 | ![inline](media/screenshots/PhotosSdef.jpg) 117 | 118 | --- 119 | 120 | ## Debugging 🐛 121 | 122 | ![inline](media/screenshots/SafariPrefs.jpg) 123 | 124 | --- 125 | 126 | ## Debugging 🐛 127 | 128 | - `debugger;` statement in Script Editor 129 | 130 | --- 131 | 132 | ## Debugging 🐛 133 | 134 | ![inline](media/screenshots/SafariDebugger.jpg) 135 | 136 | --- 137 | 138 | # Invocation ❇️ 139 | 140 | ^ Can open a `.scpt` file from Finder, etc 141 | ^ An scpt file is a binary representation of an AppleScript or JavaScript file 142 | ^ A plain-text AppleScript or JavaScript file can be converted into a compiled `.scpt` file from the Script Editor IDE or from the command line with `osacompile` (and visa-versa with `osadecompile`) 143 | 144 | - `.scpt` file 145 | 146 | ![inline](media/screenshots/scpt.jpg) 147 | 148 | --- 149 | 150 | # Invocation ❇️ 151 | 152 | ^ You can package your script as an applet which is treated like a regular application (eg. can drag items onto it when in the dock) 153 | 154 | - Applet 155 | 156 | ![inline](media/screenshots/ExportApplet.jpg) 157 | 158 | --- 159 | 160 | # Invocation ❇️ 161 | 162 | ^ Using Automator, you can create a macOS service which executes your JXA wherever an application services menu is. For example, you can create a workflow which accepts folders from the Finder and invokes the script with that. 163 | 164 | - Services menu 165 | 166 | ![inline](media/screenshots/Service.jpg) 167 | 168 | --- 169 | 170 | # Invocation ❇️ 171 | 172 | ![inline](media/screenshots/FinderServices.jpg) 173 | 174 | --- 175 | 176 | 177 | # Invocation ❇️ 178 | 179 | ^ This adds a menu bar item which gives you access to scripts in predefined paths on the system. 180 | 181 | - System-wide Scripts menu 182 | 183 | ![inline](media/screenshots/Scripts.jpg) 184 | 185 | --- 186 | 187 | # Invocation ❇️ 188 | 189 | ^ https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/WatchFolders.html#//apple_ref/doc/uid/TP40016239-CH39-SW1 190 | 191 | - Folder contents changed 192 | - Calendar alarms 193 | - Dictation command 194 | - and others 🚀 195 | 196 | --- 197 | 198 | # Even Better Invocation 🏜 199 | 200 | ^ Next macOS improves the accessibility of automation scripts in the Finder. 201 | 202 | ![inline 35%](media/screenshots/MojaveSidebar.jpg) 203 | 204 | --- 205 | 206 | # Even Better Invocation 🏜 207 | 208 | ![inline 50%](media/screenshots/TouchBar.jpg) 209 | 210 | --- 211 | 212 | # REPL ⌨️ 213 | 214 | ```sh 215 | $ osascript -i -l JavaScript 216 | ``` 217 | 218 | --- 219 | 220 | ![inline autoplay mute](media/video/REPL.mp4) 221 | 222 | --- 223 | 224 | ^ Moving on from there, the `osascript` command can also be used in scripts. 225 | 226 | # Scripting ⚡️ 227 | ## Example: `args.sh` 228 | --- 229 | 230 | ^ `padStart` is actually from ES2017 as well 231 | 232 | ```js 233 | #!/usr/bin/env osascript -l JavaScript 234 | 235 | run = argv => console.log( 236 | argv.map(x => x.padStart(10)).join('\n') 237 | ) 238 | ``` 239 | 240 | --- 241 | 242 | ```sh 243 | $ ./args.sh Hello there MelbJS! 👋 244 | 245 | Hello 246 | there 247 | MelbJS! 248 | 👋 249 | ``` 250 | 251 | --- 252 | 253 | # Interoperability ☕️ 254 | 255 | ^ Can use the standard JavaScript `replace` method... 256 | 257 | ```js 258 | const result = 'hello'.replace('e', '3') 259 | ``` 260 | 261 | --- 262 | 263 | # Interoperability ☕️ 264 | 265 | ^ ... or the much nicer Objective-C equivalent. 266 | 267 | ```js 268 | const result = ObjC.wrap('hello') 269 | .stringByReplacingOccurrencesOfStringWithString('e', '3').js 270 | ``` 271 | 272 | --- 273 | 274 | # Interoperability Examples ☕️ 275 | 276 | ```js 277 | #!/usr/bin/env osascript -l JavaScript 278 | 279 | ObjC.import('DictionaryServices') 280 | 281 | run = argv => { 282 | const word = argv[0] 283 | const range = { 'location': 0, 'length': word.length } 284 | let definition = $.DCSCopyTextDefinition(null, word, range) 285 | console.log(definition.js.split(" | ").join("\n")) 286 | } 287 | ``` 288 | 289 | --- 290 | 291 | # Interoperability Examples ☕️ 292 | 293 | ```sh 294 | $ ./define.sh JavaScript 295 | 296 | JavaScript 297 | ˈdʒɑːvəˌskrɪpt 298 | noun [mass noun] trademark an object-oriented computer programming 299 | language commonly used to create interactive effects within web browsers. 300 | ORIGIN 1990s: from Java2 + script1. 301 | ``` 302 | 303 | --- 304 | 305 | # Interoperability ☕️ 306 | 307 | - Called the `JavaScriptObjC` bridge 308 | 309 | --- 310 | 311 | ### JavaScript Context ➡️ Objective-C Context 312 | 313 | - Convert primitive JS type to ObjC object 314 | 315 | ```js 316 | ObjC.wrap(...) 317 | ``` 318 | 319 | ^ For some good old jQuery throwbacks, this is also aliased to $ 320 | 321 | ```js 322 | $(...) 323 | ``` 324 | 325 | --- 326 | 327 | ### Objective-C Context ➡️ JavaScript Context 328 | 329 | - Convert ObjC object to JS type 330 | 331 | ```js 332 | ObjC.unwrap(...) 333 | ``` 334 | 335 | ^ Aliased to appending `.js` at the end of your line 336 | 337 | ```js 338 | .js 339 | ``` 340 | 341 | --- 342 | 343 | ## Applications 🖥 344 | 345 | ^ Massive props to Tyler Gaw for his fantastic example repo, from which this example is adapted: https://github.com/tylergaw/js-osx-app-examples 346 | ^ There is an explanatory blog post here: https://tylergaw.com/articles/building-osx-apps-with-js/ 347 | 348 | - Interoperability with `AppKit` 349 | - Native UIs purely in (bridged) JavaScript 350 | 351 | --- 352 | 353 | ^ Cocoa encompasses Foundation, AppKit, and Core Data 354 | 355 | ```js 356 | ObjC.import('Cocoa') 357 | 358 | const resultString = (result) => `Result: ${ result ? result.toString() : ''}` 359 | 360 | ObjC.registerSubclass({ 361 | name: 'AppDelegate', 362 | methods: { 363 | 'calculate:': { 364 | types: ['void', ['id']], 365 | implementation: function (sender) { 366 | var total = 367 | Number(textField1.stringValue.js) + 368 | Number(textField2.stringValue.js) 369 | resultTextFieldLabel.stringValue = resultString(total) 370 | } 371 | } 372 | } 373 | }) 374 | ``` 375 | 376 | --- 377 | 378 | ```js 379 | var appDelegate = $.AppDelegate.alloc.init 380 | 381 | const styleMask = 382 | $.NSTitledWindowMask | 383 | $.NSClosableWindowMask | 384 | $.NSMiniaturizableWindowMask 385 | 386 | var window = 387 | $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer( 388 | $.NSMakeRect(0, 0, 250, 120), 389 | styleMask, 390 | $.NSBackingStoreBuffered, 391 | false 392 | ) 393 | ``` 394 | 395 | --- 396 | 397 | ```js 398 | var calculateButton = 399 | $.NSButton.alloc.initWithFrame( 400 | $.NSMakeRect(25, 20, 200, 25) 401 | ) 402 | calculateButton.title = 'Calculate 🧙‍♂️' 403 | calculateButton.bezelStyle = $.NSRoundedBezelStyle 404 | calculateButton.buttonType = $.NSMomentaryLightButton 405 | calculateButton.target = appDelegate 406 | calculateButton.action = 'calculate:' 407 | calculateButton.keyEquivalent = '\r' 408 | window.contentView.addSubview(calculateButton) 409 | ``` 410 | 411 | --- 412 | ```js 413 | var resultTextFieldLabel = 414 | $.NSTextField.alloc.initWithFrame( 415 | $.NSMakeRect(25, 45, 200, 24) 416 | ) 417 | resultTextFieldLabel.stringValue = resultString() 418 | resultTextFieldLabel.drawsBackground = false 419 | resultTextFieldLabel.editable = false 420 | resultTextFieldLabel.bezeled = false 421 | resultTextFieldLabel.selectable = true 422 | window.contentView.addSubview(resultTextFieldLabel) 423 | ``` 424 | 425 | --- 426 | 427 | ```js 428 | var textField1 = 429 | $.NSTextField.alloc.initWithFrame( 430 | $.NSMakeRect(25, 80, 90, 24) 431 | ) 432 | textField1.stringValue = '' 433 | window.contentView.addSubview(textField1) 434 | ``` 435 | 436 | --- 437 | 438 | ```js 439 | var plusTextFieldLabel = 440 | $.NSTextField.alloc.initWithFrame( 441 | $.NSMakeRect(120, 80, 24, 24) 442 | ) 443 | plusTextFieldLabel.stringValue = '+' 444 | plusTextFieldLabel.drawsBackground = false 445 | plusTextFieldLabel.editable = false 446 | plusTextFieldLabel.bezeled = false 447 | plusTextFieldLabel.selectable = true 448 | window.contentView.addSubview(plusTextFieldLabel) 449 | ``` 450 | 451 | --- 452 | 453 | ```js 454 | var textField2 = 455 | $.NSTextField.alloc.initWithFrame( 456 | $.NSMakeRect(135, 80, 90, 24) 457 | ) 458 | textField2.stringValue = '' 459 | window.contentView.addSubview(textField2) 460 | ``` 461 | 462 | --- 463 | 464 | ```js 465 | window.center 466 | window.title = 'Scientific Calculator 🙈' 467 | window.makeKeyAndOrderFront(window) 468 | ``` 469 | 470 | ^ You can save a script as an Application in Script Editor, or do the below. 471 | ^ Need to tick "Stay open after run handler" when exporting in Script Editor. 472 | 473 | --- 474 | 475 | # Packaging 📦 476 | 477 | Either through `Script Editor.app` or `osacompile`. 478 | 479 |
480 | 481 | ```sh 482 | osacompile -l JavaScript -o Calculator.app -s Calculator.js 483 | ``` 484 | 485 | --- 486 | 487 | # *Demo* 488 | 489 | --- 490 | 491 | ## PS 🐙 492 | 493 | QLScpt 494 | 495 | ```sh 496 | brew cask install josh-/qlscpt/qlscpt 497 | ``` 498 | 499 | ![inline](media/screenshots/QLScpt.jpg) 500 | 501 | --- 502 | 503 | ## Caveats ✋ 504 | 505 | ^ The documentation is lacking - the main documentation reference contains only two entries for old macOS releases: https://developer.apple.com/library/archive/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/Introduction.html#//apple_ref/doc/uid/TP40014508-CH111-SW1 506 | 507 | ^ The best documentation is probably the "About Mac Scripting" guide: https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/index.html#//apple_ref/doc/uid/TP40016239-CH56-SW1 508 | 509 | ^ Sample code can also be wrong as well 510 | 511 | ^ The JXA cookbook has a lot of extra information not covered in the official docs: https://github.com/JXA-Cookbook/JXA-Cookbook 512 | 513 | - Limited documentation 514 | 515 | ^ I've found that `const` variables can persist across invocations within Script Editor, as well as a variety of other issues. 516 | 517 | - Buggy 518 | 519 | ^ Most of the sample code and open source code is written in AppleScript, and it can be non-trivial to convert between the languages. 520 | 521 | - Majority of automation code is still AppleScript 522 | 523 | - 🐲 **Here be dragons** 🐉 524 | 525 | --- 526 | 527 | # Thanks ✌️ 528 | ## @joshparnham 529 | 530 | ^ These slides will be put up in a repo on my GitHub 531 | 532 | ## github.com/josh- 533 | -------------------------------------------------------------------------------- /Automating macOS with Javascript for Automation (JXA).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/Automating macOS with Javascript for Automation (JXA).pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automating macOS with Javascript for Automation (JXA) 2 | 3 | Made with [Deckset](https://www.decksetapp.com/). 4 | 5 | Resources: 6 | - [PDF of the slides](https://github.com/josh-/automating-macOS-with-JXA-presentation/blob/master/Automating%20macOS%20with%20Javascript%20for%20Automation%20%28JXA%29.pdf) 7 | - [Sample Code](https://github.com/josh-/automating-macOS-with-JXA-presentation/tree/master/src) 8 | -------------------------------------------------------------------------------- /media/gifs/applescript.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/gifs/applescript.gif -------------------------------------------------------------------------------- /media/gifs/javascript.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/gifs/javascript.gif -------------------------------------------------------------------------------- /media/screenshots/2011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/2011.jpg -------------------------------------------------------------------------------- /media/screenshots/Aperture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/Aperture.jpg -------------------------------------------------------------------------------- /media/screenshots/ExportApplet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/ExportApplet.jpg -------------------------------------------------------------------------------- /media/screenshots/FinderServices.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/FinderServices.jpg -------------------------------------------------------------------------------- /media/screenshots/MojaveSidebar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/MojaveSidebar.jpg -------------------------------------------------------------------------------- /media/screenshots/PhotosSdef.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/PhotosSdef.jpg -------------------------------------------------------------------------------- /media/screenshots/QLScpt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/QLScpt.jpg -------------------------------------------------------------------------------- /media/screenshots/SafariDebugger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/SafariDebugger.jpg -------------------------------------------------------------------------------- /media/screenshots/SafariPrefs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/SafariPrefs.jpg -------------------------------------------------------------------------------- /media/screenshots/ScriptEditor1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/ScriptEditor1.jpg -------------------------------------------------------------------------------- /media/screenshots/ScriptEditor2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/ScriptEditor2.jpg -------------------------------------------------------------------------------- /media/screenshots/Scripts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/Scripts.jpg -------------------------------------------------------------------------------- /media/screenshots/Service.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/Service.jpg -------------------------------------------------------------------------------- /media/screenshots/TouchBar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/TouchBar.jpg -------------------------------------------------------------------------------- /media/screenshots/scpt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/screenshots/scpt.jpg -------------------------------------------------------------------------------- /media/video/REPL.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/media/video/REPL.mp4 -------------------------------------------------------------------------------- /src/args/args.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | run = argv => console.log( 4 | argv.map(x => x.padStart(10)).join('\n') 5 | ) 6 | -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleAllowMixedLocalizations 6 | 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | applet 11 | CFBundleIconFile 12 | applet 13 | CFBundleIdentifier 14 | com.apple.ScriptEditor.id.Calculator 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | Calculator 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | aplt 25 | LSMinimumSystemVersionByArchitecture 26 | 27 | x86_64 28 | 10.6 29 | 30 | LSRequiresCarbon 31 | 32 | WindowState 33 | 34 | bundleDividerCollapsed 35 | 36 | bundlePositionOfDivider 37 | 0.0 38 | dividerCollapsed 39 | 40 | eventLogLevel 41 | 2 42 | name 43 | ScriptWindowState 44 | positionOfDivider 45 | 593 46 | savedFrame 47 | 0 33 720 844 0 0 1440 877 48 | selectedTab 49 | description 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/MacOS/applet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/src/calculator/Calculator.app/Contents/MacOS/applet -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPLaplt -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/Resources/Scripts/main.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/src/calculator/Calculator.app/Contents/Resources/Scripts/main.scpt -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/Resources/applet.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/src/calculator/Calculator.app/Contents/Resources/applet.icns -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/Resources/applet.rsrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/src/calculator/Calculator.app/Contents/Resources/applet.rsrc -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/Resources/description.rtfd/TXT.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf400 2 | {\fonttbl} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\expandedcolortbl;;} 5 | } -------------------------------------------------------------------------------- /src/calculator/Calculator.app/Contents/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | Resources/Scripts/main.scpt 8 | 9 | 8X58jMCdcZohOuteaTlUIWT18T8= 10 | 11 | Resources/applet.icns 12 | 13 | sINd6lbiqHD5dL8c6u79cFvVXhw= 14 | 15 | Resources/applet.rsrc 16 | 17 | 03GMg1LK/YSdtQnC5JmZVcrcSmg= 18 | 19 | Resources/description.rtfd/TXT.rtf 20 | 21 | lcAGO33IpiFXFKH4zArtBM/qDMo= 22 | 23 | 24 | files2 25 | 26 | Resources/Scripts/main.scpt 27 | 28 | hash 29 | 30 | 8X58jMCdcZohOuteaTlUIWT18T8= 31 | 32 | hash2 33 | 34 | fKcZbvQMzPnuKVOFiguOLzVfmItoI469Hg5VGLSjBDo= 35 | 36 | 37 | Resources/applet.icns 38 | 39 | hash 40 | 41 | sINd6lbiqHD5dL8c6u79cFvVXhw= 42 | 43 | hash2 44 | 45 | J7weZ6vlnv9r32tS5HFcyuPXl2StdDnfepLxAixlryk= 46 | 47 | 48 | Resources/applet.rsrc 49 | 50 | hash 51 | 52 | 03GMg1LK/YSdtQnC5JmZVcrcSmg= 53 | 54 | hash2 55 | 56 | HvDodrjydmQenhDjgBXv+i+agDtK3j7Hw1UQHq3xpfY= 57 | 58 | 59 | Resources/description.rtfd/TXT.rtf 60 | 61 | hash 62 | 63 | lcAGO33IpiFXFKH4zArtBM/qDMo= 64 | 65 | hash2 66 | 67 | xSJq5q+RSjvZKxm6uOcpcm7cV6u5Y/qiw98pqGnO8wA= 68 | 69 | 70 | 71 | rules 72 | 73 | ^Resources/ 74 | 75 | ^Resources/.*\.lproj/ 76 | 77 | optional 78 | 79 | weight 80 | 1000 81 | 82 | ^Resources/.*\.lproj/locversion.plist$ 83 | 84 | omit 85 | 86 | weight 87 | 1100 88 | 89 | ^Resources/Base\.lproj/ 90 | 91 | weight 92 | 1010 93 | 94 | ^version.plist$ 95 | 96 | 97 | rules2 98 | 99 | .*\.dSYM($|/) 100 | 101 | weight 102 | 11 103 | 104 | ^(.*/)?\.DS_Store$ 105 | 106 | omit 107 | 108 | weight 109 | 2000 110 | 111 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ 112 | 113 | nested 114 | 115 | weight 116 | 10 117 | 118 | ^.* 119 | 120 | ^Info\.plist$ 121 | 122 | omit 123 | 124 | weight 125 | 20 126 | 127 | ^PkgInfo$ 128 | 129 | omit 130 | 131 | weight 132 | 20 133 | 134 | ^Resources/ 135 | 136 | weight 137 | 20 138 | 139 | ^Resources/.*\.lproj/ 140 | 141 | optional 142 | 143 | weight 144 | 1000 145 | 146 | ^Resources/.*\.lproj/locversion.plist$ 147 | 148 | omit 149 | 150 | weight 151 | 1100 152 | 153 | ^Resources/Base\.lproj/ 154 | 155 | weight 156 | 1010 157 | 158 | ^[^/]+$ 159 | 160 | nested 161 | 162 | weight 163 | 10 164 | 165 | ^embedded\.provisionprofile$ 166 | 167 | weight 168 | 20 169 | 170 | ^version\.plist$ 171 | 172 | weight 173 | 20 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/calculator/Calculator.js: -------------------------------------------------------------------------------- 1 | ObjC.import('Cocoa') 2 | 3 | const resultString = (result) => `Result: ${ result ? result.toString() : ''}` 4 | 5 | ObjC.registerSubclass({ 6 | name: 'AppDelegate', 7 | methods: { 8 | 'calculate:': { 9 | types: ['void', ['id']], 10 | implementation: function (sender) { 11 | var total = Number(textField1.stringValue.js) + Number(textField2.stringValue.js) 12 | resultTextFieldLabel.stringValue = resultString(total) 13 | } 14 | } 15 | } 16 | }) 17 | 18 | var appDelegate = $.AppDelegate.alloc.init 19 | 20 | const styleMask = $.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask 21 | var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer( 22 | $.NSMakeRect(0, 0, 250, 120), 23 | styleMask, 24 | $.NSBackingStoreBuffered, 25 | false 26 | ) 27 | 28 | var calculateButton = $.NSButton.alloc.initWithFrame($.NSMakeRect(25, 20, 200, 25)) 29 | calculateButton.title = 'Calculate 🧙‍♂️' 30 | calculateButton.bezelStyle = $.NSRoundedBezelStyle 31 | calculateButton.buttonType = $.NSMomentaryLightButton 32 | calculateButton.target = appDelegate 33 | calculateButton.action = 'calculate:' 34 | calculateButton.keyEquivalent = '\r' 35 | 36 | window.contentView.addSubview(calculateButton) 37 | 38 | var resultTextFieldLabel = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, 45, 200, 24)) 39 | resultTextFieldLabel.stringValue = resultString() 40 | resultTextFieldLabel.drawsBackground = false 41 | resultTextFieldLabel.editable = false 42 | resultTextFieldLabel.bezeled = false 43 | resultTextFieldLabel.selectable = true 44 | window.contentView.addSubview(resultTextFieldLabel) 45 | 46 | var textField1 = $.NSTextField.alloc.initWithFrame($.NSMakeRect(25, 80, 90, 24)) 47 | textField1.stringValue = '' 48 | window.contentView.addSubview(textField1) 49 | 50 | var plusTextFieldLabel = $.NSTextField.alloc.initWithFrame($.NSMakeRect(120, 80, 24, 24)) 51 | plusTextFieldLabel.stringValue = '+' 52 | plusTextFieldLabel.drawsBackground = false 53 | plusTextFieldLabel.editable = false 54 | plusTextFieldLabel.bezeled = false 55 | plusTextFieldLabel.selectable = true 56 | window.contentView.addSubview(plusTextFieldLabel) 57 | 58 | var textField2 = $.NSTextField.alloc.initWithFrame($.NSMakeRect(135, 80, 90, 24)) 59 | textField2.stringValue = '' 60 | window.contentView.addSubview(textField2) 61 | 62 | window.center 63 | window.title = 'Scientific Calculator 🙈' 64 | window.makeKeyAndOrderFront(window) 65 | -------------------------------------------------------------------------------- /src/calculator/Calculator.scpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/src/calculator/Calculator.scpt -------------------------------------------------------------------------------- /src/crop image/Crop Image.workflow/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSServices 6 | 7 | 8 | NSMenuItem 9 | 10 | default 11 | Crop Image 12 | 13 | NSMessage 14 | runWorkflowAsService 15 | NSRequiredContext 16 | 17 | NSApplicationIdentifier 18 | com.apple.finder 19 | 20 | NSSendFileTypes 21 | 22 | public.item 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/crop image/Crop Image.workflow/Contents/QuickLook/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josh-/automating-macOS-with-JXA-presentation/f0f019ad2a827625fd875c761c99fed57c93d337/src/crop image/Crop Image.workflow/Contents/QuickLook/Preview.png -------------------------------------------------------------------------------- /src/crop image/Crop Image.workflow/Contents/document.wflow: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AMApplicationBuild 6 | 444.7 7 | AMApplicationVersion 8 | 2.8 9 | AMDocumentVersion 10 | 2 11 | actions 12 | 13 | 14 | action 15 | 16 | AMAccepts 17 | 18 | Container 19 | List 20 | Optional 21 | 22 | Types 23 | 24 | com.apple.applescript.object 25 | 26 | 27 | AMActionVersion 28 | 1.0 29 | AMApplication 30 | 31 | Automator 32 | 33 | AMParameterProperties 34 | 35 | source 36 | 37 | 38 | AMProvides 39 | 40 | Container 41 | List 42 | Types 43 | 44 | com.apple.applescript.object 45 | 46 | 47 | ActionBundlePath 48 | /System/Library/Automator/Run JavaScript.action 49 | ActionName 50 | Run JavaScript 51 | ActionParameters 52 | 53 | source 54 | const SystemEvents = Application('System Events') const ImageEvents = Application('Image Events') 55 | 56 | run = (input, parameters) => { 57 | for (var filePath of input) { 58 | const file = ImageEvents.open(filePath.toString()) 59 | file.crop({toDimensions: [400, 400]}) file.save({in: filePath.toString()}) 60 | } return input; } 61 | 62 | BundleIdentifier 63 | com.apple.Automator.RunJavaScript 64 | CFBundleVersion 65 | 1.0 66 | CanShowSelectedItemsWhenRun 67 | 68 | CanShowWhenRun 69 | 70 | Category 71 | 72 | AMCategoryUtilities 73 | 74 | Class Name 75 | RunJavaScriptAction 76 | InputUUID 77 | BDA8FF6C-A73A-4410-AA15-60D8EAE3E732 78 | Keywords 79 | 80 | Run 81 | JavaScript 82 | 83 | OutputUUID 84 | 614A4317-86FE-4219-B38B-BFB100091E4D 85 | UUID 86 | 1F3ABE58-4D10-4A36-90D5-B67486012FB7 87 | UnlocalizedApplications 88 | 89 | Automator 90 | 91 | arguments 92 | 93 | 0 94 | 95 | default value 96 | function run(input, parameters) { 97 | 98 | // Your script goes here 99 | 100 | return input; 101 | } 102 | name 103 | source 104 | required 105 | 0 106 | type 107 | 0 108 | uuid 109 | 0 110 | 111 | 112 | isViewVisible 113 | 114 | location 115 | 297.500000:599.000000 116 | nibPath 117 | /System/Library/Automator/Run JavaScript.action/Contents/Resources/Base.lproj/main.nib 118 | 119 | isViewVisible 120 | 121 | 122 | 123 | connectors 124 | 125 | workflowMetaData 126 | 127 | serviceApplicationBundleID 128 | com.apple.finder 129 | serviceApplicationPath 130 | /System/Library/CoreServices/Finder.app 131 | serviceInputTypeIdentifier 132 | com.apple.Automator.fileSystemObject 133 | serviceOutputTypeIdentifier 134 | com.apple.Automator.nothing 135 | serviceProcessesInput 136 | 0 137 | workflowTypeIdentifier 138 | com.apple.Automator.servicesMenu 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/define/define.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env osascript -l JavaScript 2 | 3 | ObjC.import('DictionaryServices') 4 | 5 | run = argv => { 6 | const word = argv[0] 7 | const range = { 'location': 0, 'length': word.length } 8 | let definition = $.DCSCopyTextDefinition(null, word, range) 9 | console.log(definition.js.split(" | ").join("\n")) 10 | } 11 | -------------------------------------------------------------------------------- /src/safari/Create Tab.js: -------------------------------------------------------------------------------- 1 | const Safari = Application('Safari') 2 | const firstWindow = Safari.windows[0] 3 | const tab = Safari.Tab({url: "https://joshparnham.com"}) 4 | firstWindow.tabs.push(tab) 5 | firstWindow.currentTab = tab 6 | --------------------------------------------------------------------------------