// ---------------------------------------------------------------------------80
// ICQ plugin for Miranda Instant Messenger
// ________________________________________
//
// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede
// Copyright © 2001-2002 Jon Keating, Richard Hughes
// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater
// Copyright © 2004-2010 Angeli-Ka, Joe Kucera
// Copyright © 2012-2018 Miranda NG team
//
// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------------
// DESCRIPTION:
//
// Support for Custom Statuses
// -----------------------------------------------------------------------------
#include "stdafx.h"
#include "m_extraicons.h"
#include "../icq_xstatus/src/resource.h"
static HANDLE hXStatusIcons[XSTATUS_COUNT];
static int hXStatusCListIcons[XSTATUS_COUNT];
static BOOL bXStatusCListIconsValid[XSTATUS_COUNT];
int OnReloadIcons(WPARAM, LPARAM)
{
memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid));
return 0;
}
BYTE CIcqProto::getContactXStatus(MCONTACT hContact)
{
if (!m_bXStatusEnabled && !m_bMoodsEnabled)
return 0;
BYTE bXStatus = getByte(hContact, DBSETTING_XSTATUS_ID, 0);
return (bXStatus < 1 || bXStatus > XSTATUS_COUNT) ? 0 : bXStatus;
}
DWORD CIcqProto::sendXStatusDetailsRequest(MCONTACT hContact, int bForced)
{
DWORD dwCookie = 0;
// only request custom status detail when the contact has one
if (m_bXStatusEnabled && getContactXStatus(hContact) != 0) {
int nNotifyLen = 94 + UINMAXLEN;
char *szNotify = (char*)_alloca(nNotifyLen);
mir_snprintf(szNotify, nNotifyLen, "cAwaySrvAwayStat1%d", m_dwLocalUIN);
dwCookie = SendXtrazNotifyRequest(hContact, "srvMng
", szNotify, bForced);
}
return dwCookie;
}
DWORD CIcqProto::requestXStatusDetails(MCONTACT hContact, BOOL bAllowDelay)
{
if (!validateStatusMessageRequest(hContact, MTYPE_SCRIPT_NOTIFY))
return 0; // apply privacy rules
if (!CheckContactCapabilities(hContact, CAPF_XSTATUS))
return 0; // contact does not have xstatus
// delay is disabled only if fired from dialog
if (!CheckContactCapabilities(hContact, CAPF_XTRAZ) && bAllowDelay)
return 0; // Contact does not support xtraz, do not request details
struct rates_xstatus_request : public rates_queue_item
{
protected:
virtual rates_queue_item* copyItem(rates_queue_item *aDest = nullptr)
{
rates_xstatus_request *pDest = (rates_xstatus_request*)aDest;
if (!pDest)
pDest = new rates_xstatus_request(ppro, wGroup);
pDest->bForced = bForced;
return rates_queue_item::copyItem(pDest);
};
public:
rates_xstatus_request(CIcqProto *ppro, WORD wGroup) : rates_queue_item(ppro, wGroup) {};
virtual ~rates_xstatus_request() {};
virtual void execute()
{
dwCookie = ppro->sendXStatusDetailsRequest(hContact, bForced);
};
BOOL bForced;
DWORD dwCookie;
};
WORD wGroup;
{
mir_cslock l(m_ratesMutex);
wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND);
}
rates_xstatus_request rr(this, wGroup);
rr.bForced = !bAllowDelay;
rr.hContact = hContact;
// delay at least one sec if allowed
if (!handleRateItem(&rr, RQT_REQUEST, 1000, bAllowDelay))
return rr.dwCookie;
return -1; // delayed
}
static HANDLE LoadXStatusIconLibrary(wchar_t *path, const wchar_t *sub)
{
wchar_t* p = wcsrchr(path, '\\');
HANDLE hLib;
mir_wstrcpy(p, sub);
mir_wstrcat(p, L"\\xstatus_ICQ.dll");
if (hLib = LoadLibrary(path))
return hLib;
mir_wstrcpy(p, sub);
mir_wstrcat(p, L"\\xstatus_icons.dll");
if (hLib = LoadLibrary(path))
return hLib;
mir_wstrcpy(p, L"\\");
return hLib;
}
static wchar_t* InitXStatusIconLibrary(wchar_t *buf, size_t buf_size)
{
wchar_t path[2 * MAX_PATH];
HMODULE hXStatusIconsDLL;
// get miranda's exe path
GetModuleFileName(nullptr, path, MAX_PATH);
hXStatusIconsDLL = (HMODULE)LoadXStatusIconLibrary(path, L"\\Icons");
if (!hXStatusIconsDLL) // TODO: add "Custom Folders" support
hXStatusIconsDLL = (HMODULE)LoadXStatusIconLibrary(path, L"\\Plugins");
if (hXStatusIconsDLL) {
null_strcpy(buf, path, buf_size - 1);
char ident[MAX_PATH];
if (LoadStringA(hXStatusIconsDLL, IDS_IDENTIFY, ident, sizeof(ident)) == 0 || mir_strcmp(ident, "# Custom Status Icons #"))
*buf = 0;
FreeLibrary(hXStatusIconsDLL);
}
else *buf = 0;
return buf;
}
HICON CIcqProto::getXStatusIcon(int bStatus, UINT flags)
{
HICON icon = nullptr;
if (bStatus > 0 && bStatus <= XSTATUS_COUNT)
icon = IcoLib_GetIconByHandle(hXStatusIcons[bStatus - 1], (flags & LR_BIGICON) != 0);
return (flags & LR_SHARED || !icon) ? icon : CopyIcon(icon);
}
void setContactExtraIcon(MCONTACT hContact, int xstatus)
{
ExtraIcon_SetIcon(hExtraXStatus, hContact, (xstatus > 0) ? hXStatusIcons[xstatus - 1] : nullptr);
}
#define NULLCAP {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
capstr capXStatus[XSTATUS_COUNT] = {
{0x01, 0xD8, 0xD7, 0xEE, 0xAC, 0x3B, 0x49, 0x2A, 0xA5, 0x8D, 0xD3, 0xD8, 0x77, 0xE6, 0x6B, 0x92},
{0x5A, 0x58, 0x1E, 0xA1, 0xE5, 0x80, 0x43, 0x0C, 0xA0, 0x6F, 0x61, 0x22, 0x98, 0xB7, 0xE4, 0xC7},
{0x83, 0xC9, 0xB7, 0x8E, 0x77, 0xE7, 0x43, 0x78, 0xB2, 0xC5, 0xFB, 0x6C, 0xFC, 0xC3, 0x5B, 0xEC},
{0xE6, 0x01, 0xE4, 0x1C, 0x33, 0x73, 0x4B, 0xD1, 0xBC, 0x06, 0x81, 0x1D, 0x6C, 0x32, 0x3D, 0x81},
{0x8C, 0x50, 0xDB, 0xAE, 0x81, 0xED, 0x47, 0x86, 0xAC, 0xCA, 0x16, 0xCC, 0x32, 0x13, 0xC7, 0xB7},
{0x3F, 0xB0, 0xBD, 0x36, 0xAF, 0x3B, 0x4A, 0x60, 0x9E, 0xEF, 0xCF, 0x19, 0x0F, 0x6A, 0x5A, 0x7F},
{0xF8, 0xE8, 0xD7, 0xB2, 0x82, 0xC4, 0x41, 0x42, 0x90, 0xF8, 0x10, 0xC6, 0xCE, 0x0A, 0x89, 0xA6},
{0x80, 0x53, 0x7D, 0xE2, 0xA4, 0x67, 0x4A, 0x76, 0xB3, 0x54, 0x6D, 0xFD, 0x07, 0x5F, 0x5E, 0xC6},
{0xF1, 0x8A, 0xB5, 0x2E, 0xDC, 0x57, 0x49, 0x1D, 0x99, 0xDC, 0x64, 0x44, 0x50, 0x24, 0x57, 0xAF},
{0x1B, 0x78, 0xAE, 0x31, 0xFA, 0x0B, 0x4D, 0x38, 0x93, 0xD1, 0x99, 0x7E, 0xEE, 0xAF, 0xB2, 0x18},
{0x61, 0xBE, 0xE0, 0xDD, 0x8B, 0xDD, 0x47, 0x5D, 0x8D, 0xEE, 0x5F, 0x4B, 0xAA, 0xCF, 0x19, 0xA7},
{0x48, 0x8E, 0x14, 0x89, 0x8A, 0xCA, 0x4A, 0x08, 0x82, 0xAA, 0x77, 0xCE, 0x7A, 0x16, 0x52, 0x08},
{0x10, 0x7A, 0x9A, 0x18, 0x12, 0x32, 0x4D, 0xA4, 0xB6, 0xCD, 0x08, 0x79, 0xDB, 0x78, 0x0F, 0x09},
{0x6F, 0x49, 0x30, 0x98, 0x4F, 0x7C, 0x4A, 0xFF, 0xA2, 0x76, 0x34, 0xA0, 0x3B, 0xCE, 0xAE, 0xA7},
{0x12, 0x92, 0xE5, 0x50, 0x1B, 0x64, 0x4F, 0x66, 0xB2, 0x06, 0xB2, 0x9A, 0xF3, 0x78, 0xE4, 0x8D},
{0xD4, 0xA6, 0x11, 0xD0, 0x8F, 0x01, 0x4E, 0xC0, 0x92, 0x23, 0xC5, 0xB6, 0xBE, 0xC6, 0xCC, 0xF0},
{0x60, 0x9D, 0x52, 0xF8, 0xA2, 0x9A, 0x49, 0xA6, 0xB2, 0xA0, 0x25, 0x24, 0xC5, 0xE9, 0xD2, 0x60},
{0x63, 0x62, 0x73, 0x37, 0xA0, 0x3F, 0x49, 0xFF, 0x80, 0xE5, 0xF7, 0x09, 0xCD, 0xE0, 0xA4, 0xEE},
{0x1F, 0x7A, 0x40, 0x71, 0xBF, 0x3B, 0x4E, 0x60, 0xBC, 0x32, 0x4C, 0x57, 0x87, 0xB0, 0x4C, 0xF1},
{0x78, 0x5E, 0x8C, 0x48, 0x40, 0xD3, 0x4C, 0x65, 0x88, 0x6F, 0x04, 0xCF, 0x3F, 0x3F, 0x43, 0xDF},
{0xA6, 0xED, 0x55, 0x7E, 0x6B, 0xF7, 0x44, 0xD4, 0xA5, 0xD4, 0xD2, 0xE7, 0xD9, 0x5C, 0xE8, 0x1F},
{0x12, 0xD0, 0x7E, 0x3E, 0xF8, 0x85, 0x48, 0x9E, 0x8E, 0x97, 0xA7, 0x2A, 0x65, 0x51, 0xE5, 0x8D},
{0xBA, 0x74, 0xDB, 0x3E, 0x9E, 0x24, 0x43, 0x4B, 0x87, 0xB6, 0x2F, 0x6B, 0x8D, 0xFE, 0xE5, 0x0F},
{0x63, 0x4F, 0x6B, 0xD8, 0xAD, 0xD2, 0x4A, 0xA1, 0xAA, 0xB9, 0x11, 0x5B, 0xC2, 0x6D, 0x05, 0xA1},
{0x2C, 0xE0, 0xE4, 0xE5, 0x7C, 0x64, 0x43, 0x70, 0x9C, 0x3A, 0x7A, 0x1C, 0xE8, 0x78, 0xA7, 0xDC},
{0x10, 0x11, 0x17, 0xC9, 0xA3, 0xB0, 0x40, 0xF9, 0x81, 0xAC, 0x49, 0xE1, 0x59, 0xFB, 0xD5, 0xD4},
{0x16, 0x0C, 0x60, 0xBB, 0xDD, 0x44, 0x43, 0xF3, 0x91, 0x40, 0x05, 0x0F, 0x00, 0xE6, 0xC0, 0x09},
{0x64, 0x43, 0xC6, 0xAF, 0x22, 0x60, 0x45, 0x17, 0xB5, 0x8C, 0xD7, 0xDF, 0x8E, 0x29, 0x03, 0x52},
{0x16, 0xF5, 0xB7, 0x6F, 0xA9, 0xD2, 0x40, 0x35, 0x8C, 0xC5, 0xC0, 0x84, 0x70, 0x3C, 0x98, 0xFA},
{0x63, 0x14, 0x36, 0xff, 0x3f, 0x8a, 0x40, 0xd0, 0xa5, 0xcb, 0x7b, 0x66, 0xe0, 0x51, 0xb3, 0x64},
{0xb7, 0x08, 0x67, 0xf5, 0x38, 0x25, 0x43, 0x27, 0xa1, 0xff, 0xcf, 0x4c, 0xc1, 0x93, 0x97, 0x97},
{0xdd, 0xcf, 0x0e, 0xa9, 0x71, 0x95, 0x40, 0x48, 0xa9, 0xc6, 0x41, 0x32, 0x06, 0xd6, 0xf2, 0x80},
NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
NULLCAP, NULLCAP, NULLCAP, NULLCAP };
const char *nameXStatus[XSTATUS_COUNT] = {
LPGEN("Angry"), // 23
LPGEN("Taking a bath"), // 1
LPGEN("Tired"), // 2
LPGEN("Birthday"), // 3
LPGEN("Drinking beer"), // 4
LPGEN("Thinking"), // 5
LPGEN("Eating"), // 80
LPGEN("Watching TV"), // 7
LPGEN("Meeting"), // 8
LPGEN("Coffee"), // 9
LPGEN("Listening to music"),// 10
LPGEN("Business"), // 11
LPGEN("Shooting"), // 12
LPGEN("Having fun"), // 13
LPGEN("On the phone"), // 14
LPGEN("Gaming"), // 15
LPGEN("Studying"), // 16
LPGEN("Shopping"), // 0
LPGEN("Feeling sick"), // 17
LPGEN("Sleeping"), // 18
LPGEN("Surfing"), // 19
LPGEN("Internet"), // 20
LPGEN("Working"), // 21
LPGEN("Typing"), // 22
LPGEN("Picnic"), // 66
LPGEN("Cooking"),
LPGEN("Smoking"),
LPGEN("I'm high"),
LPGEN("On WC"), // 68
LPGEN("To be or not to be"),// 77
LPGEN("Watching pro7 on TV"),
LPGEN("Love"), // 61
LPGEN("Hot Dog"), //6
LPGEN("Rough"), //24
LPGEN("Rock On"), //25
LPGEN("Baby"), //26
LPGEN("Soccer"), //27
LPGEN("Pirate"), //28
LPGEN("Cyclop"), //29
LPGEN("Monkey"), //30
LPGEN("Birdie"), //31
LPGEN("Cool"), //32
LPGEN("Evil"), //33
LPGEN("Alien"), //34
LPGEN("Scooter"), //35
LPGEN("Mask"), //36
LPGEN("Money"), //37
LPGEN("Pilot"), //38
LPGEN("Afro"), //39
LPGEN("St. Patrick"), //40
LPGEN("Headmaster"), //41
LPGEN("Lips"), //42
LPGEN("Ice-Cream"), //43
LPGEN("Pink Lady"), //44
LPGEN("Up yours"), //45
LPGEN("Laughing"), //46
LPGEN("Dog"), //47
LPGEN("Candy"), //48
LPGEN("Crazy Professor"),//50
LPGEN("Ninja"), //51
LPGEN("Cocktail"), //52
LPGEN("Punch"), //53
LPGEN("Donut"), //54
LPGEN("Feeling Good"), //55
LPGEN("Lollypop"), //56
LPGEN("Oink Oink"), //57
LPGEN("Kitty"), //58
LPGEN("Sumo"), //59
LPGEN("Broken hearted"),//60
LPGEN("Free for chat"), //62
LPGEN("@home"), //63
LPGEN("@work"), //64
LPGEN("Strawberry"), //65
LPGEN("Angel"), //67
LPGEN("Pizza"), //69
LPGEN("Snoring"), //70
LPGEN("On my mobile"), //71
LPGEN("Depressed"), //72
LPGEN("Beetle"), //73
LPGEN("Double Rainbow"),//74
LPGEN("Basketball"), //75
LPGEN("Cupid shot me"), //76
LPGEN("Celebrating"), //78
LPGEN("Sushi"), //79
LPGEN("Playing"), //81
LPGEN("Writing") //84
};
const int moodXStatus[XSTATUS_COUNT] = {
23,
1,
2,
3,
4,
5,
80,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
0,
17,
18,
19,
20,
21,
22,
66,
-1,
-1,
-1,
68,
77,
-1,
61,
6,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
62,
63,
64,
65,
67,
69,
70,
71,
72,
73,
74,
75,
76,
78,
79,
81,
84 };
void CIcqProto::handleXStatusCaps(DWORD dwUIN, char *szUID, MCONTACT hContact, BYTE *caps, int capsize, char *moods, int moodsize)
{
int bChanged = FALSE;
int nCustomStatusID = 0, nMoodID = 0;
if (!m_bXStatusEnabled && !m_bMoodsEnabled) {
ClearContactCapabilities(hContact, CAPF_STATUS_MOOD | CAPF_XSTATUS);
return;
}
int nOldXStatusID = getContactXStatus(hContact);
if (m_bMoodsEnabled) {
// process custom statuses (moods) from ICQ6
if (moods) {
if (moodsize > 0)
if (moodsize >= 32)
moods[32] = '\0';
for (int i = 0; i < XSTATUS_COUNT; i++) {
char szMoodId[32], szMoodData[32];
null_strcpy(szMoodData, moods, moodsize);
if (moodXStatus[i] == -1) continue;
mir_snprintf(szMoodId, "0icqmood%d", moodXStatus[i]);
if (!mir_strcmp(szMoodId, szMoodData)) {
BYTE bXStatusId = (BYTE)(i + 1);
char str[MAX_PATH];
SetContactCapabilities(hContact, CAPF_STATUS_MOOD);
// only write default name when it is really needed, i.e. on Custom Status change
if (nOldXStatusID != bXStatusId) {
setByte(hContact, DBSETTING_XSTATUS_ID, bXStatusId);
db_set_utf(hContact, m_szModuleName, DBSETTING_XSTATUS_NAME, ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
delSetting(hContact, DBSETTING_XSTATUS_MSG);
debugLogA("%s changed mood to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
bChanged = TRUE;
}
// cannot retrieve mood details here - need to be processed with new user details
nMoodID = bXStatusId;
break;
}
}
if (nMoodID == 0 && moods)
ClearContactCapabilities(hContact, CAPF_STATUS_MOOD);
}
}
if (m_bXStatusEnabled) {
// detect custom status capabilities
if (caps) {
if (capsize > 0)
for (int i = 0; i < XSTATUS_COUNT; i++) {
if (MatchCapability(caps, capsize, (const capstr*)capXStatus[i], BINARY_CAP_SIZE)) {
BYTE bXStatusId = (BYTE)(i + 1);
char str[MAX_PATH];
SetContactCapabilities(hContact, CAPF_XSTATUS);
// only write default name when it is really needed, i.e. on Custom Status change
if (nMoodID == 0 && nOldXStatusID != bXStatusId) {
setByte(hContact, DBSETTING_XSTATUS_ID, bXStatusId);
db_set_utf(hContact, m_szModuleName, DBSETTING_XSTATUS_NAME, ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
delSetting(hContact, DBSETTING_XSTATUS_MSG);
debugLogA("%s changed custom status to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
bChanged = TRUE;
}
if (getByte("XStatusAuto", DEFAULT_XSTATUS_AUTO))
requestXStatusDetails(hContact, TRUE);
nCustomStatusID = bXStatusId;
break;
}
}
if (nCustomStatusID == 0)
ClearContactCapabilities(hContact, CAPF_XSTATUS);
}
}
if (nCustomStatusID != 0 && nMoodID != 0 && nCustomStatusID != nMoodID)
debugLogA("Warning: Diverse custom statuses detected, using mood status.");
if ((nCustomStatusID == 0 && (caps || !m_bXStatusEnabled)) && (nMoodID == 0 && (moods || !m_bMoodsEnabled))) {
if (getByte(hContact, DBSETTING_XSTATUS_ID, -1) != -1)
bChanged = TRUE;
delSetting(hContact, DBSETTING_XSTATUS_ID);
delSetting(hContact, DBSETTING_XSTATUS_NAME);
delSetting(hContact, DBSETTING_XSTATUS_MSG);
}
if (m_bXStatusEnabled != 10 && m_bMoodsEnabled != 10)
setContactExtraIcon(hContact, nMoodID ? nMoodID : (moods ? 0 : (nCustomStatusID ? nCustomStatusID : nOldXStatusID)));
}
void CIcqProto::updateServerCustomStatus(int fullUpdate)
{
BYTE bXStatus = getContactXStatus(NULL);
// update client capabilities
if (fullUpdate) {
if (m_bXStatusEnabled)
setUserInfo();
char szMoodData[32];
// prepare mood id
if (m_bMoodsEnabled && bXStatus && moodXStatus[bXStatus - 1] != -1)
mir_snprintf(szMoodData, "0icqmood%d", moodXStatus[bXStatus - 1]);
else
szMoodData[0] = '\0';
SetStatusMood(szMoodData, 1500);
}
char *szStatusNote = nullptr;
// use custom status message as status note
if (bXStatus && (m_bXStatusEnabled || m_bMoodsEnabled))
szStatusNote = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, "");
// retrieve standard status message (e.g. custom status set to none)
else {
char **pszMsg = MirandaStatusToAwayMsg(m_iStatus);
{
mir_cslock l(m_modeMsgsMutex);
if (pszMsg)
szStatusNote = null_strdup(*pszMsg);
}
// no default status message, set empty
if (!szStatusNote)
szStatusNote = null_strdup("");
}
if (szStatusNote)
SetStatusNote(szStatusNote, 1500, FALSE);
SAFE_FREE(&szStatusNote);
}
static LRESULT CALLBACK MessageEditSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_CHAR:
if (wParam == '\n' && GetKeyState(VK_CONTROL) & 0x8000) {
PostMessage(GetParent(hwnd), WM_COMMAND, IDOK, 0);
return 0;
}
if (wParam == 1 && GetKeyState(VK_CONTROL) & 0x8000) { // ctrl-a
SendMessage(hwnd, EM_SETSEL, 0, -1);
return 0;
}
if (wParam == 23 && GetKeyState(VK_CONTROL) & 0x8000) { // ctrl-w
SendMessage(GetParent(hwnd), WM_CLOSE, 0, 0);
return 0;
}
if (wParam == 127 && GetKeyState(VK_CONTROL) & 0x8000) { // ctrl-backspace
DWORD start, end;
SendMessage(hwnd, EM_GETSEL, (WPARAM)&end, (LPARAM)(PDWORD)NULL);
SendMessage(hwnd, WM_KEYDOWN, VK_LEFT, 0);
SendMessage(hwnd, EM_GETSEL, (WPARAM)&start, (LPARAM)(PDWORD)NULL);
WCHAR *text = GetWindowTextUcs(hwnd);
memmove(text + start, text + end, sizeof(WCHAR) * (mir_wstrlen(text) + 1 - end));
SetWindowTextUcs(hwnd, text);
SAFE_FREE(&text);
SendMessage(hwnd, EM_SETSEL, start, start);
SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwnd), EN_CHANGE), (LPARAM)hwnd);
return 0;
}
break;
}
return mir_callNextSubclass(hwnd, MessageEditSubclassProc, msg, wParam, lParam);
}
struct SetXStatusData
{
CIcqProto* ppro;
BYTE bAction;
BYTE bXStatus;
MCONTACT hContact;
HANDLE hEvent;
DWORD iEvent;
int countdown;
char* okButtonFormat;
};
struct InitXStatusData
{
CIcqProto* ppro;
BYTE bAction;
BYTE bXStatus;
char* szXStatusName;
char* szXStatusMsg;
MCONTACT hContact;
};
#define HM_PROTOACK (WM_USER+10)
static INT_PTR CALLBACK SetXStatusDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
SetXStatusData *dat = (SetXStatusData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
char str[MAX_PATH];
switch (message) {
case HM_PROTOACK:
{
ACKDATA *ack = (ACKDATA*)lParam;
if (ack->type != ICQACKTYPE_XSTATUS_RESPONSE) break;
if (ack->hContact != dat->hContact) break;
if ((UINT_PTR)ack->hProcess != dat->iEvent) break;
ShowDlgItem(hwndDlg, IDC_RETRXSTATUS, SW_HIDE);
ShowDlgItem(hwndDlg, IDC_XMSG, SW_SHOW);
ShowDlgItem(hwndDlg, IDC_XTITLE, SW_SHOW);
SetDlgItemText(hwndDlg, IDOK, TranslateT("Close"));
UnhookEvent(dat->hEvent); dat->hEvent = nullptr;
char *szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_NAME, "");
SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, szText);
SAFE_FREE(&szText);
szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_MSG, "");
SetDlgItemTextUtf(hwndDlg, IDC_XMSG, szText);
SAFE_FREE(&szText);
}
break;
case WM_INITDIALOG:
{
InitXStatusData *init = (InitXStatusData*)lParam;
TranslateDialogDefault(hwndDlg);
dat = (SetXStatusData*)SAFE_MALLOC(sizeof(SetXStatusData));
dat->ppro = init->ppro;
SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);
dat->bAction = init->bAction;
if (!init->bAction) { // set our xStatus
dat->bXStatus = init->bXStatus;
SendDlgItemMessage(hwndDlg, IDC_XMSG, EM_LIMITTEXT, 1024, 0);
mir_subclassWindow(GetDlgItem(hwndDlg, IDC_XMSG), MessageEditSubclassProc);
SetDlgItemTextUtf(hwndDlg, IDC_XMSG, init->szXStatusMsg);
if (dat->ppro->m_bXStatusEnabled) { // custom status enabled, prepare title edit
SendDlgItemMessage(hwndDlg, IDC_XTITLE, EM_LIMITTEXT, 256, 0);
mir_subclassWindow(GetDlgItem(hwndDlg, IDC_XTITLE), MessageEditSubclassProc);
SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, init->szXStatusName);
}
else { // only moods enabled, hide title, resize message edit control
ShowDlgItem(hwndDlg, IDC_XTITLE_STATIC, SW_HIDE);
ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE);
MoveDlgItem(hwndDlg, IDC_XMSG_STATIC, 5, 0, 179, 8);
MoveDlgItem(hwndDlg, IDC_XMSG, 5, 9, 179, 65);
}
dat->okButtonFormat = GetDlgItemTextUtf(hwndDlg, IDOK);
dat->countdown = 5;
SendMessage(hwndDlg, WM_TIMER, 0, 0);
SetTimer(hwndDlg, 1, 1000, nullptr);
}
else { // retrieve contact's xStatus
dat->hContact = init->hContact;
dat->bXStatus = dat->ppro->getContactXStatus(dat->hContact);
dat->okButtonFormat = nullptr;
SendDlgItemMessage(hwndDlg, IDC_XTITLE, EM_SETREADONLY, 1, 0);
SendDlgItemMessage(hwndDlg, IDC_XMSG, EM_SETREADONLY, 1, 0);
if (dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_XSTATUS) && !dat->ppro->getByte("XStatusAuto", DEFAULT_XSTATUS_AUTO)) {
SetDlgItemText(hwndDlg, IDOK, TranslateT("Cancel"));
dat->hEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_PROTOACK);
ShowDlgItem(hwndDlg, IDC_RETRXSTATUS, SW_SHOW);
ShowDlgItem(hwndDlg, IDC_XMSG, SW_HIDE);
ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE);
dat->iEvent = dat->ppro->requestXStatusDetails(dat->hContact, FALSE);
}
else {
SetDlgItemText(hwndDlg, IDOK, TranslateT("Close"));
dat->hEvent = nullptr;
char *szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_NAME, "");
SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, szText);
SAFE_FREE(&szText);
// only for clients supporting just moods and not custom status
if (dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_STATUS_MOOD) && !dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_XSTATUS)) {
szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_STATUS_NOTE, "");
// hide title, resize message edit control
ShowDlgItem(hwndDlg, IDC_XTITLE_STATIC, SW_HIDE);
ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE);
MoveDlgItem(hwndDlg, IDC_XMSG_STATIC, 5, 0, 179, 8);
MoveDlgItem(hwndDlg, IDC_XMSG, 5, 9, 179, 65);
}
else szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_MSG, "");
SetDlgItemTextUtf(hwndDlg, IDC_XMSG, szText);
SAFE_FREE(&szText);
}
}
if (dat->bXStatus) {
SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)dat->ppro->getXStatusIcon(dat->bXStatus, LR_SHARED | LR_BIGICON));
SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)dat->ppro->getXStatusIcon(dat->bXStatus, LR_SHARED));
}
char buf[MAX_PATH];
char *format = GetWindowTextUtf(hwndDlg);
mir_snprintf(str, format, dat->bXStatus ? ICQTranslateUtfStatic(nameXStatus[dat->bXStatus - 1], buf, MAX_PATH) : "");
SetWindowTextUtf(hwndDlg, str);
SAFE_FREE(&format);
}
return TRUE;
case WM_TIMER:
if (dat->countdown == -1) {
DestroyWindow(hwndDlg);
break;
}
mir_snprintf(str, dat->okButtonFormat, dat->countdown);
SetDlgItemTextUtf(hwndDlg, IDOK, str);
dat->countdown--;
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
DestroyWindow(hwndDlg);
break;
case IDC_XTITLE:
case IDC_XMSG:
if (!dat->bAction) { // set our xStatus
KillTimer(hwndDlg, 1);
SetDlgItemText(hwndDlg, IDOK, TranslateT("OK"));
}
}
break;
case WM_DESTROY:
if (!dat->bAction) { // set our xStatus
char szSetting[64];
dat->ppro->setByte(DBSETTING_XSTATUS_ID, dat->bXStatus);
char *szValue = GetDlgItemTextUtf(hwndDlg, IDC_XMSG);
mir_snprintf(szSetting, "XStatus%dMsg", dat->bXStatus);
db_set_utf(NULL, dat->ppro->m_szModuleName, szSetting, szValue);
db_set_utf(NULL, dat->ppro->m_szModuleName, DBSETTING_XSTATUS_MSG, szValue);
SAFE_FREE(&szValue);
if (dat->ppro->m_bXStatusEnabled) {
szValue = GetDlgItemTextUtf(hwndDlg, IDC_XTITLE);
mir_snprintf(szSetting, "XStatus%dName", dat->bXStatus);
db_set_utf(NULL, dat->ppro->m_szModuleName, szSetting, szValue);
db_set_utf(NULL, dat->ppro->m_szModuleName, DBSETTING_XSTATUS_NAME, szValue);
SAFE_FREE(&szValue);
if (dat->bXStatus)
Window_FreeIcon_IcoLib(hwndDlg);
}
dat->ppro->updateServerCustomStatus(TRUE);
}
if (dat->hEvent) UnhookEvent(dat->hEvent);
SAFE_FREE(&dat->okButtonFormat);
SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
SAFE_FREE((void**)&dat);
break;
case WM_CLOSE:
DestroyWindow(hwndDlg);
break;
}
return FALSE;
}
void CIcqProto::setXStatusEx(BYTE bXStatus, BYTE bQuiet)
{
BYTE bOldXStatus = getByte(DBSETTING_XSTATUS_ID, 0);
if (!m_bHideXStatusUI) {
if (bOldXStatus <= XSTATUS_COUNT)
Menu_ModifyItem(hXStatusItems[bOldXStatus], nullptr, INVALID_HANDLE_VALUE, 0);
Menu_ModifyItem(hXStatusItems[bXStatus], nullptr, INVALID_HANDLE_VALUE, CMIF_CHECKED);
}
if (bXStatus) {
char szSetting[64];
char str[MAX_PATH];
char *szName = nullptr, *szMsg = nullptr;
if (m_bXStatusEnabled) {
mir_snprintf(szSetting, "XStatus%dName", bXStatus);
szName = getSettingStringUtf(NULL, szSetting, ICQTranslateUtfStatic(nameXStatus[bXStatus - 1], str, MAX_PATH));
}
mir_snprintf(szSetting, "XStatus%dMsg", bXStatus);
szMsg = getSettingStringUtf(NULL, szSetting, "");
mir_snprintf(szSetting, "XStatus%dStat", bXStatus);
if (!bQuiet && !getByte(szSetting, 0)) {
InitXStatusData init;
init.ppro = this;
init.bAction = 0; // set
init.bXStatus = bXStatus;
init.szXStatusName = szName;
init.szXStatusMsg = szMsg;
CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_SETXSTATUS), nullptr, SetXStatusDlgProc, (LPARAM)&init);
}
else {
setByte(DBSETTING_XSTATUS_ID, bXStatus);
if (m_bXStatusEnabled)
db_set_utf(NULL, m_szModuleName, DBSETTING_XSTATUS_NAME, szName);
db_set_utf(NULL, m_szModuleName, DBSETTING_XSTATUS_MSG, szMsg);
updateServerCustomStatus(TRUE);
}
SAFE_FREE(&szName);
SAFE_FREE(&szMsg);
}
else {
setByte(DBSETTING_XSTATUS_ID, bXStatus);
delSetting(DBSETTING_XSTATUS_NAME);
delSetting(DBSETTING_XSTATUS_MSG);
updateServerCustomStatus(TRUE);
}
}
INT_PTR CIcqProto::menuXStatus(WPARAM, LPARAM, LPARAM fParam)
{
setXStatusEx((BYTE)fParam, 0);
return 0;
}
void CIcqProto::InitXStatusItems(BOOL bAllowStatus)
{
BYTE bXStatus = getContactXStatus(NULL);
if (!m_bXStatusEnabled && !m_bMoodsEnabled)
return;
if (!bAllowStatus)
return;
// Custom Status UI is disabled, no need to continue items' init
if (m_bHideXStatusUI || m_bHideXStatusMenu)
return;
HGENMENU hRoot;
{
wchar_t szItem[MAX_PATH + 64];
mir_snwprintf(szItem, TranslateT("%s Custom Status"), m_tszUserName);
CMenuItem mi;
mi.root = Menu_GetProtocolMenu(m_szModuleName);
mi.name.w = szItem;
mi.position = 10001;
hRoot = Menu_AddStatusMenuItem(&mi, m_szModuleName);
}
CMenuItem mi;
mi.position = 2000040000;
mi.root = hRoot;
int bXStatusMenuBuilt = 0;
for (int i = 0; i <= XSTATUS_COUNT; i++) {
char srvFce[MAX_PATH + 64];
mir_snprintf(srvFce, "/menuXStatus%d", i);
mi.position++;
if (!i)
bXStatusMenuBuilt = ProtoServiceExists(m_szModuleName, srvFce);
if (!bXStatusMenuBuilt)
CreateProtoServiceParam(srvFce, &CIcqProto::menuXStatus, i);
mi.flags = (bXStatus == i ? CMIF_CHECKED : 0);
mi.hIcolibItem = i ? hXStatusIcons[i - 1] : nullptr;
mi.name.a = i ? (char*)nameXStatus[i - 1] : (char *)LPGEN("None");
mi.pszService = srvFce;
hXStatusItems[i] = Menu_AddStatusMenuItem(&mi, m_szModuleName);
Menu_ShowItem(hXStatusItems[i], !(m_bHideXStatusUI || m_bHideXStatusMenu));
}
}
void InitXStatusIcons()
{
wchar_t lib[2 * MAX_PATH] = { 0 };
SKINICONDESC sid = {};
sid.section.a = "Protocols/" ICQ_PROTOCOL_NAME "/" LPGEN("Custom Status");
sid.flags = SIDF_PATH_UNICODE;
sid.defaultFile.w = InitXStatusIconLibrary(lib, _countof(lib));
for (int i = 0; i < XSTATUS_COUNT; i++) {
char szTemp[100];
mir_snprintf(szTemp, "icq_xstatus%d", i);
sid.pszName = szTemp;
sid.description.a = (LPSTR)nameXStatus[i];
sid.iDefaultIndex = -(IDI_XSTATUS1 + i);
hXStatusIcons[i] = IcoLib_AddIcon(&sid);
}
// initialize arrays for CList custom status icons
memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid));
memset(hXStatusCListIcons, -1, sizeof(hXStatusCListIcons));
}
INT_PTR CIcqProto::ShowXStatusDetails(WPARAM hContact, LPARAM)
{
InitXStatusData init;
init.ppro = this;
init.bAction = 1; // retrieve
init.hContact = hContact;
CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_SETXSTATUS), nullptr, SetXStatusDlgProc, (LPARAM)&init);
return 0;
}
INT_PTR CIcqProto::SetXStatusEx(WPARAM, LPARAM lParam)
{
CUSTOM_STATUS *pData = (CUSTOM_STATUS*)lParam;
if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 1;
if (pData->cbSize < sizeof(CUSTOM_STATUS)) return 1; // Failure
if (pData->flags & CSSF_MASK_STATUS) { // set custom status
int status = *pData->status;
if (status >= 0 && status <= XSTATUS_COUNT)
setXStatusEx((BYTE)status, 1);
else
return 1; // Failure
}
if (pData->flags & (CSSF_MASK_NAME | CSSF_MASK_MESSAGE)) {
BYTE status = getContactXStatus(NULL);
if (!status) return 1; // Failure
if (m_bXStatusEnabled && (pData->flags & CSSF_MASK_NAME)) { // set custom status name
if (pData->flags & CSSF_UNICODE)
setWString(DBSETTING_XSTATUS_NAME, pData->pwszName);
else
setString(DBSETTING_XSTATUS_NAME, pData->pszName);
}
if (pData->flags & CSSF_MASK_MESSAGE) { // set custom status message
if (pData->flags & CSSF_UNICODE)
setWString(DBSETTING_XSTATUS_MSG, pData->pwszMessage);
else
setString(DBSETTING_XSTATUS_MSG, pData->pszMessage);
// update status note if used for custom status message
updateServerCustomStatus(FALSE);
}
}
// hide menu items + contact menu item
if (pData->flags & CSSF_DISABLE_UI)
m_bHideXStatusUI = (*pData->wParam) ? 0 : 1;
// hide menu items only
if (pData->flags & CSSF_DISABLE_MENU)
m_bHideXStatusMenu = (*pData->wParam) ? 0 : 1;
return 0; // Success
}
INT_PTR CIcqProto::GetXStatusEx(WPARAM hContact, LPARAM lParam)
{
if (!m_bXStatusEnabled && !m_bMoodsEnabled)
return 1;
CUSTOM_STATUS *pData = (CUSTOM_STATUS*)lParam;
if (pData->cbSize < sizeof(CUSTOM_STATUS))
return 1; // Failure
// fill status member
if (pData->flags & CSSF_MASK_STATUS)
*pData->status = getContactXStatus(hContact);
// fill status name member
if (pData->flags & CSSF_MASK_NAME) {
if (pData->flags & CSSF_DEFAULT_NAME) {
int status = (pData->wParam == nullptr) ? getContactXStatus(hContact) : *pData->wParam;
if (status < 1 || status > XSTATUS_COUNT)
return 1; // Failure
if (pData->flags & CSSF_UNICODE) {
char *text = (char*)nameXStatus[status - 1];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, text, -1, pData->pwszName, MAX_PATH);
}
else mir_strcpy(pData->pszName, (char*)nameXStatus[status - 1]);
}
else { // moods does not support status title
if (!m_bXStatusEnabled) return 1;
if (pData->flags & CSSF_UNICODE) {
char *str = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, "");
WCHAR *wstr = make_unicode_string(str);
mir_wstrcpy(pData->pwszName, wstr);
SAFE_FREE(&str);
SAFE_FREE(&wstr);
}
else {
DBVARIANT dbv;
if (!getString(hContact, DBSETTING_XSTATUS_NAME, &dbv) && dbv.pszVal) {
mir_strcpy(pData->pszName, dbv.pszVal);
db_free(&dbv);
}
else mir_strcpy(pData->pszName, "");
}
}
}
if (pData->flags & CSSF_MASK_MESSAGE) { // fill status message member
if (pData->flags & CSSF_UNICODE) {
char *str = getSettingStringUtf(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, "");
WCHAR *wstr = make_unicode_string(str);
mir_wstrcpy(pData->pwszMessage, wstr);
SAFE_FREE(&str);
SAFE_FREE(&wstr);
}
else {
DBVARIANT dbv = { 0 };
if (!getString(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, &dbv) && dbv.pszVal)
mir_strcpy(pData->pszMessage, dbv.pszVal);
else
mir_strcpy(pData->pszMessage, "");
db_free(&dbv);
}
}
if (pData->flags & CSSF_DISABLE_UI)
if (pData->wParam)
*pData->wParam = !m_bHideXStatusUI;
if (pData->flags & CSSF_DISABLE_MENU)
if (pData->wParam)
*pData->wParam = !m_bHideXStatusMenu;
if (pData->flags & CSSF_STATUSES_COUNT)
if (pData->wParam)
*pData->wParam = XSTATUS_COUNT;
if (pData->flags & CSSF_STR_SIZES) {
DBVARIANT dbv = { DBVT_DELETED };
if (pData->wParam) {
if (m_bXStatusEnabled && !getString(hContact, DBSETTING_XSTATUS_NAME, &dbv))
*pData->wParam = mir_strlen(dbv.pszVal);
else
*pData->wParam = 0;
db_free(&dbv);
}
if (pData->lParam) {
if (!getString(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, &dbv))
*pData->lParam = mir_strlen(dbv.pszVal);
else
*pData->lParam = 0;
db_free(&dbv);
}
}
return 0; // Success
}
INT_PTR CIcqProto::GetXStatusIcon(WPARAM wParam, LPARAM lParam)
{
if (!m_bXStatusEnabled && !m_bMoodsEnabled)
return 0;
if (!wParam)
wParam = getContactXStatus(NULL);
if (wParam >= 1 && wParam <= XSTATUS_COUNT)
return (INT_PTR)getXStatusIcon((BYTE)wParam, lParam);
return 0;
}
INT_PTR CIcqProto::RequestXStatusDetails(WPARAM hContact, LPARAM)
{
if (!m_bXStatusEnabled)
return 0;
// user has xstatus, no auto-retrieve details, valid contact, request details
if (hContact && !getByte("XStatusAuto", DEFAULT_XSTATUS_AUTO) && getContactXStatus(hContact) && CheckContactCapabilities(hContact, CAPF_XSTATUS))
return requestXStatusDetails(hContact, TRUE);
return 0;
}
INT_PTR CIcqProto::RequestAdvStatusIconIdx(WPARAM wParam, LPARAM)
{
if (!m_bXStatusEnabled && !m_bMoodsEnabled)
return -1;
BYTE bXStatus = getContactXStatus(wParam);
if (bXStatus) {
if (!bXStatusCListIconsValid[bXStatus - 1]) { // adding icon
int idx = hXStatusCListIcons[bXStatus - 1];
HIMAGELIST hCListImageList = Clist_GetImageList();
if (hCListImageList) {
HICON hXStatusIcon = getXStatusIcon(bXStatus, LR_SHARED);
if (idx > 0)
ImageList_ReplaceIcon(hCListImageList, idx, hXStatusIcon);
else
hXStatusCListIcons[bXStatus - 1] = ImageList_AddIcon(hCListImageList, hXStatusIcon);
// mark icon index in the array as valid
bXStatusCListIconsValid[bXStatus - 1] = TRUE;
IcoLib_ReleaseIcon(hXStatusIcon);
}
}
int idx = bXStatusCListIconsValid[bXStatus - 1] ? hXStatusCListIcons[bXStatus - 1] : -1;
if (idx > 0)
return (idx & 0xFFFF) << 16;
}
return -1;
}