📜 ⬆️ ⬇️

Export to mail.app from Qt application

There was a need to export certain files to email (users are very asking). The problem is how to do it in Mac OS X. Well, suppose we do not assume any other mailers other than Mail.app. On developer.apple.com I found a description of how this is done without Qt. The first attempt to realize this has generated a lot of questions, answers to which in developer.apple.com is not found. In general, I suffered enough, and then I post the ready-made recipe for implementing this feature using AppleScript.

1. Connect the carbon header file:
# include <Carbon/Carbon.h> 


2. We declare two static variables (constants) containing two full AppleScript text — one to send one attachment and the other to send two attachments:
 char sendMailScript_singAtt[] = "on send_email_message(subjectLine, messageText, fileAttachment)\n" " tell application \"Mail\"\n" " (* CREATE THE MESSAGE *)\n" " activate\n" " set newMessage to make new outgoing message at end of outgoing messages with properties {subject:subjectLine, content:messageText & return, visible:true}\n" " \n" " (* SPECIFY THE ATTACHMENT *)\n" " tell newMessage\n" " make new attachment with properties {file name:fileAttachment} at after last paragraph\n" " end tell\n" " (*set frontmost to true*)\n" " end tell\n" "end send_email_message"; char sendMailScript_doubAtt[] = "on send_email_message(subjectLine, messageText, fileAttachmentA, fileAttachmentB)\n" " tell application \"Mail\"\n" " (* CREATE THE MESSAGE *)\n" " activate\n" " set newMessage to make new outgoing message at end of outgoing messages with properties {subject:subjectLine, content:messageText & return, visible:true}\n" " \n" " (* SPECIFY THE ATTACHMENT *)\n" " tell newMessage\n" " make new attachment with properties {file name:fileAttachmentA} at after last paragraph\n" " make new attachment with properties {file name:fileAttachmentB} at after last paragraph\n" " end tell\n" " (*set frontmost to true*)\n" " end tell\n" "end send_email_message"; 


As you can see, scripts are two functions that take as parameters the text for the subject, the text of the message body, and one or two attachments. The method of sending is almost completely different from the method described in the above article, apparently the article is outdated, and apple did not bother to update the information. Actually the script itself is quite trivial, the only thing I want to pay attention to is the activate call. Without this, Mail.app windows open in the background.
')
3. Pay attention to the latest parameters of scripts - these are not just file paths, they are so-called aliases. Therefore, the following function is designed to get these same aliases from the path:
 GetAliasHandleForFile(QString fname, AliasHandle * ahandle) { FSRef fref; if(FSPathMakeRef((const UInt8 *)fname.toLocal8Bit().data(), &fref, NULL) == noErr) if(FSNewAlias(0, &fref, ahandle) == noErr && (*ahandle)) return true; return false; } 

If the alias is successfully received from the file path passed in an argument of type QString, the function returns true, otherwise false

4. To call AppleScript, we need to create an event object from the script text, which we will call by passing arguments to it to export the file to email. Here are the functions for creating such events for both scripts:

 OSStatus CreateEmailMessageEvent1(AppleEvent *theEvent, char* subjectLine, char* messageText, AliasHandle fileAttachment) { OSStatus err; ProcessSerialNumber PSN = {0, kCurrentProcess}; /* create the container list */ err = AEBuildAppleEvent( 'ascr', kASSubroutineEvent, typeProcessSerialNumber, (Ptr) &PSN, sizeof(PSN), kAutoGenerateReturnID, kAnyTransactionID, theEvent, NULL, "'----':[TEXT(@),TEXT(@),alis(@@)]," "'snam':TEXT(@)", subjectLine, messageText, fileAttachment, "send_email_message"); return err; } OSStatus CreateEmailMessageEvent2(AppleEvent *theEvent, char* subjectLine, char* messageText, AliasHandle fileAttachmentA, AliasHandle fileAttachmentB) { OSStatus err; ProcessSerialNumber PSN = {0, kCurrentProcess}; /* create the container list */ err = AEBuildAppleEvent( 'ascr', kASSubroutineEvent, typeProcessSerialNumber, (Ptr) &PSN, sizeof(PSN), kAutoGenerateReturnID, kAnyTransactionID, theEvent, NULL, "'----':[TEXT(@),TEXT(@),alis(@@),alis(@@)]," "'snam':TEXT(@)", subjectLine, messageText, fileAttachmentA, fileAttachmentB, "send_email_message"); return err; } 

