How to convert an AppleEventRecord to an AppleEventObjectSpecifier when the framework mangles them?


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","")
call ae.Send

Var d As AppleEventDescList=ae.ReplyDescList
if d<>nil then
  Var o As AppleEventObjectSpecifier=d.ObjectSpecifierItem(1)
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?

What are you trying to do?

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 :rage:).

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.

Thanks for your replies.

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 ''"
  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
  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 ''"
    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)
  dim theMailboxes(-1) as String = MailboxesAppleScript.getResultArray
  if MailboxesAppleScript.getError <> "" then isError = True
  '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
  Return theMailboxes
End Function

This is the result:

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 “ tell application “Music” to get track 1 of current playlist”, the result I get is this: file track id 291367 of user playlist id 291353 of source id 64 of application “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).

Perhaps I should try some more tests…

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.

1 Like

Thanks for your search.

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 :wink:
But I can’t find it anymore in Issue; it looks like it was not imported from Feedback :man_shrugging:
I’ll make another report…