Coder Social home page Coder Social logo

hhas / swiftautomation Goto Github PK

View Code? Open in Web Editor NEW
59.0 5.0 5.0 2.59 MB

High-level Apple event framework for Swift. Currently implements client-side; server-side TBD.

Home Page: https://hhas.bitbucket.io/

Swift 95.15% Objective-C 0.03% HTML 4.50% CSS 0.20% Python 0.11%
swift applescript macos automation

swiftautomation's Introduction

# SwiftAutomation README

## About

SwiftAutomation is an Apple event bridge that allows Apple's Swift language 
to control "AppleScriptable" macOS applications directly. For example:

  // tell application "iTunes" to play
  try ITunes().play()

  // tell application "Finder" to set fileNames to name of every file of home
  let fileNames = try Finder().home.files.name.get() as [String]


  // tell application "TextEdit" to make new document ¬
  //                                     with properties {text:"Hello World!"}
  try TextEdit().make(new: TED.document, 
                      withProperties: [TED.text: "Hello World!"])


The SwiftAutomation framework defines the basic functionality for constructing
object specifiers, converting data between Swift and AE types, and sending 
Apple events to running applications. Generated Swift files supply the glue
code for controlling individual applications using human-readable terminology.


## Get it

To clone the Xcode project to your own machine:

  git clone https://github.com/hhas/SwiftAutomation.git

A basic Swift "script editor" (currently under development) is also available:

  https://github.com/hhas/swiftautoedit


## Install it

To embed SwiftAutomation for use in Swift-based GUI apps see Xcode's Workspace
documentation.

To use SwiftAutomation in Swift "scripting", see the Installing SwiftAutomation
chapter of the SwiftAutomation documentation. Until Swift provides a stable ABI
some manual set-up is required.


## Try it

To run simple examples (see test/main.swift), build and run the `test` target.

Additional glues can be generated by building the `aeglue` target and running
the resulting `aeglue` command line tool in Terminal. For example, to generate
a Swift glue and accompanying documentation for macOS's Photos application:

  /path/to/aeglue -o ~/Desktop Photos

Note that `aeglue` normally retrieves application terminology using `ascrgdte`
('get dynamic terminology') Apple events. Some applications (e.g. Finder) have
faulty `ascrgdte` handlers that fail to return correct terminology, in which
case use the `-s` or 'Use SDEF terminology' options instead. (Be aware that
SDEF-based terminology may also contain bugs and omissions, in which case use
raw four-char codes or correct generated glue code by hand.)

SwiftAutomation requires macOS 10.11 and Swift 3.0 and Xcode 8.0 or later.


## Status

The code is complete except for testing and bug fixes. The documentation lacks
a usable tutorial chapter. Given current uncertainty regarding the future of
Apple event-based automation the project is on hiatus until WWDC17, after which
a final decision on its future can be made.


## Known issues

When using SwiftAutomation within an interactive playground, be aware that Xcode
automatically re-runs ALL code within a playground whenever a line of code is
modified, causing ALL application commands to be re-sent. This is not a problem
when using non-mutating application commands such as `get` and `count`; however,
take care when using commands that modify the application's state - `make`,
`move`, `delete`, etc. - within a playground as sending these more than once may
have unintended/undesirable results. This is a playground issue that affects ALL
non-idempotent and/or unsafe function calls, not just application commands.


## Etc.

SwiftAutomation is released into the public domain.

No warranty given, E&OE, use at own risk, etc.

swiftautomation's People

Contributors

hhas avatar mattneub avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

swiftautomation's Issues

unpackAsQDPoint crash