For details on the called functions, see the Apple documentation (I didn’t go into the details much, nadergal from various examples found on the Internet)

5. Now the actual call of the event created using the above functions:

 OSStatus ExecuteAppleScriptEvent(const void* text, long textLength, AppleEvent *theEvent, AEDesc *resultData) { ComponentInstance theComponent; AEDesc scriptTextDesc; OSStatus err; OSAID contextID, resultID; /* set up locals to a known state */ theComponent = NULL; AECreateDesc(typeNull, NULL, 0, &scriptTextDesc); contextID = kOSANullScript; resultID = kOSANullScript; /* open the scripting component */ theComponent = OpenDefaultComponent(kOSAComponentType, typeAppleScript); if(theComponent != NULL) { /* put the script text into a Apple event descriptor record */ err = AECreateDesc(typeChar, text, textLength, &scriptTextDesc); if(err == noErr) { /* compile the script into a new context. The flag 'kOSAModeCompileIntoContext' is used when compiling a script containing a handler into a context. */ err = OSACompile(theComponent, &scriptTextDesc, kOSAModeCompileIntoContext, &contextID); if(err == noErr) { /* run the script */ err = OSAExecuteEvent( theComponent, theEvent, contextID, kOSAModeNull, &resultID); /* collect the results - if any */ if(resultData != NULL) { AECreateDesc(typeNull, NULL, 0, resultData); if(err == errOSAScriptError) OSAScriptError(theComponent, kOSAErrorMessage, typeChar, resultData); else if(err == noErr && resultID != kOSANullScript) OSADisplay(theComponent, resultID, typeChar, kOSAModeDisplayForHumans, resultData); } } } } else err = paramErr; AEDisposeDesc(&scriptTextDesc); if(contextID != kOSANullScript) OSADispose(theComponent, contextID); if(resultID != kOSANullScript) OSADispose(theComponent, resultID); if(theComponent != NULL) CloseComponent(theComponent); return err; } 


As you can see, the function can return the results of a call through an object of type AEDesc. The script text and its length are transferred to the function. It is in this function that the compilation (OSACompile) and the launch (OSAExecuteEvent) of the script occur, and the previous event creation function only creates a call description and container for the parameters that are used when the script is executed.

6. Well, now we collect everything together:

 void SendSingleFileToEmail(QString subj, QString body, QString attPath) { AliasHandle ahandle; AppleEvent theEvent; if(!GetAliasHandleForFile(attPath, &ahandle)) return; if(CreateEmailMessageEvent1(&theEvent, subj.toLocal8Bit().data(), body.toLocal8Bit().data(), ahandle) == noErr) ExecuteAppleScriptEvent(sendMailScript_singAtt, strlen(sendMailScript_singAtt), &theEvent, NULL); } void SendDoubleFileToEmail(QString subj, QString body, QString attPathA, QString attPathB) { AliasHandle ahandleA, ahandleB; AppleEvent theEvent; if(!GetAliasHandleForFile(attPathA, &ahandleA) || !GetAliasHandleForFile(attPathB, &ahandleB)) return; if(CreateEmailMessageEvent2(&theEvent, subj.toLocal8Bit().data(), body.toLocal8Bit().data(), ahandleA, ahandleB) == noErr) ExecuteAppleScriptEvent(sendMailScript_doubAtt, strlen(sendMailScript_doubAtt), &theEvent, NULL); } 


Here we have two functions respectively for sending one attachment and for sending two attachments. Finally, I want to note that this code does not send files, but creates a new message and opens it for editing, entering the recipient's address or canceling it.
Well, another important point. You cannot use this code if you are working in privileged mode (ie, from root), as you cannot run any AppleScript itself.
That's all for today.

Source: https://habr.com/ru/post/104633/


All Articles