// ---------------------------------------------------------------------------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(hInst, 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 = pcli->pfnGetProtocolMenu(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(hInst, 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; }