//////////////////////////////////////////////////////////////////////////////// // Gadu-Gadu Plugin for Miranda IM // // Copyright (c) 2003-2009 Adam Strzelecki // Copyright (c) 2009-2012 Bartosz BiaƂek // // 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. //////////////////////////////////////////////////////////////////////////////// #include "gg.h" #include #define WM_ADDIMAGE WM_USER + 1 #define WM_SENDIMG WM_USER + 2 #define WM_CHOOSEIMG WM_USER + 3 #define TIMERID_FLASHWND WM_USER + 4 //////////////////////////////////////////////////////////////////////////// // Image Window : Data // tablica zawiera uin kolesia i uchwyt do okna, okno zawiera GGIMAGEDLGDATA // ktore przechowuje handle kontaktu, i wskaznik na pierwszy obrazek // obrazki sa poukladane jako lista jednokierunkowa. // przy tworzeniu okna podaje sie handle do kontaktu // dodajac obrazek tworzy sie element listy i wysyla do okna // wyswietlajac obrazek idzie po liscie jednokierunkowej typedef struct _GGIMAGEENTRY { HBITMAP hBitmap; wchar_t *lpszFileName; char *lpData; unsigned long nSize; struct _GGIMAGEENTRY *lpNext; uint32_t crc32; } GGIMAGEENTRY; struct GGIMAGEDLGDATA { MCONTACT hContact; HANDLE hEvent; HWND hWnd; uin_t uin; int nImg, nImgTotal; GGIMAGEENTRY *lpImages; SIZE minSize; BOOL bReceiving; GaduProto *gg; }; // Prototypes int gg_img_remove(GGIMAGEDLGDATA *dat); //////////////////////////////////////////////////////////////////////////// // Image Module : Adding item to contact menu, creating sync objects // int GaduProto::img_init() { // Send image contact menu item CMenuItem mi; SET_UID(mi, 0xab238938, 0xed85, 0x4cfe, 0x93, 0xb5, 0xb8, 0x83, 0xf4, 0x32, 0xa0, 0xec); mi.position = -2000010000; mi.hIcolibItem = iconList[11].hIcolib; mi.name.a = LPGEN("&Image"); mi.pszService = GGS_SENDIMAGE; hImageMenuItem = Menu_AddContactMenuItem(&mi, m_szModuleName); // Receive image CreateProtoService(GGS_RECVIMAGE, &GaduProto::img_recvimage); return FALSE; } //////////////////////////////////////////////////////////////////////////// // Image Module : closing dialogs, sync objects // int GaduProto::img_shutdown() { list_t l; #ifdef DEBUGMODE debugLogA("img_shutdown(): Closing all dialogs..."); #endif // Rather destroy window instead of just removing structures for (l = imagedlgs; l;) { GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data; l = l->next; if (dat && dat->hWnd) { if (IsWindow(dat->hWnd)) { // Post message async, since it maybe be different thread if (!PostMessage(dat->hWnd, WM_CLOSE, 0, 0)) debugLogA("img_shutdown(): Image dlg %x cannot be released !!", dat->hWnd); } else debugLogA("img_shutdown(): Image dlg %x not exists, but structure does !!", dat->hWnd); } } return FALSE; } //////////////////////////////////////////////////////////////////////////// // Image Module : destroying list // int GaduProto::img_destroy() { // Release all dialogs while (imagedlgs && gg_img_remove((GGIMAGEDLGDATA *)imagedlgs->data)); // Destroy list list_destroy(imagedlgs, 1); Menu_RemoveItem(hImageMenuItem); return FALSE; } //////////////////////////////////////////////////////////////////////////// // Image Window : Frees image entry structure // static int gg_img_releasepicture(void *img) { if (!img) return FALSE; free(((GGIMAGEENTRY *)img)->lpszFileName); if (((GGIMAGEENTRY *)img)->hBitmap) DeleteObject(((GGIMAGEENTRY *)img)->hBitmap); free(((GGIMAGEENTRY *)img)->lpData); free(img); return TRUE; } //////////////////////////////////////////////////////////////////////////// // Painting image // int gg_img_paint(HWND hwnd, GGIMAGEENTRY *dat) { PAINTSTRUCT paintStruct; HDC hdc = BeginPaint(hwnd, &paintStruct); RECT rc; GetWindowRect(GetDlgItem(hwnd, IDC_IMG_IMAGE), &rc); ScreenToClient(hwnd, (POINT *)&rc.left); ScreenToClient(hwnd, (POINT *)&rc.right); FillRect(hdc, &rc, (HBRUSH)GetSysColorBrush(COLOR_WINDOW)); if (dat->hBitmap) { BITMAP bmp; GetObject(dat->hBitmap, sizeof(bmp), &bmp); int nWidth = bmp.bmWidth; int nHeight = bmp.bmHeight; HDC hdcBmp = CreateCompatibleDC(hdc); SelectObject(hdcBmp, dat->hBitmap); if (hdcBmp) { SetStretchBltMode(hdc, HALFTONE); // Draw bitmap if (nWidth > (rc.right - rc.left) || nHeight > (rc.bottom - rc.top)) { if ((double)nWidth / (double)nHeight > (double)(rc.right - rc.left) / (double)(rc.bottom - rc.top)) { StretchBlt(hdc, rc.left, ((rc.top + rc.bottom) - (rc.right - rc.left) * nHeight / nWidth) / 2, (rc.right - rc.left), (rc.right - rc.left) * nHeight / nWidth, hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY); } else { StretchBlt(hdc, ((rc.left + rc.right) - (rc.bottom - rc.top) * nWidth / nHeight) / 2, rc.top, (rc.bottom - rc.top) * nWidth / nHeight, (rc.bottom - rc.top), hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY); } } else { BitBlt(hdc, (rc.left + rc.right - nWidth) / 2, (rc.top + rc.bottom - nHeight) / 2, nWidth, nHeight, hdcBmp, 0, 0, SRCCOPY); } DeleteDC(hdcBmp); } } EndPaint(hwnd, &paintStruct); return FALSE; } //////////////////////////////////////////////////////////////////////////////// // Returns supported image filters // wchar_t *gg_img_getfilter(wchar_t *szFilter, int nSize) { // Match relative to ImgDecoder presence wchar_t *szFilterName = TranslateT("Image files (*.bmp,*.gif,*.jpeg,*.jpg,*.png)"); wchar_t *szFilterMask = L"*.bmp;*.gif;*.jpeg;*.jpg;*.png"; // Make up filter wchar_t *pFilter = szFilter; wcsncpy(pFilter, szFilterName, nSize); pFilter += mir_wstrlen(pFilter) + 1; if (pFilter >= szFilter + nSize) return nullptr; wcsncpy(pFilter, szFilterMask, nSize - (pFilter - szFilter)); pFilter += mir_wstrlen(pFilter) + 1; if (pFilter >= szFilter + nSize) return nullptr; *pFilter = 0; return szFilter; } //////////////////////////////////////////////////////////////////////////////// // Save specified image entry // int gg_img_saveimage(HWND hwnd, GGIMAGEENTRY *dat) { if (!dat) return FALSE; GaduProto* gg = ((GGIMAGEDLGDATA *)GetWindowLongPtr(hwnd, GWLP_USERDATA))->gg; wchar_t szFilter[128]; gg_img_getfilter(szFilter, _countof(szFilter)); wchar_t szFileName[MAX_PATH]; wcsncpy(szFileName, dat->lpszFileName, _countof(szFileName)); OPENFILENAME ofn = { 0 }; ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; ofn.hwndOwner = hwnd; ofn.hInstance = g_plugin.getInst(); ofn.lpstrFile = szFileName; ofn.lpstrFilter = szFilter; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; if (GetSaveFileName(&ofn)) { FILE *fp = _wfopen(szFileName, L"w+b"); if (fp) { fwrite(dat->lpData, dat->nSize, 1, fp); fclose(fp); gg->debugLogW(L"gg_img_saveimage(): Image saved to %s.", szFileName); } else { gg->debugLogW(L"gg_img_saveimage(): Cannot save image to %s.", szFileName); MessageBox(hwnd, TranslateT("Image cannot be written to disk."), gg->m_tszUserName, MB_OK | MB_ICONERROR); } } return 0; } //////////////////////////////////////////////////////////////////////////// // Fit window size to image size // BOOL gg_img_fit(HWND hwndDlg) { GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); // Check if image is loaded if (!dat || !dat->lpImages || !dat->lpImages->hBitmap) return FALSE; GGIMAGEENTRY *img = dat->lpImages; // Go to last image while (img->lpNext && dat->lpImages->hBitmap) img = img->lpNext; // Get rects of display RECT dlgRect, imgRect; GetWindowRect(hwndDlg, &dlgRect); GetClientRect(GetDlgItem(hwndDlg, IDC_IMG_IMAGE), &imgRect); HDC hdc = GetDC(hwndDlg); BITMAP bmp; GetObject(img->hBitmap, sizeof(bmp), &bmp); int nWidth = bmp.bmWidth; int nHeight = bmp.bmHeight; RECT wrkRect; SystemParametersInfo(SPI_GETWORKAREA, 0, &wrkRect, 0); ReleaseDC(hwndDlg, hdc); int rWidth = 0; if ((imgRect.right - imgRect.left) < nWidth) rWidth = nWidth - imgRect.right + imgRect.left; int rHeight = 0; if ((imgRect.bottom - imgRect.top) < nHeight) rHeight = nHeight - imgRect.bottom + imgRect.top; // Check if anything needs resize if (!rWidth && !rHeight) return FALSE; int oWidth = dlgRect.right - dlgRect.left + rWidth; int oHeight = dlgRect.bottom - dlgRect.top + rHeight; if (oHeight > wrkRect.bottom - wrkRect.top) { oWidth = (int)((double)(wrkRect.bottom - wrkRect.top + imgRect.bottom - imgRect.top - dlgRect.bottom + dlgRect.top) * nWidth / nHeight) - imgRect.right + imgRect.left + dlgRect.right - dlgRect.left; if (oWidth < dlgRect.right - dlgRect.left) oWidth = dlgRect.right - dlgRect.left; oHeight = wrkRect.bottom - wrkRect.top; } if (oWidth > wrkRect.right - wrkRect.left) { oHeight = (int)((double)(wrkRect.right - wrkRect.left + imgRect.right - imgRect.left - dlgRect.right + dlgRect.left) * nHeight / nWidth) - imgRect.bottom + imgRect.top + dlgRect.bottom - dlgRect.top; if (oHeight < dlgRect.bottom - dlgRect.top) oHeight = dlgRect.bottom - dlgRect.top; oWidth = wrkRect.right - wrkRect.left; } SetWindowPos(hwndDlg, nullptr, (wrkRect.left + wrkRect.right - oWidth) / 2, (wrkRect.top + wrkRect.bottom - oHeight) / 2, oWidth, oHeight, SWP_SHOWWINDOW | SWP_NOZORDER /* | SWP_NOACTIVATE */); return TRUE; } //////////////////////////////////////////////////////////////////////////// // Dialog resizer procedure // static int sttImageDlgResizer(HWND, LPARAM, UTILRESIZECONTROL *urc) { switch (urc->wId) { case IDC_IMG_PREV: case IDC_IMG_NEXT: case IDC_IMG_DELETE: case IDC_IMG_SAVE: return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP; case IDC_IMG_IMAGE: return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORY_HEIGHT | RD_ANCHORX_WIDTH; case IDC_IMG_SEND: case IDC_IMG_CANCEL: return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM; } return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; } //////////////////////////////////////////////////////////////////////////// // Send / Recv main dialog procedure // static INT_PTR CALLBACK gg_img_dlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); switch (msg) { case WM_INITDIALOG: { TranslateDialogDefault(hwndDlg); // This should be already initialized // InitCommonControls(); // Get dialog data dat = (GGIMAGEDLGDATA *)lParam; SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); // Save dialog handle dat->hWnd = hwndDlg; // Send event if someone's waiting if (dat->hEvent) SetEvent(dat->hEvent); else dat->gg->debugLogA("gg_img_dlgproc(): WM_INITDIALOG Creation event not found, but someone might be waiting."); // Making buttons flat SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BUTTONSETASFLATBTN, TRUE, 0); SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("previous", FALSE)); SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Previous image"), BATF_UNICODE); SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BUTTONSETASFLATBTN, TRUE, 0); SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("next", FALSE)); SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Next image"), BATF_UNICODE); SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BUTTONSETASFLATBTN, TRUE, 0); SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("save", FALSE)); SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Save image to disk"), BATF_UNICODE); SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BUTTONSETASFLATBTN, TRUE, 0); SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("delete", FALSE)); SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Delete image from the list"), BATF_UNICODE); // Set main window image Window_SetIcon_IcoLib(hwndDlg, GetIconHandle(IDI_IMAGE)); wchar_t *szName = Clist_GetContactDisplayName(dat->hContact), szTitle[128]; if (dat->bReceiving) mir_snwprintf(szTitle, TranslateT("Image from %s"), szName); else mir_snwprintf(szTitle, TranslateT("Image for %s"), szName); SetWindowText(hwndDlg, szTitle); // Store client extents RECT rect; GetClientRect(hwndDlg, &rect); dat->minSize.cx = rect.right - rect.left; dat->minSize.cy = rect.bottom - rect.top; } return TRUE; case WM_SIZE: Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), dat->bReceiving ? MAKEINTRESOURCEA(IDD_IMAGE_RECV) : MAKEINTRESOURCEA(IDD_IMAGE_SEND), sttImageDlgResizer); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) InvalidateRect(hwndDlg, nullptr, FALSE); return 0; case WM_SIZING: { RECT *pRect = (RECT *)lParam; if (pRect->right - pRect->left < dat->minSize.cx) { if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT) pRect->left = pRect->right - dat->minSize.cx; else pRect->right = pRect->left + dat->minSize.cx; } if (pRect->bottom - pRect->top < dat->minSize.cy) { if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOP || wParam == WMSZ_TOPRIGHT) pRect->top = pRect->bottom - dat->minSize.cy; else pRect->bottom = pRect->top + dat->minSize.cy; } } return TRUE; case WM_CLOSE: EndDialog(hwndDlg, 0); break; // Flash the window case WM_TIMER: if (wParam == TIMERID_FLASHWND) FlashWindow(hwndDlg, TRUE); break; // Kill the timer case WM_ACTIVATE: if (LOWORD(wParam) != WA_ACTIVE) break; case WM_MOUSEACTIVATE: if (KillTimer(hwndDlg, TIMERID_FLASHWND)) FlashWindow(hwndDlg, FALSE); break; case WM_PAINT: if (dat->lpImages) { GGIMAGEENTRY *img = dat->lpImages; for (int i = 1; img && (i < dat->nImg); i++) img = img->lpNext; if (!img) { dat->gg->debugLogA("gg_img_dlgproc(): WM_PAINT Image was not found on the list. Cannot paint the window."); return FALSE; } if (dat->bReceiving) { wchar_t szTitle[128]; mir_snwprintf(szTitle, L"%s (%d / %d)", img->lpszFileName, dat->nImg, dat->nImgTotal); SetDlgItemText(hwndDlg, IDC_IMG_NAME, szTitle); } else SetDlgItemText(hwndDlg, IDC_IMG_NAME, img->lpszFileName); gg_img_paint(hwndDlg, img); } break; case WM_DESTROY: if (dat) { // Deleting all image entries GGIMAGEENTRY *temp, *img = dat->lpImages; GaduProto *gg = dat->gg; while (temp = img) { img = img->lpNext; gg_img_releasepicture(temp); } ReleaseIconEx("previous", FALSE); ReleaseIconEx("next", FALSE); ReleaseIconEx("delete", FALSE); ReleaseIconEx("save", FALSE); Window_FreeIcon_IcoLib(hwndDlg); gg->gg_EnterCriticalSection(&gg->img_mutex, "gg_img_dlgproc", 58, "img_mutex", 1); list_remove(&gg->imagedlgs, dat, 1); gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_dlgproc", 58, 1, "img_mutex", 1); } return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_IMG_CANCEL: EndDialog(hwndDlg, 0); return TRUE; case IDC_IMG_PREV: if (dat->nImg > 1) { dat->nImg--; InvalidateRect(hwndDlg, nullptr, FALSE); } return TRUE; case IDC_IMG_NEXT: if (dat->nImg < dat->nImgTotal) { dat->nImg++; InvalidateRect(hwndDlg, nullptr, FALSE); } return TRUE; case IDC_IMG_DELETE: { GGIMAGEENTRY *del, *img = dat->lpImages; if (dat->nImg == 1) { del = dat->lpImages; dat->lpImages = img->lpNext; } else { for (int i = 1; img && (i < dat->nImg - 1); i++) img = img->lpNext; if (!img) { dat->gg->debugLogA("gg_img_dlgproc(): IDC_IMG_DELETE Image was not found on the list. Cannot delete it from the list."); return FALSE; } del = img->lpNext; img->lpNext = del->lpNext; dat->nImg--; } if ((--dat->nImgTotal) == 0) EndDialog(hwndDlg, 0); else InvalidateRect(hwndDlg, nullptr, FALSE); gg_img_releasepicture(del); } return TRUE; case IDC_IMG_SAVE: { GGIMAGEENTRY *img = dat->lpImages; for (int i = 1; img && (i < dat->nImg); i++) img = img->lpNext; if (!img) { dat->gg->debugLogA("gg_img_dlgproc(): IDC_IMG_SAVE Image was not found on the list. Cannot launch saving."); return FALSE; } gg_img_saveimage(hwndDlg, img); } return TRUE; case IDC_IMG_SEND: { unsigned char format[20]; GaduProto *gg = dat->gg; if (dat->lpImages && gg->isonline()) { ((gg_msg_richtext*)format)->flag = 2; gg_msg_richtext_format *r = (gg_msg_richtext_format *)(format + sizeof(struct gg_msg_richtext)); r->position = 0; r->font = GG_FONT_IMAGE; gg_msg_richtext_image *p = (gg_msg_richtext_image *)(format + sizeof(struct gg_msg_richtext) + sizeof(struct gg_msg_richtext_format)); p->unknown1 = 0x109; p->size = dat->lpImages->nSize; dat->lpImages->crc32 = p->crc32 = gg_fix32(gg_crc32(0, (BYTE*)dat->lpImages->lpData, dat->lpImages->nSize)); int len = sizeof(struct gg_msg_richtext_format) + sizeof(struct gg_msg_richtext_image); ((gg_msg_richtext*)format)->length = len; uin_t uin = (uin_t)gg->getDword(dat->hContact, GG_KEY_UIN, 0); gg->gg_EnterCriticalSection(&gg->sess_mutex, "gg_img_dlgproc", 59, "sess_mutex", 1); gg_send_message_richtext(gg->m_sess, GG_CLASS_CHAT, uin, (unsigned char*)"\xA0\0", format, len + sizeof(struct gg_msg_richtext)); gg->gg_LeaveCriticalSection(&gg->sess_mutex, "gg_img_dlgproc", 59, 1, "sess_mutex", 1); // Protect dat from releasing SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); EndDialog(hwndDlg, 0); } return TRUE; } break; } break; case WM_ADDIMAGE: // lParam == GGIMAGEENTRY *dat { GGIMAGEENTRY *lpImage = (GGIMAGEENTRY *)lParam; GGIMAGEENTRY *lpImages = dat->lpImages; if (!dat->lpImages) // first image entry dat->lpImages = lpImage; else // adding at the end of the list { while (lpImages->lpNext) lpImages = lpImages->lpNext; lpImages->lpNext = lpImage; } dat->nImg = ++dat->nImgTotal; } // Fit window to image if (!gg_img_fit(hwndDlg)) InvalidateRect(hwndDlg, nullptr, FALSE); return TRUE; case WM_CHOOSEIMG: { wchar_t szFilter[128]; wchar_t szFileName[MAX_PATH]; OPENFILENAME ofn = { 0 }; gg_img_getfilter(szFilter, _countof(szFilter)); *szFileName = 0; ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; ofn.hwndOwner = hwndDlg; ofn.hInstance = g_plugin.getInst(); ofn.lpstrFilter = szFilter; ofn.lpstrFile = szFileName; ofn.nMaxFile = MAX_PATH; ofn.lpstrTitle = TranslateT("Select picture to send"); ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; if (GetOpenFileName(&ofn)) { if (dat->lpImages) gg_img_releasepicture(dat->lpImages); if (!(dat->lpImages = (GGIMAGEENTRY *)dat->gg->img_loadpicture(nullptr, szFileName))) { EndDialog(hwndDlg, 0); return FALSE; } if (!gg_img_fit(hwndDlg)) InvalidateRect(hwndDlg, nullptr, FALSE); } else { EndDialog(hwndDlg, 0); return FALSE; } return TRUE; } } return FALSE; } //////////////////////////////////////////////////////////////////////////// // Image dialog call thread // void __cdecl GaduProto::img_dlgcallthread(void *param) { debugLogA("img_dlgcallthread(): started."); HWND hMIWnd = nullptr; GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)param; DialogBoxParam(g_plugin.getInst(), dat->bReceiving ? MAKEINTRESOURCE(IDD_IMAGE_RECV) : MAKEINTRESOURCE(IDD_IMAGE_SEND), hMIWnd, gg_img_dlgproc, (LPARAM)dat); #ifdef DEBUGMODE debugLogA("img_dlgcallthread(): end."); #endif } //////////////////////////////////////////////////////////////////////////// // Open dialog receive for specified contact // GGIMAGEDLGDATA *gg_img_recvdlg(GaduProto *gg, MCONTACT hContact) { // Create dialog data GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA)); dat->hContact = hContact; dat->hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); dat->bReceiving = TRUE; dat->gg = gg; ResetEvent(dat->hEvent); #ifdef DEBUGMODE gg->debugLogA("gg_img_recvdlg(): ForkThread 18 GaduProto::img_dlgcallthread"); #endif gg->ForkThread(&GaduProto::img_dlgcallthread, dat); return dat; } //////////////////////////////////////////////////////////////////////////// // Checks if an image is already saved to the specified path // Returns 1 if yes, 0 if no or -1 if different image on this path is found // int gg_img_isexists(wchar_t *szPath, GGIMAGEENTRY *dat) { struct _stat st; if (_wstat(szPath, &st) != 0) return 0; if (st.st_size == (long)dat->nSize) { FILE *fp = _wfopen(szPath, L"rb"); if (!fp) return 0; char *lpData = (char*)mir_alloc(dat->nSize); if (fread(lpData, 1, dat->nSize, fp) == dat->nSize) { if (dat->crc32 == gg_fix32(gg_crc32(0, (BYTE*)lpData, dat->nSize)) || memcmp(lpData, dat->lpData, dat->nSize) == 0) { mir_free(lpData); fclose(fp); return 1; } } mir_free(lpData); fclose(fp); } return -1; } //////////////////////////////////////////////////////////////////////////// // Determine if image's file name has the proper extension // wchar_t *gg_img_hasextension(wchar_t *filename) { if (filename != nullptr && *filename != '\0') { wchar_t *imgtype = wcsrchr(filename, '.'); if (imgtype != nullptr) { size_t len = mir_wstrlen(imgtype); imgtype++; if (len == 4 && (mir_wstrcmpi(imgtype, L"bmp") == 0 || mir_wstrcmpi(imgtype, L"gif") == 0 || mir_wstrcmpi(imgtype, L"jpg") == 0 || mir_wstrcmpi(imgtype, L"png") == 0)) return --imgtype; if (len == 5 && mir_wstrcmpi(imgtype, L"jpeg") == 0) return --imgtype; } } return nullptr; } //////////////////////////////////////////////////////////////////////////////// // Display received image using message with [img] BBCode // int GaduProto::img_displayasmsg(MCONTACT hContact, void *img) { wchar_t szPath[MAX_PATH], path[MAX_PATH]; size_t tPathLen; if (hImagesFolder == nullptr || FoldersGetCustomPathT(hImagesFolder, path, MAX_PATH, L"")) { wchar_t *tmpPath = Utils_ReplaceVarsW(L"%miranda_userdata%"); tPathLen = mir_snwprintf(szPath, L"%s\\%s\\ImageCache", tmpPath, m_tszUserName); mir_free(tmpPath); } else { mir_wstrcpy(szPath, path); tPathLen = mir_wstrlen(szPath); } if (_waccess(szPath, 0)) { int ret = CreateDirectoryTreeW(szPath); if (ret == 0) { debugLogW(L"img_displayasmsg(): Created new directory for image cache: %s.", szPath); } else { debugLogW(L"img_displayasmsg(): Can not create directory for image cache: %s. errno=%d: %s", szPath, errno, strerror(errno)); wchar_t error[512]; mir_snwprintf(error, TranslateT("Cannot create image cache directory. ERROR: %d: %s\n%s"), errno, _tcserror(errno), szPath); showpopup(m_tszUserName, error, GG_POPUP_ERROR | GG_POPUP_ALLOW_MSGBOX | GG_POPUP_ONCE); } } GGIMAGEENTRY *dat = (GGIMAGEENTRY *)img; mir_snwprintf(szPath + tPathLen, MAX_PATH - tPathLen, L"\\%s", dat->lpszFileName); wchar_t *pImgext = gg_img_hasextension(szPath); if (pImgext == nullptr) pImgext = szPath + mir_wstrlen(szPath); wchar_t imgext[6]; wcsncpy_s(imgext, pImgext, _TRUNCATE); int res = -1; for (int i = 1; ; i++) { res = gg_img_isexists(szPath, dat); if (res != -1) break; mir_snwprintf(szPath, L"%.*s (%u)%s", pImgext - szPath, szPath, i, imgext); } if (res == 0) { // Image file not found, thus create it FILE *fp = _wfopen(szPath, L"w+b"); if (fp) { res = fwrite(dat->lpData, dat->nSize, 1, fp) > 0; fclose(fp); } else { debugLogW(L"img_displayasmsg(): Cannot open file %s for write image. errno=%d: %s", szPath, errno, strerror(errno)); wchar_t error[512]; mir_snwprintf(error, TranslateT("Cannot save received image to file. ERROR: %d: %s\n%s"), errno, _tcserror(errno), szPath); showpopup(m_tszUserName, error, GG_POPUP_ERROR); return 0; } } if (res != 0) { wchar_t image_msg[MAX_PATH + 11]; mir_snwprintf(image_msg, L"[img]%s[/img]", szPath); T2Utf szMessage(image_msg); PROTORECVEVENT pre = { 0 }; pre.timestamp = time(nullptr); pre.szMessage = szMessage; ProtoChainRecvMsg(hContact, &pre); debugLogW(L"img_displayasmsg(): Image saved to %s.", szPath); } else debugLogW(L"img_displayasmsg(): Cannot save image to %s.", szPath); return 0; } //////////////////////////////////////////////////////////////////////////// // Return if uin has it's window already opened // BOOL GaduProto::img_opened(uin_t uin) { list_t l = imagedlgs; while (l) { GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data; if (dat->uin == uin) return TRUE; l = l->next; } return FALSE; } //////////////////////////////////////////////////////////////////////////// // Image Module : Looking for window entry, create if not found // int GaduProto::img_display(MCONTACT hContact, void *img) { list_t l = imagedlgs; GGIMAGEDLGDATA *dat = nullptr; if (!img) return FALSE; // Look for already open dialog gg_EnterCriticalSection(&img_mutex, "img_display", 60, "img_mutex", 1); while (l) { dat = (GGIMAGEDLGDATA *)l->data; if (dat->bReceiving && dat->hContact == hContact) break; l = l->next; } if (!l) dat = nullptr; if (!dat) { dat = gg_img_recvdlg(this, hContact); dat->uin = getDword(hContact, GG_KEY_UIN, 0); while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0); CloseHandle(dat->hEvent); dat->hEvent = nullptr; list_add(&imagedlgs, dat, 0); } gg_LeaveCriticalSection(&img_mutex, "img_display", 60, 1, "img_mutex", 1); SendMessage(dat->hWnd, WM_ADDIMAGE, 0, (LPARAM)img); if (/*db_get_b(NULL, "Chat", "bFlashWindowHighlight", 0) != 0 && */ GetActiveWindow() != dat->hWnd && GetForegroundWindow() != dat->hWnd) { SetTimer(dat->hWnd, TIMERID_FLASHWND, 900, nullptr); } return TRUE; } //////////////////////////////////////////////////////////////////////////// // Helper function to determine image file format and the right extension // const wchar_t *gg_img_guessfileextension(const char *lpData) { if (lpData != nullptr) { if (memcmp(lpData, "BM", 2) == 0) return L".bmp"; if (memcmp(lpData, "GIF8", 4) == 0) return L".gif"; if (memcmp(lpData, "\xFF\xD8", 2) == 0) return L".jpg"; if (memcmp(lpData, "\x89PNG", 4) == 0) return L".png"; } return L""; } //////////////////////////////////////////////////////////////////////////// // Image Window : Loading picture and sending for display // void* GaduProto::img_loadpicture(gg_event* e, wchar_t *szFileName) { if (!szFileName && (!e || !e->event.image_reply.size || !e->event.image_reply.image || !e->event.image_reply.filename)) return nullptr; GGIMAGEENTRY *dat = (GGIMAGEENTRY *)calloc(1, sizeof(GGIMAGEENTRY)); if (dat == nullptr) return nullptr; // Copy the file name if (szFileName) { FILE *fp = _wfopen(szFileName, L"rb"); if (!fp) { free(dat); debugLogW(L"img_loadpicture(): fopen(\"%s\", \"rb\" failed. errno=%d: %s)", szFileName, errno, strerror(errno)); wchar_t error[512]; mir_snwprintf(error, TranslateT("Cannot open image file. ERROR: %d: %s\n%s"), errno, _tcserror(errno), szFileName); showpopup(m_tszUserName, error, GG_POPUP_ERROR); return nullptr; } fseek(fp, 0, SEEK_END); dat->nSize = ftell(fp); if (dat->nSize <= 0) { fclose(fp); free(dat); debugLogW(L"img_loadpicture(): Zero file size \"%s\" failed.", szFileName); return nullptr; } // Maximum acceptable image size if (dat->nSize > 255 * 1024) { fclose(fp); free(dat); debugLogW(L"img_loadpicture(): Image size of \"%s\" exceeds 255 KB.", szFileName); MessageBox(nullptr, TranslateT("Image exceeds maximum allowed size of 255 KB."), m_tszUserName, MB_OK | MB_ICONEXCLAMATION); return nullptr; } fseek(fp, 0, SEEK_SET); dat->lpData = (char*)malloc(dat->nSize); if (fread(dat->lpData, 1, dat->nSize, fp) < dat->nSize) { free(dat->lpData); fclose(fp); free(dat); debugLogW(L"img_loadpicture(): Reading file \"%s\" failed.", szFileName); return nullptr; } fclose(fp); dat->lpszFileName = wcsdup(szFileName); } // Copy picture from packet else if (e && e->event.image_reply.filename) { dat->nSize = e->event.image_reply.size; dat->lpData = (char*)malloc(dat->nSize); memcpy(dat->lpData, e->event.image_reply.image, dat->nSize); ptrW tmpFileName(mir_a2u(e->event.image_reply.filename)); if (!gg_img_hasextension(tmpFileName)) { // Add missing file extension const wchar_t *szImgType = gg_img_guessfileextension(dat->lpData); if (*szImgType) { dat->lpszFileName = (wchar_t*)calloc(sizeof(wchar_t), mir_wstrlen(tmpFileName) + mir_wstrlen(szImgType) + 1); if (dat->lpszFileName != nullptr) { mir_wstrcpy(dat->lpszFileName, tmpFileName); mir_wstrcat(dat->lpszFileName, szImgType); } } } if (dat->lpszFileName == nullptr) dat->lpszFileName = wcsdup(_A2T(e->event.image_reply.filename)); } //////////////////////////////////////////////////////////////////// // Loading picture using Miranda Image services if (!szFileName) // Load image from memory dat->hBitmap = Image_LoadFromMem(dat->lpData, dat->nSize, FIF_UNKNOWN); else dat->hBitmap = Bitmap_Load(szFileName); // Load image from file // If everything is fine return the handle if (dat->hBitmap) return dat; debugLogA("img_loadpicture(): MS_IMG_LOAD(MEM) failed."); if (dat) { free(dat->lpData); free(dat->lpszFileName); free(dat); } return nullptr; } //////////////////////////////////////////////////////////////////////////// // Image Recv : AddEvent proc // INT_PTR GaduProto::img_recvimage(WPARAM wParam, LPARAM lParam) { CLISTEVENT *cle = (CLISTEVENT *)lParam; GGIMAGEENTRY *img = (GGIMAGEENTRY *)cle->lParam; debugLogA("img_recvimage(%x, %x): Popup new image.", wParam, lParam); if (!img) return FALSE; img_display(cle->hContact, img); return FALSE; } //////////////////////////////////////////////////////////////////////////// // Windows queue management //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// // Removes dat structure // int gg_img_remove(GGIMAGEDLGDATA *dat) { GGIMAGEENTRY *temp = nullptr, *img = nullptr; GaduProto *gg; if (!dat) return FALSE; gg = dat->gg; gg->gg_EnterCriticalSection(&gg->img_mutex, "gg_img_remove", 61, "img_mutex", 1); // Remove the structure img = dat->lpImages; // Destroy picture handle while (temp = img) { img = img->lpNext; gg_img_releasepicture(temp); } // Remove from list list_remove(&gg->imagedlgs, dat, 1); gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_remove", 61, 1, "img_mutex", 1); return TRUE; } //////////////////////////////////////////////////////////////////////////// // GGIMAGEDLGDATA* gg_img_find(GaduProto *gg, uin_t uin, uint32_t crc32) { list_t l = gg->imagedlgs; GGIMAGEDLGDATA *dat; uin_t c_uin; gg->gg_EnterCriticalSection(&gg->img_mutex, "gg_img_find", 62, "img_mutex", 1); while (l) { dat = (GGIMAGEDLGDATA *)l->data; if (!dat) break; c_uin = dat->gg->getDword(dat->hContact, GG_KEY_UIN, 0); if (!dat->bReceiving && dat->lpImages && dat->lpImages->crc32 == crc32 && c_uin == uin) { gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_find", 62, 1, "img_mutex", 1); return dat; } l = l->next; } gg->gg_LeaveCriticalSection(&gg->img_mutex, "gg_img_find", 62, 2, "img_mutex", 1); gg->debugLogA("gg_img_find(): Image not found on the list. It might be released before calling this function."); return nullptr; } //////////////////////////////////////////////////////////////////////////// // Image Module : Send on Request // BOOL GaduProto::img_sendonrequest(gg_event* e) { GGIMAGEDLGDATA *dat = gg_img_find(this, e->event.image_request.sender, e->event.image_request.crc32); if (!dat || !isonline()) return FALSE; char* lpszFileNameA = mir_u2a(dat->lpImages->lpszFileName); gg_EnterCriticalSection(&sess_mutex, "img_sendonrequest", 63, "sess_mutex", 1); gg_image_reply(m_sess, e->event.image_request.sender, lpszFileNameA, dat->lpImages->lpData, dat->lpImages->nSize); gg_LeaveCriticalSection(&sess_mutex, "img_sendonrequest", 63, 1, "sess_mutex", 1); mir_free(lpszFileNameA); gg_img_remove(dat); return TRUE; } //////////////////////////////////////////////////////////////////////////// // Send Image : Run (Thread and main) // INT_PTR GaduProto::img_sendimg(WPARAM hContact, LPARAM) { GGIMAGEDLGDATA *dat = nullptr; gg_EnterCriticalSection(&img_mutex, "img_sendimg", 64, "img_mutex", 1); if (!dat) { dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA)); dat->hContact = hContact; dat->hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); dat->gg = this; ResetEvent(dat->hEvent); // Create new dialog #ifdef DEBUGMODE debugLogA("img_sendimg(): ForkThread 19 GaduProto::img_dlgcallthread"); #endif ForkThread(&GaduProto::img_dlgcallthread, dat); while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0); CloseHandle(dat->hEvent); dat->hEvent = nullptr; list_add(&imagedlgs, dat, 0); } // Request choose dialog SendMessage(dat->hWnd, WM_CHOOSEIMG, 0, 0); gg_LeaveCriticalSection(&img_mutex, "img_sendimg", 64, 1, "img_mutex", 1); return 0; }