/* Jabber Protocol Plugin for Miranda IM Copyright ( C ) 2002-04 Santithorn Bunchua Copyright ( C ) 2005-11 George Hazan Copyright ( C ) 2007 Maxim Mluhov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or ( at your option ) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Revision : $Revision: 13898 $ Last change on : $Date: 2006-07-13 16:11:29 +0400 Last change by : $Author: borkra $ */ #include "jabber.h" #include "jabber_list.h" #include "jabber_caps.h" #include #include "sdk/m_folders.h" /////////////////////////////////////////////////////////////////////////////// // JabberAddContactToRoster() - adds a contact to the roster void CJabberProto::AddContactToRoster( const TCHAR* jid, const TCHAR* nick, const TCHAR* grpName ) { XmlNodeIq iq( _T("set"), SerialNext()); HXML query = iq << XQUERY( _T(JABBER_FEAT_IQ_ROSTER)) << XCHILD( _T("item")) << XATTR( _T("jid"), jid ) << XATTR( _T("name"), nick ); if ( grpName ) query << XCHILD( _T("group"), grpName ); m_ThreadInfo->send( iq ); } /////////////////////////////////////////////////////////////////////////////// // JabberChatDllError() - missing CHAT.DLL void JabberChatDllError() { MessageBox( NULL, TranslateT( "CHAT plugin is required for conferences. Install it before chatting" ), TranslateT( "Jabber Error" ), MB_OK|MB_SETFOREGROUND ); } /////////////////////////////////////////////////////////////////////////////// // JabberCompareJids int JabberCompareJids( const TCHAR* jid1, const TCHAR* jid2 ) { if ( !lstrcmpi( jid1, jid2 )) return 0; // match only node@domain part TCHAR szTempJid1[ JABBER_MAX_JID_LEN ], szTempJid2[ JABBER_MAX_JID_LEN ]; return lstrcmpi( JabberStripJid( jid1, szTempJid1, SIZEOF(szTempJid1) ), JabberStripJid( jid2, szTempJid2, SIZEOF(szTempJid2)) ); } /////////////////////////////////////////////////////////////////////////////// // JabberContactListCreateGroup() static void JabberContactListCreateClistGroup( TCHAR* groupName ) { char str[33]; int i; DBVARIANT dbv; for ( i=0;;i++ ) { _itoa( i, str, 10 ); if ( DBGetContactSettingTString( NULL, "CListGroups", str, &dbv )) break; TCHAR* name = dbv.ptszVal; if ( name[0]!='\0' && !_tcscmp( name+1, groupName )) { // Already exists, no need to create JFreeVariant( &dbv ); return; } JFreeVariant( &dbv ); } // Create new group with id = i ( str is the text representation of i ) TCHAR newName[128]; newName[0] = 1 | GROUPF_EXPANDED; _tcsncpy( newName+1, groupName, SIZEOF( newName )-1 ); newName[ SIZEOF( newName )-1] = '\0'; DBWriteContactSettingTString( NULL, "CListGroups", str, newName ); JCallService( MS_CLUI_GROUPADDED, i+1, 0 ); } void JabberContactListCreateGroup( TCHAR* groupName ) { TCHAR name[128], *p; if ( groupName==NULL || groupName[0]=='\0' || groupName[0]=='\\' ) return; _tcsncpy( name, groupName, SIZEOF( name )); name[ SIZEOF( name )-1] = '\0'; for ( p=name; *p!='\0'; p++ ) { if ( *p == '\\' ) { *p = '\0'; JabberContactListCreateClistGroup( name ); *p = '\\'; } } JabberContactListCreateClistGroup( name ); } /////////////////////////////////////////////////////////////////////////////// // JabberDBAddAuthRequest() void CJabberProto::DBAddAuthRequest( const TCHAR* jid, const TCHAR* nick ) { HANDLE hContact = DBCreateContact( jid, NULL, TRUE, TRUE ); JDeleteSetting( hContact, "Hidden" ); //JSetStringT( hContact, "Nick", nick ); char* szJid = mir_utf8encodeT( jid ); char* szNick = mir_utf8encodeT( nick ); //blob is: uin( DWORD ), hContact( HANDLE ), nick( ASCIIZ ), first( ASCIIZ ), last( ASCIIZ ), email( ASCIIZ ), reason( ASCIIZ ) //blob is: 0( DWORD ), hContact( HANDLE ), nick( ASCIIZ ), ""( ASCIIZ ), ""( ASCIIZ ), email( ASCIIZ ), ""( ASCIIZ ) DBEVENTINFO dbei = {0}; dbei.cbSize = sizeof( DBEVENTINFO ); dbei.szModule = m_szModuleName; dbei.timestamp = ( DWORD )time( NULL ); dbei.flags = DBEF_UTF; dbei.eventType = EVENTTYPE_AUTHREQUEST; dbei.cbBlob = (DWORD)(sizeof( DWORD )+ sizeof( HANDLE ) + strlen( szNick ) + strlen( szJid ) + 5); PBYTE pCurBlob = dbei.pBlob = ( PBYTE ) mir_alloc( dbei.cbBlob ); *(( PDWORD ) pCurBlob ) = 0; pCurBlob += sizeof( DWORD ); *(( PHANDLE ) pCurBlob ) = hContact; pCurBlob += sizeof( HANDLE ); strcpy(( char* )pCurBlob, szNick ); pCurBlob += strlen( szNick )+1; *pCurBlob = '\0'; pCurBlob++; //firstName *pCurBlob = '\0'; pCurBlob++; //lastName strcpy(( char* )pCurBlob, szJid ); pCurBlob += strlen( szJid )+1; *pCurBlob = '\0'; //reason JCallService( MS_DB_EVENT_ADD, ( WPARAM ) ( HANDLE ) NULL, ( LPARAM )&dbei ); Log( "Setup DBAUTHREQUEST with nick='%s' jid='%s'", szNick, szJid ); mir_free( szJid ); mir_free( szNick ); } /////////////////////////////////////////////////////////////////////////////// // JabberDBCreateContact() HANDLE CJabberProto::DBCreateContact( const TCHAR* jid, const TCHAR* nick, BOOL temporary, BOOL stripResource ) { TCHAR* s, *p, *q; size_t len; char* szProto; if ( jid==NULL || jid[0]=='\0' ) return NULL; s = mir_tstrdup( jid ); q = NULL; // strip resource if present if (( p = _tcschr( s, '@' )) != NULL ) if (( q = _tcschr( p, '/' )) != NULL ) *q = '\0'; if ( !stripResource && q!=NULL ) // so that resource is not stripped *q = '/'; len = _tcslen( s ); // We can't use JabberHContactFromJID() here because of the stripResource option HANDLE hContact = ( HANDLE ) JCallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ); while ( hContact != NULL ) { szProto = ( char* )JCallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM ) hContact, 0 ); if ( szProto!=NULL && !strcmp( m_szModuleName, szProto )) { DBVARIANT dbv; if ( !JGetStringT( hContact, "jid", &dbv )) { p = dbv.ptszVal; if ( p && _tcslen( p )>=len && ( p[len]=='\0'||p[len]=='/' ) && !_tcsnicmp( p, s, len )) { JFreeVariant( &dbv ); break; } JFreeVariant( &dbv ); } } hContact = ( HANDLE ) JCallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM ) hContact, 0 ); } if ( hContact == NULL ) { hContact = ( HANDLE ) JCallService( MS_DB_CONTACT_ADD, 0, 0 ); JCallService( MS_PROTO_ADDTOCONTACT, ( WPARAM ) hContact, ( LPARAM )m_szModuleName ); JSetStringT( hContact, "jid", s ); if ( nick != NULL && *nick != '\0' ) JSetStringT( hContact, "Nick", nick ); if ( temporary ) DBWriteContactSettingByte( hContact, "CList", "NotOnList", 1 ); else SendGetVcard( s ); Log( "Create Jabber contact jid=" TCHAR_STR_PARAM ", nick=" TCHAR_STR_PARAM, s, nick ); DBCheckIsTransportedContact(s,hContact); } mir_free( s ); return hContact; } BOOL CJabberProto::AddDbPresenceEvent(HANDLE hContact, BYTE btEventType) { if ( !hContact ) return FALSE; switch ( btEventType ) { case JABBER_DB_EVENT_PRESENCE_SUBSCRIBE: case JABBER_DB_EVENT_PRESENCE_SUBSCRIBED: case JABBER_DB_EVENT_PRESENCE_UNSUBSCRIBE: case JABBER_DB_EVENT_PRESENCE_UNSUBSCRIBED: if ( !m_options.LogPresence ) return FALSE; break; case JABBER_DB_EVENT_PRESENCE_ERROR: if ( !m_options.LogPresenceErrors ) return FALSE; break; } DBEVENTINFO dbei; dbei.cbSize = sizeof( dbei ); dbei.pBlob = &btEventType; dbei.cbBlob = sizeof( btEventType ); dbei.eventType = JABBER_DB_EVENT_TYPE_PRESENCE; dbei.flags = DBEF_READ; dbei.timestamp = time( NULL ); dbei.szModule = m_szModuleName; CallService( MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei ); return TRUE; } /////////////////////////////////////////////////////////////////////////////// // JabberGetAvatarFileName() - gets a file name for the avatar image static HANDLE hJabberAvatarsFolder = NULL; static bool bInitDone = false; void CJabberProto::InitCustomFolders( void ) { if ( bInitDone ) return; bInitDone = true; if ( ServiceExists( MS_FOLDERS_REGISTER_PATH )) { TCHAR AvatarsFolder[MAX_PATH]; TCHAR* tmpPath = Utils_ReplaceVarsT( _T("%miranda_avatarcache%")); mir_sntprintf( AvatarsFolder, SIZEOF( AvatarsFolder ), _T("%s\\Jabber"), tmpPath ); mir_free(tmpPath); hJabberAvatarsFolder = FoldersRegisterCustomPathT( m_szModuleName, "Avatars", AvatarsFolder ); // title!!!!!!!!!!! } } void CJabberProto::GetAvatarFileName( HANDLE hContact, TCHAR* pszDest, size_t cbLen ) { size_t tPathLen; TCHAR* path = ( TCHAR* )alloca( cbLen * sizeof( TCHAR )); InitCustomFolders(); if ( hJabberAvatarsFolder == NULL || FoldersGetCustomPathT( hJabberAvatarsFolder, path, (int)cbLen, _T(""))) { TCHAR *tmpPath = Utils_ReplaceVarsT( _T("%miranda_avatarcache%")); tPathLen = mir_sntprintf( pszDest, cbLen, _T("%s\\Jabber"), tmpPath ); mir_free(tmpPath); } else tPathLen = mir_sntprintf( pszDest, cbLen, _T("%s"), path ); DWORD dwAttributes = GetFileAttributes( pszDest ); if ( dwAttributes == 0xffffffff || ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY ) == 0 ) JCallService( MS_UTILS_CREATEDIRTREET, 0, ( LPARAM )pszDest ); pszDest[ tPathLen++ ] = '\\'; char* szFileType = NULL; switch( JGetByte( hContact, "AvatarType", PA_FORMAT_PNG )) { case PA_FORMAT_JPEG: szFileType = "jpg"; break; case PA_FORMAT_PNG: szFileType = "png"; break; case PA_FORMAT_GIF: szFileType = "gif"; break; case PA_FORMAT_BMP: szFileType = "bmp"; break; } if ( hContact != NULL ) { char str[ 256 ]; DBVARIANT dbv; if ( !JGetStringUtf( hContact, "jid", &dbv )) { strncpy( str, dbv.pszVal, sizeof str ); str[ sizeof(str)-1 ] = 0; JFreeVariant( &dbv ); } else _i64toa(( LONG_PTR )hContact, str, 10 ); char* hash = JabberSha1( str ); mir_sntprintf( pszDest + tPathLen, MAX_PATH - tPathLen, _T(TCHAR_STR_PARAM) _T(".") _T(TCHAR_STR_PARAM), hash, szFileType ); mir_free( hash ); } else if ( m_ThreadInfo != NULL ) { mir_sntprintf( pszDest + tPathLen, MAX_PATH - tPathLen, _T("%s@") _T(TCHAR_STR_PARAM) _T(" avatar.") _T(TCHAR_STR_PARAM), m_ThreadInfo->username, m_ThreadInfo->server, szFileType ); } else { DBVARIANT dbv1, dbv2; BOOL res1 = DBGetContactSettingString( NULL, m_szModuleName, "LoginName", &dbv1 ); BOOL res2 = DBGetContactSettingString( NULL, m_szModuleName, "LoginServer", &dbv2 ); mir_sntprintf( pszDest + tPathLen, MAX_PATH - tPathLen, _T(TCHAR_STR_PARAM) _T("@") _T(TCHAR_STR_PARAM) _T(" avatar.") _T(TCHAR_STR_PARAM), res1 ? "noname" : dbv1.pszVal, res2 ? m_szModuleName : dbv2.pszVal, szFileType ); if (!res1) JFreeVariant( &dbv1 ); if (!res2) JFreeVariant( &dbv2 ); } } /////////////////////////////////////////////////////////////////////////////// // JabberResolveTransportNicks - massive vcard update void CJabberProto::ResolveTransportNicks( const TCHAR* jid ) { // Set all contacts to offline HANDLE hContact = m_ThreadInfo->resolveContact; if ( hContact == NULL ) hContact = ( HANDLE ) JCallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ); for ( ; hContact != NULL; hContact = ( HANDLE )JCallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM ) hContact, 0 )) { char* szProto = ( char* )JCallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM ) hContact, 0 ); if ( lstrcmpA( szProto, m_szModuleName )) continue; if ( !JGetByte( hContact, "IsTransported", 0 )) continue; DBVARIANT dbv, nick; if ( JGetStringT( hContact, "jid", &dbv )) continue; if ( JGetStringT( hContact, "Nick", &nick )) { JFreeVariant( &dbv ); continue; } TCHAR* p = _tcschr( dbv.ptszVal, '@' ); if ( p ) { *p = 0; if ( !lstrcmp( jid, p+1 ) && !lstrcmp( dbv.ptszVal, nick.ptszVal )) { *p = '@'; m_ThreadInfo->resolveID = SendGetVcard( dbv.ptszVal ); m_ThreadInfo->resolveContact = hContact; JFreeVariant( &dbv ); JFreeVariant( &nick ); return; } } JFreeVariant( &dbv ); JFreeVariant( &nick ); } m_ThreadInfo->resolveID = -1; m_ThreadInfo->resolveContact = NULL; } /////////////////////////////////////////////////////////////////////////////// // JabberSetServerStatus() void CJabberProto::SetServerStatus( int iNewStatus ) { if ( !m_bJabberOnline ) return; // change status int oldStatus = m_iStatus; switch ( iNewStatus ) { case ID_STATUS_ONLINE: case ID_STATUS_NA: case ID_STATUS_FREECHAT: case ID_STATUS_INVISIBLE: m_iStatus = iNewStatus; break; case ID_STATUS_AWAY: case ID_STATUS_ONTHEPHONE: case ID_STATUS_OUTTOLUNCH: m_iStatus = ID_STATUS_AWAY; break; case ID_STATUS_DND: case ID_STATUS_OCCUPIED: m_iStatus = ID_STATUS_DND; break; default: return; } if ( m_iStatus == oldStatus ) return; // send presence update SendPresence( m_iStatus, true ); JSendBroadcast( NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, ( HANDLE ) oldStatus, m_iStatus ); } // Process a string, and double all % characters, according to chat.dll's restrictions // Returns a pointer to the new string (old one is not freed) TCHAR* EscapeChatTags(TCHAR* pszText) { int nChars = 0; for ( TCHAR* p = pszText; ( p = _tcschr( p, '%' )) != NULL; p++ ) nChars++; if ( nChars == 0 ) return mir_tstrdup( pszText ); TCHAR* pszNewText = (TCHAR*)mir_alloc( sizeof(TCHAR)*(_tcslen( pszText ) + 1 + nChars )), *s, *d; if ( pszNewText == NULL ) return mir_tstrdup( pszText ); for ( s = pszText, d = pszNewText; *s; s++ ) { if ( *s == '%' ) *d++ = '%'; *d++ = *s; } *d = 0; return pszNewText; } TCHAR* UnEscapeChatTags(TCHAR* str_in) { TCHAR* s = str_in, *d = str_in; while ( *s ) { if ( *s == '%' && s[1] == '%' ) s++; *d++ = *s++; } *d = 0; return str_in; } ////////////////////////////////////////////////////////////////////////// // update MirVer with data for active resource struct { TCHAR *node; TCHAR *name; } static sttCapsNodeToName_Map[] = { { _T("http://miranda-im.org"), _T("Miranda IM Jabber") }, { _T("http://www.google.com"), _T("GTalk") }, { _T("http://mail.google.com"), _T("GMail") }, { _T("http://talk.google.com/xmpp/bot"), _T("GTalk Bot") }, { _T("http://www.android.com"), _T("Android") }, }; void CJabberProto::UpdateMirVer(JABBER_LIST_ITEM *item) { HANDLE hContact = HContactFromJID(item->jid); if (!hContact) return; Log("JabberUpdateMirVer: for jid " TCHAR_STR_PARAM, item->jid); int resource = -1; if (item->resourceMode == RSMODE_LASTSEEN) resource = item->lastSeenResource; else if (item->resourceMode == RSMODE_MANUAL) resource = item->manualResource; if ((resource < 0) || (resource >= item->resourceCount)) return; UpdateMirVer( hContact, &item->resource[resource] ); } void CJabberProto::FormatMirVer(JABBER_RESOURCE_STATUS *resource, TCHAR *buf, int bufSize) { if ( !buf || !bufSize ) return; buf[ 0 ] = _T('\0'); if ( !resource ) return; // jabber:iq:version info requested and exists? if ( resource->dwVersionRequestTime && resource->software ) { Log("JabberUpdateMirVer: for iq:version rc " TCHAR_STR_PARAM ": " TCHAR_STR_PARAM, resource->resourceName, resource->software); if ( !resource->version || _tcsstr(resource->software, resource->version)) lstrcpyn(buf, resource->software, bufSize); else mir_sntprintf(buf, bufSize, _T("%s %s"), resource->software, resource->version); } // no version info and no caps info? set MirVer = resource name else if ( !resource->szCapsNode || !resource->szCapsVer ) { Log("JabberUpdateMirVer: for rc " TCHAR_STR_PARAM ": " TCHAR_STR_PARAM, resource->resourceName, resource->resourceName); if ( resource->resourceName ) lstrcpyn(buf, resource->resourceName, bufSize); } // XEP-0115 caps mode else { Log("JabberUpdateMirVer: for rc " TCHAR_STR_PARAM ": " TCHAR_STR_PARAM "#" TCHAR_STR_PARAM, resource->resourceName, resource->szCapsNode, resource->szCapsVer); int i; // search through known software list for (i = 0; i < SIZEOF(sttCapsNodeToName_Map); ++i) if ( _tcsstr( resource->szCapsNode, sttCapsNodeToName_Map[i].node ) ) { mir_sntprintf( buf, bufSize, _T("%s %s"), sttCapsNodeToName_Map[i].name, resource->szCapsVer ); break; } // unknown software if (i == SIZEOF(sttCapsNodeToName_Map)) mir_sntprintf( buf, bufSize, _T("%s %s"), resource->szCapsNode, resource->szCapsVer ); } // attach additional info for fingerprint plguin if (resource->resourceName && !_tcsstr(buf, resource->resourceName)) { if (_tcsstr(buf, _T("Miranda IM")) || m_options.ShowForeignResourceInMirVer ) { int offset = lstrlen(buf); mir_sntprintf(buf + offset, bufSize - offset, _T(" [%s]"), resource->resourceName); } } if (resource->szCapsExt && _tcsstr(resource->szCapsExt, _T(JABBER_EXT_SECUREIM)) && !_tcsstr(buf, _T("(SecureIM)"))) { int offset = lstrlen(buf); mir_sntprintf(buf + offset, bufSize - offset, _T(" (SecureIM)")); } } void CJabberProto::UpdateMirVer(HANDLE hContact, JABBER_RESOURCE_STATUS *resource) { TCHAR szMirVer[ 512 ]; FormatMirVer(resource, szMirVer, SIZEOF(szMirVer)); if ( szMirVer[0] ) JSetStringT( hContact, "MirVer", szMirVer ); else JDeleteSetting( hContact, "MirVer" ); DBVARIANT dbv; if ( !JGetStringT( hContact, "jid", &dbv )) { TCHAR szFullJid[ JABBER_MAX_JID_LEN ]; if ( resource->resourceName ) mir_sntprintf( szFullJid, SIZEOF( szFullJid ), _T("%s/%s"), dbv.ptszVal, resource->resourceName ); else lstrcpyn( szFullJid, dbv.ptszVal, SIZEOF(szFullJid) ); JSetStringT( hContact, DBSETTING_DISPLAY_UID, szFullJid ); JFreeVariant( &dbv ); } } void CJabberProto::UpdateSubscriptionInfo(HANDLE hContact, JABBER_LIST_ITEM *item) { switch (item->subscription) { case SUB_TO: JSetStringT(hContact, "SubscriptionText", TranslateT("To")); JSetString(hContact, "Subscription", "to"); JSetByte(hContact, "Auth", 0); JSetByte(hContact, "Grant", 1); break; case SUB_FROM: JSetStringT(hContact, "SubscriptionText", TranslateT("From")); JSetString(hContact, "Subscription", "from"); JSetByte(hContact, "Auth", 1); JSetByte(hContact, "Grant", 0); break; case SUB_BOTH: JSetStringT(hContact, "SubscriptionText", TranslateT("Both")); JSetString(hContact, "Subscription", "both"); JSetByte(hContact, "Auth", 0); JSetByte(hContact, "Grant", 0); break; case SUB_NONE: JSetStringT(hContact, "SubscriptionText", TranslateT("None")); JSetString(hContact, "Subscription", "none"); JSetByte(hContact, "Auth", 1); JSetByte(hContact, "Grant", 1); break; } } void CJabberProto::SetContactOfflineStatus( HANDLE hContact ) { if ( JGetWord( hContact, "Status", ID_STATUS_OFFLINE ) != ID_STATUS_OFFLINE ) JSetWord( hContact, "Status", ID_STATUS_OFFLINE ); JDeleteSetting( hContact, DBSETTING_XSTATUSID ); JDeleteSetting( hContact, DBSETTING_XSTATUSNAME ); JDeleteSetting( hContact, DBSETTING_XSTATUSMSG ); JDeleteSetting( hContact, DBSETTING_DISPLAY_UID ); ResetAdvStatus( hContact, ADVSTATUS_MOOD ); ResetAdvStatus( hContact, ADVSTATUS_TUNE ); //JabberUpdateContactExtraIcon(hContact); } void CJabberProto::InitPopups(void) { TCHAR desc[256]; char name[256]; POPUPCLASS ppc = {0}; ppc.cbSize = sizeof(ppc); ppc.flags = PCF_TCHAR; ppc.ptszDescription = desc; ppc.pszName = name; ppc.hIcon = LoadIconEx("main"); ppc.colorBack = RGB(191, 0, 0); //Red ppc.colorText = RGB(255, 245, 225); //Yellow ppc.iSeconds = 60; mir_sntprintf(desc, SIZEOF(desc), _T("%s %s"), m_tszUserName, TranslateT("Errors")); mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Error"); JCallService(MS_POPUP_REGISTERCLASS, 0, (WPARAM)&ppc); } void CJabberProto::MsgPopup(HANDLE hContact, const TCHAR *szMsg, const TCHAR *szTitle) { if (ServiceExists(MS_POPUP_ADDPOPUPCLASS)) { char name[256]; POPUPDATACLASS ppd = { sizeof(ppd) }; ppd.ptszTitle = szTitle; ppd.ptszText = szMsg; ppd.pszClassName = name; ppd.hContact = hContact; mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Error"); JCallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&ppd); } else { DWORD mtype = MB_OK | MB_SETFOREGROUND | MB_ICONSTOP; MessageBox(NULL, szMsg, szTitle, mtype); } }