Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ScriptingBridge Support #3

Open
seandenigris opened this issue May 27, 2020 · 0 comments
Open

ScriptingBridge Support #3

seandenigris opened this issue May 27, 2020 · 0 comments

Comments

@seandenigris
Copy link

seandenigris commented May 27, 2020

Here is a sample acceptance test usage in Python (see reference):

from Foundation import *
from ScriptingBridge import *
iTunes = SBApplication.applicationWithBundleIdentifier_("com.apple.iTunes")
print iTunes.currentTrack().name()

The first three lines are no problem. Foundation is already loaded and then in Pharo, we can do:

DynamicLoader loadLibrary: '/System/Library/Frameworks/ScriptingBridge.framework/ScriptingBridge'.
iTunesID := 'com.apple.iTunes' asNSString autorelease.
iTunes := (#SBApplication inObjCFramework: 'ScriptingBridge')
		applicationWithBundleIdentifier: iTunesID.

The problem is the last line. What we'd like to write is:

iTunes currentTrack name.

However, the bridge uses class_getInstanceMethod to determine if an object understands a message/selector, and it returns NULL for scripting messages like currentTrack and playpause.

I've asked on SO for more information about how SB works its magic. Here are some parts of the Objective-C API that do see these scripting methods:

  • respondsToSelector: -> TRUE
  • methodSignatureForSelector: - does return a signature
  • and performSelector: actually sends the message

Strangely, methodForSelector:@"playpause" successfully returns an IMP in Obj-C, but crashes if sent from the other side of the bridge.

Possible Solutions

Perform Selector

iTunes performSelector: #playpause asObjCSelector.

This works, but requires inheritance from NSObject and the docs suggest this approach may lead to memory leaks and a SO threads here and here say that NSInvokation might be better

Bypass SB Dynamically-Generated Methods

This lower level alternative works, but completely misses much of the convenience of SB:

currentTrack := iTunes propertyWithCode: 'pTrk' asObjCFourCharCode.
"Current track can also be retrieved this way, but there doesn't seem to be an advantage...
trackClass := iTunes classForScriptingClass: 'track' asNSString autorelease.
track := iTunes propertyWithClass: trackClass code: 'pTrk' asObjCFourCharCode."
trackName := currentTrack propertyWithCode: 'pnam' asObjCFourCharCode.
trackName get cString

For a command (as opposed to a property as above), a similar approach from SO would be:

iTunes sendEvent:'hook' asObjCFourCharCode id:'PlPs' asObjCFourCharCode parameters:0.
iTunes currentTrack

Directions for further research

Notes

To create application specific scripting definition: sdef /path/to/application.app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant