There’s a problem in the Xojo framework when storing an AppleEventObjectSpecifier into a variable; the variable may turn to an AppleEventRecord. The following example illustrates the issue, provided a NSAppleEventsUsageDescription key is added to the info.plist of the bundle:
//Get the selection of the "Music" app:
Var ae As new AppleEvent("core","getd","com.apple.Music")
ae.ObjectSpecifierParam("----")=GetPropertyObjectDescriptor(nil,"sele")
call ae.Send
Var d As AppleEventDescList=ae.ReplyDescList
if d<>nil then
Var o As AppleEventObjectSpecifier=d.ObjectSpecifierItem(1)
Break
end if
Notice “o” is defined as an AppleEventObjectSpecifier but, at the Break statement, the debugged shows “o” as containing an AppleEventRecord. Also, passing this variable to a function that expects an AppleEventObjectRecord parameter will raise an IllegalCastException. Basically, the variable is not useful.
But, under the hood, it’s still an AppleEventObjectSpecifier, both because the variable has been created as one, and because the d.ObjectSpecifierItem call returns an AppleEventObjectSpecifier. How can I force “o” to become again an AppleEventObjectSpecifier?
My app allows workflows to be done (like removing tracks whose original file doesn’t exist anymore, revealing selected tracks in the file browser, clearing artworks, opening selected tracks in another app, etc.).
I’m currently re-writing the app, which surprisingly worked since now; I can’t predict when the bug happens in the framework, as I can’t find a pattern. The example in my original post shows that it’s weak, as just an assignment shows the issue (but not always ).
My brain isn’t up to speed today. That doesn’t tell me anything. You are working with iTunes/Music, correct? Why do you need to send raw AppleEvents instead of using AppleScript?
The Xojo app does the core logic. AppleEvents are like the pieces of a puzzle. In a module, I have one method per function (get name of track, get file of track, get all playlists, get selection, etc.). There are getter and setter functions. Then, at need, I build the logic in my Xojo app (like “remove all tracks whose file is not found”, “remove duplicate tracks”, “convert checked items to m4a” or more extended functions).
One reason I can’t do all in AppleScript is because it lacks UI. For showing progress, and for selecting the command, it’s far intuitive with a window. With only AppleScript, I’d have dozen of scripts laying in a folder, and each time I need a new function, I’d have to rewrite the commands (or copy/paste).
Also, the Xojo bug of converting an AppleEventObjectSpecifier to an AppleEventRecord should rather be fixed rather than me moving to an AppleScript solution, IMO.
Scratching head. I can’t follow you at all. The AppleEvents also don’t have a UI. I never had AppleScripts laying in a folder. I do all my AppleScripts dynamically in code (64 and counting).
The UI is the whole puzzle, while various AppleEvents are the pieces of the puzzle.
So, for example, the “Delete tracks of selection whose file is not found” command would use these pieces:
• Get selection of Music/iTunes
• For each track…
— Check if the track has an existing file
— If not, delete the track
Of course, other commands involve other AppleEvents.
The UI, here, is to select among the various commands, using a popup menu (I sometimes add commands for my needs) and to show a progress bar and a cancel button.
With pure AppleScript, I’d not be able to show a progress bar nor a cancel button. Also, I feel more comfortable with Xojo (e.g., in AppleScript, the NthField function for a string is rather unintuitive).
That’d be another approach, I presume. Could you provide an example, please?
If Xojo could fix this issue, I’d not need to rewrite the core logic to AppleScript, though.
I’m still totally confused why you think that the UI is related to AppleScript or AppleEvents. And it was never possible to cancel an AppleScript.
My app get’s mailboxes from Mail or Outlook, munges them and displays them in a listbox. Below are the starting functions to get the mailboxes.
Private Function getMailboxesAS() As String()
'get normal mailboxes
Globals.theErrorLog.logitem currentMethodName, "open"
dim theScript(-1) as string
theScript.Add "tell application id 'com.apple.mail'"
theScript.Add "return mailboxes"
theScript.Add "end tell"
'do the AS and get the result
dim doAppleScript as new RunAS(theScript, array(-1728))
dim theResultArray(-1) as String = doAppleScript.getResultArray
if theResultArray = Nil or doAppleScript.getError <> "" then
isError = True
Return Nil
end if
'fill the result
dim currentMailbox as integer
for currentMailbox = 0 to theResultArray.LastIndex
dim currentM as String = theResultArray(currentMailbox)
currentM = currentM.ReplaceAll("mailbox """, "")
currentM = currentM.ReplaceAll(" of application ""Mail""", "")
currentM = currentM.ReplaceAll("/", globals.MailboxDivider)
'add Mailboxes as top level
currentM = "Mailboxes" + globals.MailboxDivider + currentM
theResultArray(currentMailbox) = currentM
next
Return theResultArray
End Function
Private Function getMailboxesForAccount(theAccount as String) As String()
'get the mailboxes for an account
if MailboxesAppleScript = nil then
MailboxesScript.Add "property theAccount: ''"
MailboxesScript.Add "tell application id 'com.apple.mail'"
MailboxesScript.Add "set theMailboxes to mailboxes of the account theAccount"
MailboxesScript.Add "end tell"
MailboxesScript.Add "return theMailboxes"
MailboxesAppleScript = new RunAS(MailboxesScript, array(-1728), false, true)
end if
dim theAccountDictionary as Dictionary = RunAS.MakeDictionary("theAccount", theAccount)
MailboxesAppleScript.ExecuteScriptWithParameters(theAccountDictionary)
dim theMailboxes(-1) as String = MailboxesAppleScript.getResultArray
if MailboxesAppleScript.getError <> "" then isError = True
theMailboxes.Sort
'add account and use mailbox divider
for currentBox as integer = 0 to theMailboxes.LastIndex
dim theMailbox as String = theMailboxes(currentBox).ReplaceAll("/", globals.MailboxDivider)
if theMailbox <> theAccount then
theMailboxes(currentBox) = theAccount + globals.MailboxDivider + theMailbox
end if
next
Return theMailboxes
End Function
runAS is my class to run AppleScripts with NSAppleScriptMBS. The big benefit is that it’s possible to compile the script once and then run it multiple times with different parameters.
Not that they are related, but I’m more accustomed in packing AppleEvents than pieces of scripts.
Thanks for your example. It appears you’re dealing with strings returned by the scripts, and I think it’s the reason of the confusion.
iTunes/Music tracks aren’t referred by strings (you can have a given track twice in the same playlist; also, the displayed name can change, e.g. if you rename the original file and Music reflects the change). Tracks are objects that can be referred by an ID and a container (the playlist in which the track is).
Now, back to my app, if I try to get a track with an AppleScript script such as “ tellapplication “Music” togettrack 1 of current playlist”, the result I get is this: file trackid 291367 ofuser playlistid 291353 ofsourceid 64 ofapplication “Music”. How may I pass this to a given command, later in the flow?
I can’t get a simple string uniquely identifying a given track that I could store. Dealing with the ID also looks tricky (the track with a given ID may exist in several playlists).
This happens on my machine as well. This is clearly a bug - AppleEventRecord does not inherit from AppleEventObjectSpecifiernd and O should not be able to hold a reference to it.
I tried to get the debug dump of the internal Apple Event structure to see what exactly is being returned, to possibly craft a workaround, but it seems the olde tyme AEDebug environment variables don’t dump replies anymore. It’s possible that I could figure this out – I may look into it this weekend, because this is important stuff if you’re working with Apple Events.
In any case, I think you’re going to have file an issue against this and get them to fix it… and then wait. AppleEvents probably aren’t high on the priority list.
I’ve eventually found that immediately storing the result into a dummy class (one whose the only property is an AppleEventObjectSpecifier) seems to keep the type (but it depend on “when” you store it; that’s puzzling).
So it looks the returned type is correct in the beginning.
Agreed. Nice if you can take a look.
Granted. My report was made more than 10 years ago
But I can’t find it anymore in Issue; it looks like it was not imported from Feedback
I’ll make another report…