unpackAsQDPoint crashes at:
let point: Rect = descriptor.data.withUnsafeBytes { buffer in
buffer.bindMemory(to: Rect.self)[0]

The fix is to replace "Rect" with "Point" at two places in that line.

This can be tested by targeting Finder, which uses a point for its "desktop position" folder property:

let finder = Finder()
print("TEST: get desktop position of folder "TEMP"")
print(try finder.folders["TEMP"].desktopPosition.get()) // [2301, 554]

support for typeRGBColor

I've implemented a local change and tested against my own app, which is a scriptable text editor. Here is my one added method to unpack RGB data and a change to unpackAsAny() that calls it, both in the file UnpackFuncs.swift.

public func unpackAsRGBColor(_ descriptor: Descriptor) throws -> [Int] {

	guard descriptor.data.count == MemoryLayout<UInt16>.size * 3 
		else { throw AppleEventError.corruptData }

	let rgbColor:RGBColor = descriptor.data.withUnsafeBytes { buffer in
		buffer.bindMemory(to: RGBColor.self)[0]
	}
		
	let red   = Int(UInt16(bigEndian: rgbColor.red))
	let green = Int(UInt16(bigEndian: rgbColor.green))
	let blue  = Int(UInt16(bigEndian: rgbColor.blue))

	return [red, green, blue]
}	

and in existing method UnpackAsAny

public func unpackAsAny(_ descriptor: Descriptor) throws -> Any {
	...
	case typeRGBColor:
		result = try unpackAsRGBColor(descriptor)
	case typeQDPoint, typeQDRectangle:
		return try unpackAsArray(descriptor, using: unpackAsInt)
	...
}

and in test project main.swift, add

 print(try doc.text.color.set(to: [25186, 48058, 18246])) // 6-color Apple logo's green

 print("TEST: get the color of text of doc")
 print("// try doc.text.color.get()")
 // print(try doc.text.color.get())  // WAS: Error -1700: Can't make some data into the expected type.: file
 print(try doc.text.color.get())     // NOW: [25186, 48058, 18246]

add support for typeQDRectangle

Similar to the proposed solution to issue #1:

public func unpackAsQDRectangle(_ descriptor: Descriptor) throws -> [Int] {
	
	struct QDRectangle { // derived from MacTypes.h
		var top: Int16
		var left: Int16
		var bottom: Int16
		var right: Int16
	}

	guard descriptor.data.count == MemoryLayout<Int16>.size * 4 else { throw AppleEventError.corruptData }

	let theRect: QDRectangle = descriptor.data.withUnsafeBytes { buffer in
		buffer.bindMemory(to: QDRectangle.self)[0]
	}

	let top 		= Int(Int16(bigEndian: theRect.top))
	let left 	= Int(Int16(bigEndian: theRect.left))
	let bottom 	= Int(Int16(bigEndian: theRect.bottom))
	let right 	= Int(Int16(bigEndian: theRect.right))

	// AppleScript returns {left, top, right, bottom}
	return [left, top, right, bottom]
}

typeChar edge case

Finder has a "comment" property, which can be seen in Get Info for a folder, for example, or in SwiftAutomation:
print("comment: \"\(try finder.folders["TEMP"].comment.get())\"")

If there is a comment, the descriptorType is utxt and is printed correctly.
But if the comment is empty, the descriptorType is typeChar and SA prints
comment: "<ScalarDescriptor "TEXT" ...>"

One way to fix:
In unpackAsAny change
case typeUTF16ExternalRepresentation, typeUnicodeText:
to
case typeUTF16ExternalRepresentation, typeUnicodeText, typeChar:

Then, in unpackAsString add this case:

case typeChar:
	guard let result = String(data: descriptor.data, encoding: .macOSRoman) else { throw AppleEventError.corruptData }
	return result

NOTE: I'm running this on Mojave, so regarding the question posed in the comment on the above line,
"what, if any, macOS apps still use them", one answer is Finder (at least on Mojave 10.14.6).
NOTE: I have since confirmed that this is still the case on Big Sur 11.4

typeLongDateTime suggested fix

A couple of tweaks to handle AppleScript typeLongDateTime:

  1. unpackAsDate calls unpackAsInteger, which does not handle typeLongDateTime. Add to unpackAsInteger:
    case typeLongDateTime:
        result = T(exactly: Int64(bigEndian: try decodeFixedWidthValue(descriptor.data)))

or alternatively modify existing case:

    case typeSInt64, typeLongDateTime:
        result = T(exactly: Int64(bigEndian: try decodeFixedWidthValue(descriptor.data)))
  1. now that unpackAsDate has computed the TimeInterval delta, fix a bug in the return statement, changing + to -:

    return Date(timeIntervalSinceReferenceDate: delta - epochDelta)

  2. test with a folder on the desktop:

let finder = Finder()
print(try finder.folders["TEMP"].creationDate.get())
  1. perhaps a better name for "delta" might be "secondsSince1904"?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.