/*
MetaContacts Plugin for Miranda IM.
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
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.
*/
/** @file meta_services.c
*
* Functions specific to the protocol part of the plugin.
* Centralizes all the functions called by Miranda to make
* the plugin work as a protocol.
*/
#include "metacontacts.h"
#define PREF_METANODB 0x2000 //!< Flag to indicate message should not be added to db by filter when sending
char *pendingACK = 0; //!< Name of the protocol in which an ACK is about to come.
int previousMode, //!< Previous status of the MetaContacts Protocol
mcStatus; //!< Current status of the MetaContacts Protocol
HGENMENU
hMenuConvert, //!< \c HANDLE to the convert menu item.
hMenuAdd, //!< \c HANDLE to the add to menu item.
hMenuEdit, //!< \c HANDLE to the edit menu item.
hMenuDelete, //!< \c HANDLE to the delete menu item.
hMenuDefault, //!< \c HANDLE to the delete menu item.
hMenuForceDefault, //!< \c HANDLE to the delete menu item.
hMenuOnOff; //!< \c HANDLE to the enable/disable menu item.
HANDLE
hEventDefaultChanged, //!< \c HANDLE to the 'default changed' event
hEventForceSend, //!< \c HANDLE to the 'force send' event
hEventUnforceSend, //!< \c HANDLE to the 'unforce send' event
hSubcontactsChanged, //!< \c HANDLE to the 'contacts changed' event
hEventNudge;
DWORD nextMetaID; //!< Global variable specifying the ID value the next MetaContact will have.
BOOL message_window_api_enabled = FALSE; //!< Global variable specifying whether the message window api ver 0.0.0.1+ is available
// stuff for mw_clist extra icon
HANDLE hExtraImage[MAX_PROTOCOLS * 2]; // online and offline icons
char proto_names[MAX_PROTOCOLS * 128];
HANDLE hProtoIcons[MAX_PROTOCOLS * 2]; // online and offline icons
UINT_PTR setStatusTimerId = 0;
BOOL firstSetOnline = TRUE; // see Meta_SetStatus function
/** Get the capabilities of the "MetaContacts" protocol.
*
* @param wParam : equals to one of the following values :\n
PFLAGNUM_1 | PFLAGNUM_2 | PFLAGNUM_3 | PFLAGNUM_4 | PFLAG_UNIQUEIDTEXT | PFLAG_MAXLENOFMESSAGE | PFLAG_UNIQUEIDSETTING .
* @param lParam : Allways set to 0.
*
* @return Depending on the \c WPARAM.
*/
INT_PTR Meta_GetCaps(WPARAM wParam,LPARAM lParam)
{
int ret = 0;
switch (wParam) {
case PFLAGNUM_1:
//ret = PF1_IM | PF1_URL | PF1_FILE | PF1_MODEMSG | PF1_AUTHREQ | PF1_ADDED;
//ret = PF1_IMSEND | PF1_URLSEND | PF1_FILESEND | PF1_MODEMSGSEND;
ret = PF1_IM | PF1_CHAT | PF1_FILESEND | PF1_MODEMSGRECV | PF1_NUMERICUSERID;
break;
case PFLAGNUM_2:
if (!options.suppress_proto) {
ret = PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND
| PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE;
}
//ret = PF2_ONLINE;
break;
case PFLAGNUM_3:
//ret = PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND;
ret = PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND
| PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE;
break;
case PFLAGNUM_4:
//ret = PF4_FORCEAUTH;
ret = PF4_SUPPORTTYPING | PF4_AVATARS;
break;
case PFLAGNUM_5:
ret = PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND
| PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE;
break;
case PFLAG_UNIQUEIDTEXT:
ret = (INT_PTR) Translate("Meta ID");
break;
case PFLAG_MAXLENOFMESSAGE:
ret = 2000;
break;
case PFLAG_UNIQUEIDSETTING:
ret = (INT_PTR) META_ID;
break;
}
return ret;
}
/** Copy the name of the protocole into lParam
* @param wParam : max size of the name
* @param lParam : reference to a char *, which will hold the name
*/
INT_PTR Meta_GetName(WPARAM wParam,LPARAM lParam)
{
char *name = (char *)Translate(META_PROTO);
size_t size = min(strlen(name),wParam-1); // copy only the first size bytes.
if (strncpy((char *)lParam,name,size)==NULL)
return 1;
((char *)lParam)[size]='\0';
return 0;
}
/** Loads the icon corresponding to the status
* Called by the CList when the status changes.
* @param wParam : one of the following values : \n
PLI_PROTOCOL | PLI_ONLINE | PLI_OFFLINE
* @return an \c HICON in which the icon has been loaded.
*/
INT_PTR Meta_LoadIcon(WPARAM wParam,LPARAM lParam)
{
UINT id;
switch (wParam & 0xFFFF)
{
case PLI_PROTOCOL:
id = IDI_MCMENU;
break;
case PLI_ONLINE:
id = IDI_MCMENU;
break;
case PLI_OFFLINE:
id = IDI_MCMENU;
break;
default:
return 0;
}
return (INT_PTR) LoadImage(hInstance, MAKEINTRESOURCE(id), IMAGE_ICON,
GetSystemMetrics(wParam & PLIF_SMALL ? SM_CXSMICON : SM_CXICON),
GetSystemMetrics(wParam & PLIF_SMALL ? SM_CYSMICON : SM_CYICON), 0);
}
//static DWORD CALLBACK SetStatusThread( LPVOID param )
void CALLBACK SetStatusThread(HWND hWnd, UINT msg, UINT_PTR id, DWORD dw)
{
previousMode = mcStatus;
//Sleep(options.set_status_from_offline_delay);
mcStatus = (int)ID_STATUS_ONLINE;
ProtoBroadcastAck(META_PROTO,NULL,ACKTYPE_STATUS,ACKRESULT_SUCCESS, (HANDLE)previousMode, mcStatus);
//return 0;
KillTimer(0, setStatusTimerId);
}
/** Changes the status and notifies everybody
* @param wParam : The new mode
* @param lParam : Allways set to 0.
*/
INT_PTR Meta_SetStatus(WPARAM wParam,LPARAM lParam)
{
// firstSetOnline starts out true - used to delay metacontact's 'onlineness' to prevent double status notifications on startup
if (mcStatus == ID_STATUS_OFFLINE && firstSetOnline) {
// causes crash on exit if miranda is closed in under options.set_status_from_offline milliseconds!
//CloseHandle( CreateThread( NULL, 0, SetStatusThread, (void *)wParam, 0, 0 ));
setStatusTimerId = SetTimer(0, 0, options.set_status_from_offline_delay, SetStatusThread);
firstSetOnline = FALSE;
} else {
previousMode = mcStatus;
mcStatus = (int)wParam;
ProtoBroadcastAck(META_PROTO,NULL,ACKTYPE_STATUS,ACKRESULT_SUCCESS, (HANDLE)previousMode, mcStatus);
}
return 0;
}
/** Returns the current status
*/
INT_PTR Meta_GetStatus(WPARAM wParam,LPARAM lParam)
{
return mcStatus;
}
//////////////////////////////////////////////////////////
/// Copied from MSN plugin - sent acks need to be from different thread :(
//////////////////////////////////////////////////////////
typedef struct tag_TFakeAckParams
{
HANDLE hEvent;
HANDLE hContact;
LONG id;
char msg[512];
} TFakeAckParams;
/*
static DWORD CALLBACK sttFakeAckSuccess( LPVOID param )
{
TFakeAckParams *tParam = ( TFakeAckParams* )param;
WaitForSingleObject( tParam->hEvent, INFINITE );
Sleep( 100 );
ProtoBroadcastAck(META_PROTO, tParam->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, ( HANDLE )tParam->id, 0 );
CloseHandle( tParam->hEvent );
free(tParam);
return 0;
}
*/
static DWORD CALLBACK sttFakeAckFail( LPVOID param )
{
TFakeAckParams *tParam = ( TFakeAckParams* )param;
WaitForSingleObject( tParam->hEvent, INFINITE );
Sleep( 100 );
ProtoBroadcastAck(META_PROTO, tParam->hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, ( HANDLE )tParam->id, (WPARAM)tParam->msg );
CloseHandle( tParam->hEvent );
mir_free(tParam);
return 0;
}
/** Filter messages sent by subcontacts
*
* When groups are disabled, add an event to the DB for the metacontact to maintain history
*
* @param wParam : index of the protocol in the protocol chain.
* @param lParam : \c CCSDATA structure holding all the information about the message.
*
* @return 0 on success, 1 otherwise.
*/
INT_PTR MetaFilter_SendMessage(WPARAM wParam,LPARAM lParam)
{
DBEVENTINFO dbei;
CCSDATA *ccs = (CCSDATA *) lParam;
HANDLE hMeta;
if ((hMeta = (HANDLE)DBGetContactSettingDword(ccs->hContact,META_PROTO, "Handle", (DWORD)0)) == (DWORD)0) {
return CallService(MS_PROTO_CHAINSEND, wParam, lParam); // Can't find the MetaID of the metacontact linked to
}
// if subcontact sending, add db event to keep metacontact history correct
if (options.metahistory && !(ccs->wParam & PREF_METANODB)) {
// reject "file As Message" messages
if (strlen((char *)ccs->lParam) > 5 && strncmp((char *)ccs->lParam, "<%fAM", 5) == 0)
return CallService(MS_PROTO_CHAINSEND, wParam, lParam); // continue processing
// reject "data As Message" messages
if (strlen((char *)ccs->lParam) > 5 && strncmp((char *)ccs->lParam, "<%dAM", 5) == 0)
return CallService(MS_PROTO_CHAINSEND, wParam, lParam); // continue processing
// reject "OTR" messages
if (strlen((char *)ccs->lParam) > 5 && strncmp((char *)ccs->lParam, "?OTR", 4) == 0)
return CallService(MS_PROTO_CHAINSEND, wParam, lParam); // continue processing
ZeroMemory(&dbei, sizeof(dbei));
dbei.cbSize = sizeof(dbei);
dbei.szModule = META_PROTO;
dbei.flags = DBEF_SENT;
dbei.timestamp = time(NULL);
dbei.eventType = EVENTTYPE_MESSAGE;
if (ccs->wParam & PREF_RTL) dbei.flags |= DBEF_RTL;
if (ccs->wParam & PREF_UTF) dbei.flags |= DBEF_UTF;
dbei.cbBlob = (DWORD)strlen((char *)ccs->lParam) + 1;
if ( ccs->wParam & PREF_UNICODE )
dbei.cbBlob *= ( sizeof( wchar_t )+1 );
dbei.pBlob = (PBYTE)ccs->lParam;
CallService(MS_DB_EVENT_ADD, (WPARAM) hMeta, (LPARAM)&dbei);
}
return CallService(MS_PROTO_CHAINSEND, wParam, lParam);
}
INT_PTR Meta_SendNudge(WPARAM wParam,LPARAM lParam)
{
HANDLE hMeta = (HANDLE)wParam,
hSubContact = Meta_GetMostOnline(hMeta);
char servicefunction[ 100 ];
char *protoName = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hSubContact, 0);
sprintf(servicefunction, "%s/SendNudge", protoName);
return CallService(servicefunction, (WPARAM)hSubContact, lParam);
//return CallService("NUDGE/Send", (WPARAM)hSubContact, lParam);
}
/////////////////////////////////////////////////////////////////
/** Send a message to the protocol specific network.
*
* Call the function specific to the protocol that belongs
* to the contact chosen to send the message.
*
* @param wParam : index of the protocol in the protocol chain.
* @param lParam : \c CCSDATA structure holding all the information abour rhe message.
*
* @return 0 on success, 1 otherwise.
*/
INT_PTR Meta_SendMessage(WPARAM wParam,LPARAM lParam)
{
CCSDATA *ccs = (CCSDATA *) lParam;
char *proto = 0;
DWORD default_contact_number;
if ((default_contact_number = DBGetContactSettingDword(ccs->hContact,META_PROTO,"Default",(DWORD)-1)) == (DWORD)-1)
{
// This is a simple contact, let through the stack of protocols
// (this should normally not happen, since linked contacts do not appear on the list.)
return CallService(MS_PROTO_CHAINSEND, wParam, lParam);
}
char szServiceName[100];
HANDLE most_online = Meta_GetMostOnline(ccs->hContact);
if (!most_online) {
DWORD dwThreadId;
HANDLE hEvent;
TFakeAckParams *tfap;
// send failure to notify user of reason
hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
tfap = (TFakeAckParams *)mir_alloc(sizeof(TFakeAckParams));
tfap->hContact = ccs->hContact;
tfap->hEvent = hEvent;
tfap->id = 10;
strcpy(tfap->msg, Translate("No online contacts found."));
CloseHandle( CreateThread( NULL, 0, sttFakeAckFail, tfap, 0, &dwThreadId ));
SetEvent( hEvent );
return 10;
}
Meta_CopyContactNick(ccs->hContact, most_online);
ccs->hContact = most_online;
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
Meta_SetNick(proto); // (no matter what was there before)
// don't bypass filters etc
strncpy(szServiceName, PSS_MESSAGE, sizeof(szServiceName));
if (ccs->wParam & PREF_UNICODE) {
char szTemp[100];
_snprintf(szTemp, sizeof(szTemp), "%s%sW", proto, PSS_MESSAGE);
if (ServiceExists(szTemp))
strncpy(szServiceName, PSS_MESSAGE "W", sizeof(szServiceName));
}
if (options.subhistory && !(ccs->wParam & PREF_METANODB)) {
// add sent event to subcontact
DBEVENTINFO dbei = { sizeof(dbei) };
dbei.szModule = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0);
if (dbei.szModule) {
dbei.flags = DBEF_SENT;
dbei.timestamp = time(NULL);
dbei.eventType = EVENTTYPE_MESSAGE;
if (ccs->wParam & PREF_RTL) dbei.flags |= DBEF_RTL;
if (ccs->wParam & PREF_UTF) dbei.flags |= DBEF_UTF;
dbei.cbBlob = (DWORD)strlen((char *)ccs->lParam) + 1;
if ( ccs->wParam & PREF_UNICODE )
dbei.cbBlob *= ( sizeof( wchar_t )+1 );
dbei.pBlob = (PBYTE)ccs->lParam;
CallService(MS_DB_EVENT_ADD, (WPARAM) ccs->hContact, (LPARAM)&dbei);
}
}
// prevent send filter from adding another copy of this send event to the db
ccs->wParam |= PREF_METANODB;
return CallContactService(ccs->hContact, szServiceName, ccs->wParam, ccs->lParam);
}
/** Transmit a message received by a contact.
*
* Forward the message received by a contact linked to a MetaContact
* to that MetaContact and inhibit the further reception of this message
* by the standard protocol of the contact.
*
* @param wParam : index of the protocol in the protocol chain.
* @param lParam : \c CCSDATA structure holding all the information about the message.
*
* @return 0 on success, 1 otherwise.
*/
INT_PTR MetaFilter_RecvMessage(WPARAM wParam,LPARAM lParam)
{
DBEVENTINFO dbei;
CCSDATA *ccs = (CCSDATA *) lParam;
PROTORECVEVENT *pre = (PROTORECVEVENT *) ccs->lParam;
HANDLE hMeta;
if ((hMeta = (HANDLE)DBGetContactSettingDword(ccs->hContact,META_PROTO, "Handle", (DWORD)0)) == (DWORD)0) {
CallService(MS_PROTO_CHAINRECV, wParam, (LPARAM)ccs); // Can't find the MetaID of the metacontact linked to
// this contact, let through the protocol chain
return 0;
}
if (options.set_default_on_recv) {
if (options.temp_default && DBGetContactSettingDword(hMeta, META_PROTO, "SavedDefault", (DWORD)-1) == (DWORD)-1)
DBWriteContactSettingDword(hMeta, META_PROTO, "SavedDefault", DBGetContactSettingDword(hMeta, META_PROTO, "Default", 0));
DBWriteContactSettingDword(hMeta, META_PROTO, "Default", DBGetContactSettingDword(ccs->hContact, META_PROTO, "ContactNumber", 0));
NotifyEventHooks(hEventDefaultChanged, (WPARAM)hMeta, (LPARAM)ccs->hContact); // nick set in event handler
}
// if meta disabled (now message api) or window open (message api), or using subcontact windows,
// let through but add db event for metacontact history
if (!Meta_IsEnabled()
|| DBGetContactSettingByte(ccs->hContact, META_PROTO, "WindowOpen", 0) == 1
|| options.subcontact_windows)
{
// add a clist event, so that e.g. there is an icon flashing
// (only add it when message api available, 'cause then we can remove the event when the message window is opened)
if (message_window_api_enabled
&& DBGetContactSettingByte(ccs->hContact, META_PROTO, "WindowOpen", 0) == 0
&& DBGetContactSettingByte(hMeta, META_PROTO, "WindowOpen", 0) == 0
&& options.flash_meta_message_icon)
{
CLISTEVENT cle;
char toolTip[256], *contactName;
ZeroMemory(&cle, sizeof(cle));
cle.cbSize = sizeof(cle);
cle.hContact = hMeta;
cle.hDbEvent = ccs->hContact; // use subcontact handle as key - then we can remove all events if the subcontact window is opened
cle.hIcon = LoadSkinnedIcon(SKINICON_EVENT_MESSAGE);
cle.pszService = "MetaContacts/CListMessageEvent";
contactName = (char *) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hMeta, 0);
_snprintf(toolTip, sizeof(toolTip), Translate("Message from %s"), contactName);
cle.pszTooltip = toolTip;
CallService(MS_CLIST_ADDEVENT, 0, (LPARAM) & cle);
}
if (options.metahistory) {
BOOL added = FALSE;
// should be able to do this, but some protos mess with the memory
if (options.use_proto_recv)
{
// use the subcontact's protocol 'recv' service to add the meta's history (AIMOSCAR removes HTML here!) if possible
char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0);
if (proto) {
char service[256];
HANDLE hSub = ccs->hContact;
DWORD flags = pre->flags;
mir_snprintf(service, 256, "%s%s", proto, PSR_MESSAGE);
ccs->hContact = hMeta;
pre->flags |= (DBGetContactSettingByte(hMeta, META_PROTO, "WindowOpen", 0) ? 0 : PREF_CREATEREAD);
if (ServiceExists(service) && !CallService(service, 0, (LPARAM)ccs))
added = TRUE;
ccs->hContact = hSub;
pre->flags = flags;
}
}
if (!added) {
// otherwise add raw db event
ZeroMemory(&dbei, sizeof(dbei));
dbei.cbSize = sizeof(dbei);
dbei.szModule = META_PROTO;
dbei.timestamp = pre->timestamp;
dbei.flags = (DBGetContactSettingByte(hMeta, META_PROTO, "WindowOpen", 0) ? 0 : DBEF_READ);
if (pre->flags & PREF_RTL) dbei.flags |= DBEF_RTL;
if (pre->flags & PREF_UTF) dbei.flags |= DBEF_UTF;
dbei.eventType = EVENTTYPE_MESSAGE;
dbei.cbBlob = (DWORD)strlen(pre->szMessage) + 1;
if ( pre->flags & PREF_UNICODE ) {
dbei.cbBlob *= ( sizeof( wchar_t )+1 );
}
dbei.pBlob = (PBYTE) pre->szMessage;
CallService(MS_DB_EVENT_ADD, (WPARAM) hMeta, (LPARAM)&dbei);
}
}
CallService(MS_PROTO_CHAINRECV, wParam, (LPARAM)ccs);
return 0;
} // else:
/*
// add event to subcontact history (would do it in meta_recvmessage, but here we have the hcontact)
// should be able to use the method below, except some protos can mess with the memory
if (options.subhistory) {
ZeroMemory(&dbei, sizeof(dbei));
dbei.cbSize = sizeof(dbei);
dbei.szModule = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0);
dbei.timestamp = pre->timestamp;
dbei.flags = (DBGetContactSettingByte(ccs->hContact, META_PROTO, "WindowOpen", 0) ? 0 : DBEF_READ);
if (pre->flags & PREF_RTL) dbei.flags |= DBEF_RTL;
dbei.eventType = EVENTTYPE_MESSAGE;
dbei.cbBlob = strlen(pre->szMessage) + 1;
if ( pre->flags & PREF_UNICODE )
dbei.cbBlob *= ( sizeof( wchar_t )+1 );
dbei.pBlob = (PBYTE) pre->szMessage;
CallService(MS_DB_EVENT_ADD, (WPARAM) ccs->hContact, (LPARAM)&dbei);
}
*/
{
HANDLE hSub = ccs->hContact;
ccs->hContact = hMeta; // Forward to the associated MetaContact.
CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)ccs);
ccs->hContact = hSub;
}
if (options.subhistory && !(ccs->wParam & PREF_METANODB)) {
// allow event pass through and thereby be added to subcontact history
pre->flags |= (DBGetContactSettingByte(ccs->hContact, META_PROTO, "WindowOpen", 0) ? 0 : PREF_CREATEREAD);
CallService(MS_PROTO_CHAINRECV, wParam, (LPARAM)ccs); // pass through as normal
return 0;
}
return 1; // Stop further processing.
}
/** Receive a message for a MetaContact
*
* @return 0
*/
INT_PTR Meta_RecvMessage(WPARAM wParam, LPARAM lParam)
{
DBEVENTINFO dbei;
CCSDATA *ccs = (CCSDATA *) lParam;
PROTORECVEVENT *pre = (PROTORECVEVENT *) ccs->lParam;
char *proto;
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)ccs->hContact, 0);
// contact is not a meta proto contact - just leave it
if (!proto || strcmp(proto, META_PROTO)) {
return 0;
}
if (options.use_proto_recv)
{
// use the subcontact's protocol to add the db if possible (AIMOSCAR removes HTML here!)
HANDLE most_online = Meta_GetMostOnline(ccs->hContact);
char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
if (proto) {
char service[256];
mir_snprintf(service, 256, "%s%s", proto, PSR_MESSAGE);
if (CallService(service, wParam, lParam) != CALLSERVICE_NOTFOUND)
return 0;
}
}
// otherwise, add event to db directly
ZeroMemory(&dbei, sizeof(dbei));
dbei.cbSize = sizeof(dbei);
dbei.szModule = META_PROTO;
dbei.timestamp = pre->timestamp;
dbei.flags = (pre->flags & PREF_CREATEREAD ? DBEF_READ : 0);
if (pre->flags & PREF_RTL) dbei.flags |= DBEF_RTL;
if (pre->flags & PREF_UTF) dbei.flags |= DBEF_UTF;
dbei.eventType = EVENTTYPE_MESSAGE;
dbei.cbBlob = (DWORD)strlen(pre->szMessage) + 1;
if ( pre->flags & PREF_UNICODE )
dbei.cbBlob *= ( sizeof( wchar_t )+1 );
dbei.pBlob = (PBYTE) pre->szMessage;
CallService(MS_DB_EVENT_ADD, (WPARAM) ccs->hContact, (LPARAM)&dbei);
return 0;
}
/** Called when an ACK is received.
*
* Retransmit the ACK sent by a simple contact so that it
* looks like it was the MetaContact that sends the ACK.
*
* @param wParam : Allways set to 0.
* @param lParam : Reference to a ACKDATA that contains
information about the ACK.
* @return 0 on success, 1 otherwise.
*/
int Meta_HandleACK(WPARAM wParam, LPARAM lParam)
{
ACKDATA *ack = (ACKDATA*) lParam;
HANDLE hUser;
if (ack->hContact == 0 || (hUser = (HANDLE)DBGetContactSettingDword(ack->hContact,META_PROTO,"Handle",0)) == 0)
return 0; // Can't find the MetaID, let through the protocol chain
if (!strcmp(ack->szModule, META_PROTO)) {
return 0; // don't rebroadcast our own acks
}
// if it's for something we don't support, ignore
if (ack->type != ACKTYPE_MESSAGE && ack->type != ACKTYPE_CHAT && ack->type != ACKTYPE_FILE && ack->type != ACKTYPE_AWAYMSG
&& ack->type != ACKTYPE_AVATAR && ack->type != ACKTYPE_GETINFO)
{
return 0;
}
// change the hContact in the avatar info struct, if it's the avatar we're using - else drop it
if (ack->type == ACKTYPE_AVATAR) {
if (ack->result == ACKRESULT_SUCCESS || ack->result == ACKRESULT_FAILED || ack->result == ACKRESULT_STATUS) {
HANDLE most_online;
DBVARIANT dbv;
// change avatar if the most online supporting avatars changes, or if we don't have one
most_online = Meta_GetMostOnlineSupporting(hUser, PFLAGNUM_4, PF4_AVATARS);
//if (AI.hContact == 0 || AI.hContact != most_online) {
if (ack->hContact == 0 || ack->hContact != most_online) {
return 0;
}
//if (!DBGetContactSetting(AI.hContact, "ContactPhoto", "File", &dbv)) {
if (!DBGetContactSetting(ack->hContact, "ContactPhoto", "File", &dbv)) {
DBWriteContactSettingTString(hUser, "ContactPhoto", "File", dbv.ptszVal);
DBFreeVariant(&dbv);
}
if (ack->hProcess) {
PROTO_AVATAR_INFORMATIONT AI;
memcpy(&AI, (PROTO_AVATAR_INFORMATIONT *)ack->hProcess, sizeof(PROTO_AVATAR_INFORMATIONT));
if (AI.hContact)
AI.hContact = hUser;
return ProtoBroadcastAck(META_PROTO,hUser,ack->type,ack->result, (HANDLE)&AI, ack->lParam);
} else
return ProtoBroadcastAck(META_PROTO,hUser,ack->type,ack->result, 0, ack->lParam);
}
}
return ProtoBroadcastAck(META_PROTO,hUser,ack->type,ack->result,ack->hProcess,ack->lParam);
}
// hiding contacts on "CList/UseGroups" setting changed can cause a crash - do it in a seperate thread during idle time
static DWORD sttHideContacts( BOOL param )
{
Meta_HideMetaContacts((int)param);
return 0;
}
/** Call whenever a contact changes one of its settings (for example, the status)
**
* @param wParam \c HANDLE to the contact that has change of its setting.
* @param lParam Reference to a structure that contains the setting that has changed (not used)
*/
int Meta_SettingChanged(WPARAM wParam, LPARAM lParam)
{
DBCONTACTWRITESETTING *dcws = (DBCONTACTWRITESETTING *)lParam;
char buffer[512], buffer2[512];
int contact_number;
HANDLE hMeta, most_online;
// hide metacontacts when groups disabled
if (wParam == 0
&& ((strcmp(dcws->szModule, "CList") == 0 && strcmp(dcws->szSetting, "UseGroups") == 0)
|| (strcmp(dcws->szModule, META_PROTO) == 0 && strcmp(dcws->szSetting, "Enabled") == 0)))
{
sttHideContacts(!Meta_IsEnabled());
return 0;
}
if (wParam == 0
&& strcmp(dcws->szModule, "Import") == 0 && strcmp(dcws->szSetting, "Completed") == 0)
{
// import process has just been run...call startup routines...
Meta_SetHandles();
{
HANDLE hContact = db_find_first();
int meta_id;
while ( hContact != NULL ) {
if ((meta_id = DBGetContactSettingDword(hContact,META_PROTO,META_ID,(DWORD)-1))!=(DWORD)-1) {
Meta_CopyData(hContact);
}
hContact = db_find_next(hContact);
}
}
Meta_HideLinkedContacts();
Meta_SuppressStatus(options.suppress_status);
}
if (wParam == 0
&& strcmp(dcws->szModule, "CListGroups") == 0 && dcws->value.type != DBVT_DELETED && strcmp(dcws->value.pszVal, META_HIDDEN_GROUP) == 0)
{
// someone is creating our hidden group!!
}
// from here on, we're just interested in contact settings
if (wParam == 0) return 0;
if ((hMeta=(HANDLE)DBGetContactSettingDword((HANDLE)wParam,META_PROTO,"Handle",0))!=0
&& CallService(MS_DB_CONTACT_IS, (WPARAM)hMeta, 0)) // just to be safe
{ // This contact is attached to a MetaContact.
contact_number = Meta_GetContactNumber((HANDLE)wParam);
if (contact_number == -1) return 0; // exit - db corruption
if (!meta_group_hack_disabled && !strcmp(dcws->szModule, "CList") && !strcmp(dcws->szSetting, "Group") &&
Meta_IsEnabled() && DBGetContactSettingByte((HANDLE)wParam, META_PROTO, "Hidden", 0) == 0 && !Miranda_Terminated()) {
if ((dcws->value.type == DBVT_ASCIIZ || dcws->value.type == DBVT_UTF8) && !Meta_IsHiddenGroup(dcws->value.pszVal)) {
// subcontact group reassigned - copy to saved group
MyDBWriteContactSetting((HANDLE)wParam, META_PROTO, "OldCListGroup", &dcws->value);
DBWriteContactSettingString((HANDLE)wParam, "CList", "Group", META_HIDDEN_GROUP);
} else if (dcws->value.type == DBVT_DELETED) {
DBDeleteContactSetting((HANDLE)wParam, META_PROTO, "OldCListGroup");
DBWriteContactSettingString((HANDLE)wParam, "CList", "Group", META_HIDDEN_GROUP);
}
} else
// copy IP
if (!strcmp(dcws->szSetting, "IP")) {
if (dcws->value.type == DBVT_DWORD)
DBWriteContactSettingDword(hMeta, META_PROTO, "IP", dcws->value.dVal);
else
DBDeleteContactSetting(hMeta, META_PROTO, "IP");
} else
// copy RealIP
if (!strcmp(dcws->szSetting, "RealIP")) {
if (dcws->value.type == DBVT_DWORD)
DBWriteContactSettingDword(hMeta, META_PROTO, "RealIP", dcws->value.dVal);
else
DBDeleteContactSetting(hMeta, META_PROTO, "RealIP");
} else
// copy ListeningTo
if (!strcmp(dcws->szSetting, "ListeningTo")) {
switch(dcws->value.type) {
case DBVT_ASCIIZ:
DBWriteContactSettingString(hMeta, META_PROTO, "ListeningTo", dcws->value.pszVal);
break;
case DBVT_UTF8:
DBWriteContactSettingStringUtf(hMeta, META_PROTO, "ListeningTo", dcws->value.pszVal);
break;
case DBVT_WCHAR:
DBWriteContactSettingWString(hMeta, META_PROTO, "ListeningTo", dcws->value.pwszVal);
break;
case DBVT_DELETED:
DBDeleteContactSetting(hMeta, META_PROTO, "ListeningTo");
break;
}
} else
if (!strcmp(dcws->szSetting, "Nick") && !dcws->value.type == DBVT_DELETED) {
DBVARIANT dbv;
HANDLE most_online;
// subcontact nick has changed - update metacontact
strcpy(buffer, "Nick");
strcat(buffer, _itoa(contact_number, buffer2, 10));
MyDBWriteContactSetting(hMeta, META_PROTO, buffer, &dcws->value);
if (MyDBGetContactSetting((HANDLE)wParam, "CList", "MyHandle", &dbv)) {
strcpy(buffer, "CListName");
strcat(buffer, _itoa(contact_number, buffer2, 10));
MyDBWriteContactSetting(hMeta, META_PROTO, buffer, &dcws->value);
} else {
DBFreeVariant(&dbv);
}
// copy nick to metacontact, if it's the most online
most_online = Meta_GetMostOnline(hMeta);
Meta_CopyContactNick(hMeta, most_online);
return 0;
} else
if (!strcmp(dcws->szSetting, "IdleTS")) {
if (dcws->value.type == DBVT_DWORD)
DBWriteContactSettingDword(hMeta, META_PROTO, "IdleTS", dcws->value.dVal);
else if (dcws->value.type == DBVT_DELETED)
DBWriteContactSettingDword(hMeta, META_PROTO, "IdleTS", 0);
} else
if (!strcmp(dcws->szSetting, "LogonTS")) {
if (dcws->value.type == DBVT_DWORD)
DBWriteContactSettingDword(hMeta, META_PROTO, "LogonTS", dcws->value.dVal);
else if (dcws->value.type == DBVT_DELETED)
DBWriteContactSettingDword(hMeta, META_PROTO, "LogonTS", 0);
} else
if (!strcmp(dcws->szModule, "CList") && !strcmp(dcws->szSetting, "MyHandle")) {
HANDLE most_online;
if (dcws->value.type == DBVT_DELETED) {
DBVARIANT dbv;
char *proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
strcpy(buffer, "CListName");
strcat(buffer, _itoa(contact_number, buffer2, 10));
if (proto && !MyDBGetContactSetting((HANDLE)wParam, proto, "Nick", &dbv)) {
MyDBWriteContactSetting(hMeta, META_PROTO, buffer, &dbv);
DBFreeVariant(&dbv);
} else {
DBDeleteContactSetting(hMeta, META_PROTO, buffer);
}
} else {
// subcontact clist displayname has changed - update metacontact
strcpy(buffer, "CListName");
strcat(buffer, _itoa(contact_number, buffer2, 10));
MyDBWriteContactSetting(hMeta, META_PROTO, buffer, &dcws->value);
}
// copy nick to metacontact, if it's the most online
most_online = Meta_GetMostOnline(hMeta);
Meta_CopyContactNick(hMeta, most_online);
return 0;
} else
if (!strcmp(dcws->szSetting, "Status") && !dcws->value.type == DBVT_DELETED) {
// subcontact changing status
// update subcontact status setting
strcpy(buffer, "Status");
strcat(buffer, _itoa(contact_number, buffer2, 10));
DBWriteContactSettingWord(hMeta, META_PROTO, buffer, dcws->value.wVal);
strcpy(buffer, "StatusString");
strcat(buffer, _itoa(contact_number, buffer2, 10));
Meta_GetStatusString(dcws->value.wVal, buffer2, 512);
DBWriteContactSettingString(hMeta, META_PROTO, buffer, buffer2);
// if the contact was forced, unforce it (which updates status)
if ((HANDLE)DBGetContactSettingDword(hMeta, META_PROTO, "ForceSend", 0) == (HANDLE)wParam) {
MetaAPI_UnforceSendContact((WPARAM)hMeta, 0);
} else {
// set status to that of most online contact
most_online = Meta_GetMostOnline(hMeta);
Meta_CopyContactNick(hMeta, most_online);
Meta_FixStatus(hMeta);
Meta_CopyData(hMeta);
}
// most online contact with avatar support might have changed - update avatar
most_online = Meta_GetMostOnlineSupporting(hMeta, PFLAGNUM_4, PF4_AVATARS);
if (most_online) {
PROTO_AVATAR_INFORMATIONT AI;
AI.cbSize = sizeof(AI);
AI.hContact = hMeta;
AI.format = PA_FORMAT_UNKNOWN;
_tcscpy(AI.filename, _T("X"));
if ((int)CallProtoService(META_PROTO, PS_GETAVATARINFOT, 0, (LPARAM)&AI) == GAIR_SUCCESS)
DBWriteContactSettingTString(hMeta, "ContactPhoto", "File",AI.filename);
}
} else
if (strcmp(dcws->szSetting, "XStatusId") == 0 || strcmp(dcws->szSetting, "XStatusMsg") == 0 || strcmp(dcws->szSetting, "XStatusName") == 0 || strcmp(dcws->szSetting, "StatusMsg") == 0) {
Meta_CopyData(hMeta);
} else
if (strcmp(dcws->szSetting, "MirVer") == 0) {
Meta_CopyData(hMeta);
} else
if (!meta_group_hack_disabled && !strcmp(dcws->szModule, "CList") && !strcmp(dcws->szSetting, "Hidden")) {
if ((dcws->value.type == DBVT_DELETED || DBGetContactSettingByte((HANDLE)wParam, "CList", "Hidden", 0) == 0)
&& DBGetContactSettingByte((HANDLE)wParam, META_PROTO, "Hidden", 0) == 1)
{
// a subcontact we hid (e.g. jabber) has been unhidden - hide it again :(
DBWriteContactSettingByte((HANDLE)wParam, "CList", "Hidden", 1);
}
}
}
return 0;
}
int Meta_ContactDeleted(WPARAM wParam, LPARAM lParam) {
HANDLE hMeta;
// is a subcontact - update meta contact
hMeta = (HANDLE)DBGetContactSettingDword((HANDLE)wParam, META_PROTO, "Handle", 0);
if (hMeta) {
Meta_RemoveContactNumber(hMeta, DBGetContactSettingDword((HANDLE)wParam, META_PROTO, "ContactNumber", -1));
NotifyEventHooks(hSubcontactsChanged, (WPARAM)hMeta, 0);
return 0;
} else {
// not a subcontact - is it a metacontact?
int num_contacts = DBGetContactSettingDword((HANDLE)wParam, META_PROTO, "NumContacts", 0);
int i;
HANDLE hContact;
if (num_contacts) NotifyEventHooks(hSubcontactsChanged, (WPARAM)wParam, 0);
// remove & restore all subcontacts
for (i = 0; i < num_contacts; i++) {
hContact = Meta_GetContactHandle((HANDLE)wParam, i);
if (hContact && (HANDLE)DBGetContactSettingDword(hContact, META_PROTO, "Handle", 0) == (HANDLE)wParam) {
if (DBGetContactSettingByte(hContact, META_PROTO, "IsSubcontact", 0) == 1)
DBDeleteContactSetting(hContact,META_PROTO,"IsSubcontact");
DBDeleteContactSetting(hContact,META_PROTO,META_LINK);
DBDeleteContactSetting(hContact,META_PROTO,"Handle");
DBDeleteContactSetting(hContact,META_PROTO,"ContactNumber");
Meta_RestoreGroup(hContact);
DBDeleteContactSetting(hContact,META_PROTO,"OldCListGroup");
CallService(MS_PROTO_REMOVEFROMCONTACT, (WPARAM)hContact, (LPARAM)META_FILTER);
// stop ignoring, if we were
if (options.suppress_status)
CallService(MS_IGNORE_UNIGNORE, (WPARAM)hContact, (WPARAM)IGNOREEVENT_USERONLINE);
}
}
return 0;
}
return 0;
}
/** Call when we want to send a user is typing message
*
* @param wParam \c HANDLE to the contact that we are typing to
* @param lParam either PROTOTYPE_SELFTYPING_ON or PROTOTYPE_SELFTYPING_OFF
*/
INT_PTR Meta_UserIsTyping(WPARAM wParam, LPARAM lParam)
{
char *proto;
char buff[512];
if (DBGetContactSettingDword((HANDLE)wParam,META_PROTO,META_ID,(DWORD)-1) == (DWORD)-1)
{
// This is a simple contact, let through the stack of protocols
return 0;
}
else
{
// forward to sending protocol, if supported
HANDLE most_online = Meta_GetMostOnline((HANDLE)wParam);
Meta_CopyContactNick((HANDLE)wParam, most_online);
if (!most_online) return 0;
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
if (proto) {
strncpy(buff, proto, 512);
strncpy(buff + strlen(proto), PSS_USERISTYPING, 512 - strlen(proto));
if (ServiceExists(buff)) {
CallService(buff, (WPARAM)most_online, (LPARAM)lParam);
}
}
}
return 0;
}
/** Call when we want to receive a user is typing message
*
* @param wParam \c HANDLE to the contact that is typing or not
* @param lParam either PROTOTYPE_SELFTYPING_ON or PROTOTYPE_SELFTYPING_OFF
*/
int Meta_ContactIsTyping(WPARAM wParam, LPARAM lParam)
{
HANDLE hMeta;
if ((hMeta = (HANDLE)DBGetContactSettingDword((HANDLE)wParam,META_PROTO,"Handle",(DWORD)0)) != 0
// check metacontacts enabled
&& Meta_IsEnabled()
)
{ // This contact is attached to a MetaContact.
if (!options.subcontact_windows) { // we don't want clicking on the clist notification icon to open the metacontact message window
// try to remove any clist events we added for subcontact
CallServiceSync(MS_CLIST_REMOVEEVENT, wParam, (LPARAM) 1);
CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hMeta, lParam);
// stop processing of event
return 1;
}
}
return 0;
}
/** Called when user info is about to be shown
*
* Returns 1 to stop event processing and opens page for metacontact default contact (returning 1 to stop it doesn't work!)
*
*/
int Meta_UserInfo(WPARAM wParam, LPARAM lParam)
{
DWORD default_contact_number = DBGetContactSettingDword((HANDLE)lParam, META_PROTO, "Default", (DWORD)-1);
if (default_contact_number == -1) // not a meta contact
return 0;
CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)Meta_GetContactHandle((HANDLE)lParam, default_contact_number), 0);
return 1;
}
// handle message window api ver 0.0.0.1+ events - record window open/close status for subcontacts, so we know whether to
// let received messages through and add db history to metacontact, or vice versa
int Meta_MessageWindowEvent(WPARAM wParam, LPARAM lParam) {
MessageWindowEventData *mwed = (MessageWindowEventData *)lParam;
HANDLE hMeta = 0;
message_window_api_enabled = TRUE;
if ((hMeta = (HANDLE)DBGetContactSettingDword(mwed->hContact, META_PROTO, "Handle", 0)) != 0
|| DBGetContactSettingDword(mwed->hContact, META_PROTO, META_ID, (DWORD)-1) != (DWORD)-1)
{
// contact is subcontact of metacontact, or an actual metacontact - record whether window is open or closed
if (mwed->uType == MSG_WINDOW_EVT_OPEN || mwed->uType == MSG_WINDOW_EVT_OPENING) {
DBWriteContactSettingByte(mwed->hContact, META_PROTO, "WindowOpen", 1);
if (hMeta) { // subcontact window opened - remove clist events we added for metacontact
while(!CallService(MS_CLIST_REMOVEEVENT, (WPARAM)hMeta, (LPARAM)mwed->hContact));
}
} else if (mwed->uType == MSG_WINDOW_EVT_CLOSE || mwed->uType == MSG_WINDOW_EVT_CLOSING) {
DBWriteContactSettingByte(mwed->hContact, META_PROTO, "WindowOpen", 0);
if (!hMeta) { // hMeta is 0 for metacontact (sorry)
DWORD saved_def;
MetaAPI_UnforceSendContact((WPARAM)mwed->hContact, 0);
// restore saved default contact
if (options.set_default_on_recv) {
saved_def = DBGetContactSettingDword(mwed->hContact, META_PROTO, "SavedDefault", -1);
if (options.temp_default && saved_def != (DWORD)-1) {
DBWriteContactSettingDword(mwed->hContact, META_PROTO, "Default", saved_def);
DBWriteContactSettingDword(mwed->hContact, META_PROTO, "SavedDefault", (DWORD)-1);
NotifyEventHooks(hEventDefaultChanged, (WPARAM)mwed->hContact, (LPARAM)Meta_GetContactHandle(hMeta, saved_def)); // nick set in event handler
}
}
}
}
}
return 0;
}
int Meta_ClistDoubleClicked(WPARAM wParam, LPARAM lParam)
{
if (DBGetContactSettingDword((HANDLE)wParam,META_PROTO,"Default",(WORD)-1) == (WORD)-1)
{
// This is a simple contact
return 0;
}
else
{
// -1 indicates no specific capability but respect 'ForceDefault'
HANDLE most_online = Meta_GetMostOnlineSupporting((HANDLE)wParam, PFLAGNUM_1, -1);
//DBEVENTINFO dbei;
char *proto;
char buffer[512];
int caps;
if (!most_online)
return 0;
if (options.subcontact_windows) {
if (lParam) {
// contact from incoming message in lParam via (at this point) clist message event
CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)lParam, 0);
} else {
// simulate double click on most_online contact and stop event processing
CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)most_online, 0);
}
return 1;
} else {
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
if (proto) {
strcpy(buffer, proto);
strcat(buffer, PS_GETCAPS);
// get the contacts messaging capabilities
caps = CallService(buffer, (WPARAM)PFLAGNUM_1, 0);
if ((caps & PF1_IMSEND) || (caps & PF1_CHAT) || (proto && strcmp(proto, "IRC") == 0))
// let event process normally
return 0;
else {
// simulate double click on most_online contact and stop event processing
CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)most_online, 0);
return 1;
}
} else
return 0;
}
}
return 0;
}
INT_PTR Meta_ClistMessageEventClicked(WPARAM wParam, LPARAM lParam) {
HANDLE hContact = ((CLISTEVENT *)lParam)->hContact;
// hdbevent contains the id of the subcontact
return Meta_ClistDoubleClicked((WPARAM)hContact, (LPARAM)((CLISTEVENT *)lParam)->hDbEvent);
}
int NudgeRecieved(WPARAM wParam, LPARAM lParam) {
/*
// already being forwarded by someone
HANDLE hMeta = (HANDLE)DBGetContactSettingDword((HANDLE)wParam,META_PROTO, "Handle", (DWORD)0);
if (hMeta)
NotifyEventHooks(hEventNudge, (WPARAM)hMeta, 0);
*/
return 0;
}
/** Called when all the plugin are loaded into Miranda.
*
* Initializes the 4 menus present in the context-menu
* and the initial value of nextMetaID
*/
int Meta_ModulesLoaded(WPARAM wParam, LPARAM lParam)
{
char buffer[512], buffer2[512], buffer3[512];
int i;
if (ServiceExists(MS_MSG_GETWINDOWAPI))
message_window_api_enabled = TRUE;
// disable group hack for older nicer versions without the fix
if (ServiceExists(MS_CLUI_GETVERSION)) {
char *version = (char *)CallService(MS_CLUI_GETVERSION, 0, 0);
if (version && strlen(version) >= strlen("CList Nicer+") && strncmp(version, "CList Nicer+", strlen("CList Nicer+")) == 0)
meta_group_hack_disabled = TRUE;
}
// for database editor++ ver 3+
if (ServiceExists("DBEditorpp/RegisterSingleModule"))
CallService("DBEditorpp/RegisterSingleModule",(WPARAM)META_PROTO,0);
HookEvent(ME_CLIST_PREBUILDCONTACTMENU, Meta_ModifyMenu);
HookEvent(ME_CLIST_DOUBLECLICKED, Meta_ClistDoubleClicked );
////////////////////////////////////////////////////////////////////////////
CLISTMENUITEM mi = { sizeof(mi) };
mi.flags = CMIM_ALL;
// main menu item
mi.pszName = "Toggle MetaContacts Off";
mi.pszService = "MetaContacts/OnOff";
mi.position = 500010000;
hMenuOnOff = Menu_AddMainMenuItem(&mi);
// contact menu items
mi.position = -200010;
mi.pszName = "Convert to MetaContact";
mi.pszService = "MetaContacts/Convert";
hMenuConvert = Menu_AddContactMenuItem(&mi);
mi.position = -200009;
mi.pszName = "Add to existing MetaContact...";
mi.pszService = "MetaContacts/AddTo";
hMenuAdd = Menu_AddContactMenuItem(&mi);
mi.position = -200010;
mi.pszName = "Edit MetaContact...";
mi.pszService = "MetaContacts/Edit";
hMenuEdit = Menu_AddContactMenuItem(&mi);
mi.position = -200009;
mi.pszName = "Set as MetaContact default";
mi.pszService = "MetaContacts/Default";
hMenuDefault = Menu_AddContactMenuItem(&mi);
mi.position = -200008;
mi.pszName = "Delete MetaContact";
mi.pszService = "MetaContacts/Delete";
hMenuDelete = Menu_AddContactMenuItem(&mi);
//mi.pszName = "Force Default";
//mi.pszService = "MetaContacts/ForceDefault";
//hMenuForceDefault = Menu_AddContactMenuItem(&mi);
mi.flags |= CMIF_HIDDEN;
mi.pszContactOwner = META_PROTO;
mi.position = -99000;
for (i = 0; i < MAX_CONTACTS; i++) {
mi.position--;
strcpy(buffer3, (char *)Translate("Context"));
strcat(buffer3, _itoa(i, buffer2, 10));
mi.pszName = buffer3;
strcpy(buffer, "MetaContacts/MenuFunc");
strcat(buffer, _itoa(i, buffer2, 10));
mi.pszService= buffer;
hMenuContact[i] = Menu_AddContactMenuItem(&mi);
}
nextMetaID = DBGetContactSettingDword(NULL,META_PROTO,"NextMetaID",(DWORD)0);
// attemp to subsume userinfo...(returning 1 does not prevent dialog - so disabled)
//hHooks[] = (HANDLE)HookEvent(ME_USERINFO_INITIALISE, Meta_UserInfo);
// loop and copy data from subcontacts
if (options.copydata) {
HANDLE hContact = db_find_first();
int meta_id;
while ( hContact != NULL ) {
if ((meta_id = DBGetContactSettingDword(hContact,META_PROTO,META_ID,(DWORD)-1))!=(DWORD)-1) {
Meta_CopyData(hContact);
}
hContact = db_find_next(hContact);
}
}
Meta_HideLinkedContacts();
InitIcons();
if (!Meta_IsEnabled())
{
// modify main menu item
mi.flags = CMIM_NAME;
mi.pszName = "Toggle MetaContacts On";
CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuOnOff, (LPARAM)&mi);
Meta_HideMetaContacts(TRUE);
} else {
Meta_SuppressStatus(options.suppress_status);
}
// hook srmm window close/open events - message api ver 0.0.0.1+
if (HookEvent(ME_MSG_WINDOWEVENT, Meta_MessageWindowEvent))
message_window_api_enabled = TRUE;
// hook protocol nudge events to forward to subcontacts
{
int i, numberOfProtocols,ret;
char str[MAXMODULELABELLENGTH + 10];
HANDLE hNudgeEvent = NULL;
PROTOACCOUNT ** ppProtocolDescriptors;
ret = ProtoEnumAccounts(&numberOfProtocols, &ppProtocolDescriptors);
if (ret == 0)
{
for (i = 0; i < numberOfProtocols ; i++)
{
if (strcmp(ppProtocolDescriptors[i]->szModuleName, META_PROTO)) {
sprintf(str,"%s/Nudge",ppProtocolDescriptors[i]->szModuleName);
HookEvent(str, NudgeRecieved);
}
}
}
}
return 0;
}
static VOID CALLBACK sttMenuThread( PVOID param )
{
HMENU hMenu;
TPMPARAMS tpmp;
BOOL menuRet;
hMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)param, 0);
ZeroMemory(&tpmp, sizeof(tpmp));
tpmp.cbSize = sizeof(tpmp);
menuRet = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, menuMousePoint.x, menuMousePoint.y, (HWND)CallService(MS_CLUI_GETHWND, 0, 0), &tpmp);
CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(menuRet), MPCF_CONTACTMENU), (LPARAM)param);
DestroyMenu(hMenu);
}
INT_PTR Meta_ContactMenuFunc(WPARAM wParam, LPARAM lParam) {
HANDLE hContact;
hContact = Meta_GetContactHandle((HANDLE)wParam, (int)lParam);
if (options.menu_function == FT_MSG) {
// open message window if protocol supports message sending or chat, else simulate double click
int caps;
char *proto;
char buffer[512];
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
if (proto) {
strcpy(buffer, proto);
strcat(buffer, PS_GETCAPS);
caps = CallService(buffer, (WPARAM)PFLAGNUM_1, 0);
if ((caps & PF1_IMSEND) || (caps & PF1_CHAT) || (proto && strcmp(proto, "IRC") == 0)) {
// set default contact for sending/status and open message window
DBWriteContactSettingDword((HANDLE)wParam, META_PROTO, "Default", (DWORD)(int)lParam);
NotifyEventHooks(hEventDefaultChanged, wParam, (LPARAM)hContact);
CallService(MS_MSG_SENDMESSAGE, wParam, 0);
} else
// protocol does not support messaging - simulate double click
CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)hContact, 0);
} else
// protocol does not support messaging - simulate double click
CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM)hContact, 0);
} else if (options.menu_function == FT_MENU) {
// show contact's context menu
CallFunctionAsync(sttMenuThread, hContact);
} else if (options.menu_function == FT_INFO) {
// show user info for subcontact
CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)hContact, 0);
}
return 0;
}
////////////////////
// file transfer support - mostly not required, since subcontacts do the receiving
////////////////////
INT_PTR Meta_FileSend(WPARAM wParam, LPARAM lParam)
{
CCSDATA *ccs = (CCSDATA *) lParam;
char *proto = 0;
DWORD default_contact_number;
if ((default_contact_number = DBGetContactSettingDword(ccs->hContact,META_PROTO,"Default",(DWORD)-1)) == (DWORD)-1)
{
// This is a simple contact
// (this should normally not happen, since linked contacts do not appear on the list.)
//PUShowMessage("meta has no default", SM_NOTIFY);
return 0;
}
else
{
HANDLE most_online;
//DBEVENTINFO dbei;
//char szServiceName[100];
most_online = Meta_GetMostOnlineSupporting(ccs->hContact, PFLAGNUM_1, PF1_FILESEND);
if (!most_online) {
//PUShowMessage("no most online for ft", SM_NOTIFY);
return 0;
}
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
//Meta_CopyContactNick(ccs->hContact, most_online, proto);
if (proto) {
//ccs->hContact = most_online;
//Meta_SetNick(proto);
// don't check for existence of service - 'accounts' based protos don't have them!
//_snprintf(szServiceName, sizeof(szServiceName), "%s%s", proto, PSS_FILE);
//if (ServiceExists(szServiceName)) {
// PUShowMessage("sending to subcontact", SM_NOTIFY);
return (int)(CallContactService(most_online, PSS_FILE, ccs->wParam, ccs->lParam));
//} else
// PUShowMessage("no service", SM_NOTIFY);
} //else
//PUShowMessage("no proto for subcontact", SM_NOTIFY);
}
return 0; // fail
}
INT_PTR Meta_GetAwayMsg(WPARAM wParam, LPARAM lParam) {
CCSDATA *ccs = (CCSDATA *) lParam;
char *proto = 0;
DWORD default_contact_number;
if ((default_contact_number = DBGetContactSettingDword(ccs->hContact,META_PROTO,"Default",(DWORD)-1)) == (DWORD)-1)
{
// This is a simple contact
// (this should normally not happen, since linked contacts do not appear on the list.)
return 0;
}
else
{
HANDLE most_online;
most_online = Meta_GetMostOnlineSupporting(ccs->hContact, PFLAGNUM_1, PF1_MODEMSGRECV);
if (!most_online)
return 0;
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
if (!proto) return 0;
//Meta_CopyContactNick(ccs->hContact, most_online, proto);
ccs->hContact = most_online;
//Meta_SetNick(proto);
return (int)(CallContactService(ccs->hContact, PSS_GETAWAYMSG, ccs->wParam, ccs->lParam));
}
return 0; // fail
}
INT_PTR Meta_GetAvatarInfo(WPARAM wParam, LPARAM lParam) {
PROTO_AVATAR_INFORMATIONT *AI = (PROTO_AVATAR_INFORMATIONT *) lParam;
char *proto = 0;
DWORD default_contact_number;
if ((default_contact_number = DBGetContactSettingDword(AI->hContact,META_PROTO,"Default",(DWORD)-1)) == (DWORD)-1)
{
// This is a simple contact
// (this should normally not happen, since linked contacts do not appear on the list.)
return 0;
}
else
{
HANDLE hSub, hMeta;
char szServiceName[100];
int result;
hMeta = AI->hContact;
hSub = Meta_GetMostOnlineSupporting(AI->hContact, PFLAGNUM_4, PF4_AVATARS);
if (!hSub)
return GAIR_NOAVATAR;
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hSub, 0);
if (!proto) return GAIR_NOAVATAR;
AI->hContact = hSub;
mir_snprintf(szServiceName, sizeof(szServiceName), "%s%s", proto, PS_GETAVATARINFOT);
result = CallService(szServiceName, wParam, lParam);
AI->hContact = hMeta;
if (result != CALLSERVICE_NOTFOUND) return result;
}
return GAIR_NOAVATAR; // fail
}
INT_PTR Meta_GetInfo(WPARAM wParam, LPARAM lParam) {
CCSDATA *ccs = (CCSDATA *) lParam;
char *proto = 0;
DWORD default_contact_number;
if ((default_contact_number = DBGetContactSettingDword(ccs->hContact,META_PROTO,"Default",(DWORD)-1)) == (DWORD)-1)
{
// This is a simple contact
// (this should normally not happen, since linked contacts do not appear on the list.)
return 0;
}
else
{
HANDLE most_online;
PROTO_AVATAR_INFORMATIONT AI;
char szServiceName[100];
most_online = Meta_GetMostOnlineSupporting(ccs->hContact, PFLAGNUM_4, PF4_AVATARS);
if (!most_online)
return 0;
proto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)most_online, 0);
if (!proto) return 0;
AI.cbSize = sizeof(AI);
AI.hContact = ccs->hContact;
AI.format = PA_FORMAT_UNKNOWN;
_tcscpy(AI.filename, _T("X"));
if ((int)CallProtoService(META_PROTO, PS_GETAVATARINFOT, 0, (LPARAM)&AI) == GAIR_SUCCESS)
DBWriteContactSettingTString(ccs->hContact, "ContactPhoto", "File",AI.filename);
most_online = Meta_GetMostOnline(ccs->hContact);
Meta_CopyContactNick(ccs->hContact, most_online);
if (!most_online)
return 0;
//Meta_CopyContactNick(ccs->hContact, most_online, proto);
ccs->hContact = most_online;
//Meta_SetNick(proto);
_snprintf(szServiceName, sizeof(szServiceName), "%s%s", proto, PSS_GETINFO);
if (ServiceExists(szServiceName)) {
strncpy(szServiceName, PSS_GETINFO, sizeof(szServiceName));
return (int)(CallContactService(ccs->hContact, szServiceName, ccs->wParam, ccs->lParam));
}
}
return 0; // fail
}
int Meta_OptInit(WPARAM wParam, LPARAM lParam)
{
OPTIONSDIALOGPAGE odp = { 0 };
odp.cbSize = sizeof(odp);
odp.position = -790000000;
odp.hInstance = hInstance;
odp.flags = ODPF_BOLDGROUPS;
odp.pszTemplate = MAKEINTRESOURCE(IDD_OPTIONS);
odp.pszTitle = LPGEN("MetaContacts");
odp.pszGroup = LPGEN("Contact List");
odp.pszTab = LPGEN("General");
odp.pfnDlgProc = DlgProcOpts;
Options_AddPage(wParam, &odp);
odp.pszTemplate = MAKEINTRESOURCE(IDD_PRIORITIES);
odp.pszTab = LPGEN("Priorities");
odp.pfnDlgProc = DlgProcOptsPriorities;
Options_AddPage(wParam, &odp);
odp.pszTemplate = MAKEINTRESOURCE(IDD_HISTORY);
odp.pszTab = LPGEN("History");
odp.pfnDlgProc = DlgProcOpts;
Options_AddPage(wParam, &odp);
return 0;
}
int Meta_CallMostOnline(WPARAM wParam, LPARAM lParam) {
HANDLE most_online_im = Meta_GetMostOnline((HANDLE)wParam);
// fix nick
Meta_CopyContactNick((HANDLE)wParam, most_online_im);
// fix status
Meta_FixStatus((HANDLE)wParam);
// copy all other data
Meta_CopyData((HANDLE) wParam);
return 0;
}
INT_PTR Meta_OnOff(WPARAM wParam, LPARAM lParam) {
CLISTMENUITEM mi;
mi.cbSize = sizeof(CLISTMENUITEM);
// just write to db - the rest is handled in the Meta_SettingChanged function
if (DBGetContactSettingByte(0, META_PROTO, "Enabled", 1)) {
DBWriteContactSettingByte(0, META_PROTO, "Enabled", 0);
// modify main mi item
mi.flags = CMIM_NAME | CMIM_ICON;
mi.hIcon = LoadIconEx(I_MENU);
mi.pszName = "Toggle MetaContacts On";
CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuOnOff, (LPARAM)&mi);
} else {
DBWriteContactSettingByte(0, META_PROTO, "Enabled", 1);
// modify main mi item
mi.flags = CMIM_NAME | CMIM_ICON;
mi.hIcon = LoadIconEx(I_MENUOFF);
mi.pszName = "Toggle MetaContacts Off";
CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuOnOff, (LPARAM)&mi);
}
ReleaseIconEx(mi.hIcon);
return 0;
}
int Meta_PreShutdown(WPARAM wParam, LPARAM lParam) {
//MessageBox(0, "Preshutdown called", "MC", MB_OK);
Meta_SetStatus((WPARAM)ID_STATUS_OFFLINE, 0);
Meta_UnhideLinkedContacts();
Meta_SuppressStatus(FALSE);
//MessageBox(0, "Status is OFFLINE", "MC", MB_OK);
//MessageBox(0, "Preshutdown complete", "MC", MB_OK);
if (setStatusTimerId) KillTimer(0, setStatusTimerId);
return 0;
}
int Meta_OkToExit(WPARAM wParam, LPARAM lParam) {
Meta_SetStatus((WPARAM)ID_STATUS_OFFLINE, 0);
return 0;
}
int Meta_OnIdleChanged(WPARAM wParam, LPARAM lParam) {
return 0;
}
/** Initializes all services provided by the plugin
*
* Creates every function and hooks the event desired.
*/
void Meta_InitServices()
{
previousMode = mcStatus = ID_STATUS_OFFLINE;
CreateServiceFunction("MetaContacts/Convert",Meta_Convert);
CreateServiceFunction("MetaContacts/AddTo",Meta_AddTo);
CreateServiceFunction("MetaContacts/Edit",Meta_Edit);
CreateServiceFunction("MetaContacts/Delete",Meta_Delete);
CreateServiceFunction("MetaContacts/Default",Meta_Default);
CreateServiceFunction("MetaContacts/ForceDefault",Meta_ForceDefault);
// hidden contact menu items...ho hum
CreateServiceFunction("MetaContacts/MenuFunc0",MenuFunc0);
CreateServiceFunction("MetaContacts/MenuFunc1",MenuFunc1);
CreateServiceFunction("MetaContacts/MenuFunc2",MenuFunc2);
CreateServiceFunction("MetaContacts/MenuFunc3",MenuFunc3);
CreateServiceFunction("MetaContacts/MenuFunc4",MenuFunc4);
CreateServiceFunction("MetaContacts/MenuFunc5",MenuFunc5);
CreateServiceFunction("MetaContacts/MenuFunc6",MenuFunc6);
CreateServiceFunction("MetaContacts/MenuFunc7",MenuFunc7);
CreateServiceFunction("MetaContacts/MenuFunc8",MenuFunc8);
CreateServiceFunction("MetaContacts/MenuFunc9",MenuFunc9);
CreateServiceFunction("MetaContacts/MenuFunc10",MenuFunc10);
CreateServiceFunction("MetaContacts/MenuFunc11",MenuFunc11);
CreateServiceFunction("MetaContacts/MenuFunc12",MenuFunc12);
CreateServiceFunction("MetaContacts/MenuFunc13",MenuFunc13);
CreateServiceFunction("MetaContacts/MenuFunc14",MenuFunc14);
CreateServiceFunction("MetaContacts/MenuFunc15",MenuFunc15);
CreateServiceFunction("MetaContacts/MenuFunc16",MenuFunc16);
CreateServiceFunction("MetaContacts/MenuFunc17",MenuFunc17);
CreateServiceFunction("MetaContacts/MenuFunc18",MenuFunc18);
CreateServiceFunction("MetaContacts/MenuFunc19",MenuFunc19);
CreateProtoServiceFunction(META_PROTO,PS_GETCAPS,Meta_GetCaps);
CreateProtoServiceFunction(META_PROTO,PS_GETNAME,Meta_GetName);
CreateProtoServiceFunction(META_PROTO,PS_LOADICON,Meta_LoadIcon);
CreateProtoServiceFunction(META_PROTO,PS_SETSTATUS,Meta_SetStatus);
CreateProtoServiceFunction(META_PROTO,PS_GETSTATUS,Meta_GetStatus);
CreateProtoServiceFunction(META_PROTO,PSS_MESSAGE,Meta_SendMessage);
CreateProtoServiceFunction(META_PROTO,PSS_MESSAGE"W",Meta_SendMessage); // unicode send (same send func as above line, checks for PREF_UNICODE)
CreateProtoServiceFunction(META_PROTO,PSS_USERISTYPING,Meta_UserIsTyping );
CreateProtoServiceFunction(META_PROTO,PSR_MESSAGE,Meta_RecvMessage);
// file recv is done by subcontacts
CreateProtoServiceFunction(META_PROTO,PSS_FILE, Meta_FileSend);
CreateProtoServiceFunction(META_PROTO,PSS_GETAWAYMSG,Meta_GetAwayMsg);
CreateProtoServiceFunction(META_PROTO,PS_GETAVATARINFOT,Meta_GetAvatarInfo);
CreateProtoServiceFunction(META_PROTO,PSS_GETINFO,Meta_GetInfo);
CreateProtoServiceFunction(META_FILTER,PSR_MESSAGE,MetaFilter_RecvMessage);
CreateProtoServiceFunction(META_FILTER,PSS_MESSAGE,MetaFilter_SendMessage);
CreateProtoServiceFunction(META_FILTER,PSS_MESSAGE"W",MetaFilter_SendMessage);
// API services and events
CreateServiceFunction(MS_MC_GETMETACONTACT, MetaAPI_GetMeta);
CreateServiceFunction(MS_MC_GETDEFAULTCONTACT, MetaAPI_GetDefault);
CreateServiceFunction(MS_MC_GETDEFAULTCONTACTNUM, MetaAPI_GetDefaultNum);
CreateServiceFunction(MS_MC_GETMOSTONLINECONTACT, MetaAPI_GetMostOnline);
CreateServiceFunction(MS_MC_GETNUMCONTACTS, MetaAPI_GetNumContacts);
CreateServiceFunction(MS_MC_GETSUBCONTACT, MetaAPI_GetContact);
CreateServiceFunction(MS_MC_SETDEFAULTCONTACTNUM, MetaAPI_SetDefaultContactNum);
CreateServiceFunction(MS_MC_SETDEFAULTCONTACT, MetaAPI_SetDefaultContact);
CreateServiceFunction(MS_MC_FORCESENDCONTACTNUM, MetaAPI_ForceSendContactNum);
CreateServiceFunction(MS_MC_FORCESENDCONTACT, MetaAPI_ForceSendContact);
CreateServiceFunction(MS_MC_UNFORCESENDCONTACT, MetaAPI_UnforceSendContact);
CreateServiceFunction(MS_MC_GETPROTOCOLNAME, MetaAPI_GetProtoName);
CreateServiceFunction(MS_MC_GETFORCESTATE, MetaAPI_GetForceState);
CreateServiceFunction(MS_MC_CONVERTTOMETA, MetaAPI_ConvertToMeta);
CreateServiceFunction(MS_MC_ADDTOMETA, MetaAPI_AddToMeta);
CreateServiceFunction(MS_MC_REMOVEFROMMETA, MetaAPI_RemoveFromMeta);
CreateServiceFunction(MS_MC_DISABLEHIDDENGROUP, MetaAPI_DisableHiddenGroup);
CreateServiceFunction("MetaContacts/OnOff", Meta_OnOff);
CreateServiceFunction("MetaContacts/CListMessageEvent", Meta_ClistMessageEventClicked);
CreateProtoServiceFunction(META_PROTO, "/SendNudge", Meta_SendNudge);
// create our hookable events
hEventDefaultChanged = CreateHookableEvent(ME_MC_DEFAULTTCHANGED);
hEventForceSend = CreateHookableEvent(ME_MC_FORCESEND);
hEventUnforceSend = CreateHookableEvent(ME_MC_UNFORCESEND);
hSubcontactsChanged = CreateHookableEvent(ME_MC_SUBCONTACTSCHANGED);
// hook other module events we need
HookEvent(ME_PROTO_ACK, Meta_HandleACK);
HookEvent(ME_DB_CONTACT_SETTINGCHANGED, Meta_SettingChanged);
HookEvent(ME_SYSTEM_MODULESLOADED, Meta_ModulesLoaded);
HookEvent(ME_PROTO_CONTACTISTYPING, Meta_ContactIsTyping);
HookEvent(ME_DB_CONTACT_DELETED, Meta_ContactDeleted);
HookEvent(ME_OPT_INITIALISE, Meta_OptInit );
HookEvent(ME_SYSTEM_PRESHUTDOWN, Meta_PreShutdown);
HookEvent(ME_SYSTEM_OKTOEXIT, Meta_OkToExit);
// hook our own events, used to call Meta_GetMostOnline which sets nick for metacontact
HookEvent(ME_MC_DEFAULTTCHANGED, Meta_CallMostOnline );
HookEvent(ME_MC_FORCESEND, Meta_CallMostOnline );
HookEvent(ME_MC_UNFORCESEND, Meta_CallMostOnline );
// redirect nudge events
hEventNudge = CreateHookableEvent(META_PROTO "/Nudge");
}
//! Unregister all hooks and services from Miranda
void Meta_CloseHandles()
{
// destroy our hookable events
DestroyHookableEvent(hEventDefaultChanged);
DestroyHookableEvent(hEventForceSend);
DestroyHookableEvent(hEventUnforceSend);
DestroyHookableEvent(hSubcontactsChanged);
DestroyHookableEvent(hEventNudge);
}