/* * PROJECT: ReactOS Clipboard Viewer * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: Provides a view of the contents of the ReactOS clipboard. * COPYRIGHT: Copyright 2015-2018 Ricardo Hanke * Copyright 2015-2018 Hermes Belusca-Maito */ #include "precomp.h" static const WCHAR szClassName[] = L"ClipBookWClass"; CLIPBOARD_GLOBALS Globals; SCROLLSTATE Scrollstate; static void SaveClipboardToFile(void) { OPENFILENAMEW sfn; LPWSTR c; WCHAR szFileName[MAX_PATH]; WCHAR szFilterMask[MAX_STRING_LEN + 10]; ZeroMemory(&szFilterMask, sizeof(szFilterMask)); c = szFilterMask + LoadStringW(Globals.hInstance, STRING_FORMAT_NT, szFilterMask, MAX_STRING_LEN) + 1; wcscpy(c, L"*.clp"); ZeroMemory(&szFileName, sizeof(szFileName)); ZeroMemory(&sfn, sizeof(sfn)); sfn.lStructSize = sizeof(sfn); sfn.hwndOwner = Globals.hMainWnd; sfn.hInstance = Globals.hInstance; sfn.lpstrFilter = szFilterMask; sfn.lpstrFile = szFileName; sfn.nMaxFile = ARRAYSIZE(szFileName); sfn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; sfn.lpstrDefExt = L"clp"; if (!GetSaveFileNameW(&sfn)) return; if (!OpenClipboard(Globals.hMainWnd)) { ShowLastWin32Error(Globals.hMainWnd); return; } WriteClipboardFile(szFileName, CLIP_FMT_NT /* CLIP_FMT_31 */); CloseClipboard(); } static void LoadClipboardDataFromFile(LPWSTR lpszFileName) { if (MessageBoxRes(Globals.hMainWnd, Globals.hInstance, STRING_DELETE_MSG, STRING_DELETE_TITLE, MB_ICONWARNING | MB_YESNO) != IDYES) { return; } if (!OpenClipboard(Globals.hMainWnd)) { ShowLastWin32Error(Globals.hMainWnd); return; } EmptyClipboard(); ReadClipboardFile(lpszFileName); CloseClipboard(); } static void LoadClipboardFromFile(void) { OPENFILENAMEW ofn; LPWSTR c; WCHAR szFileName[MAX_PATH]; WCHAR szFilterMask[MAX_STRING_LEN + 10]; ZeroMemory(&szFilterMask, sizeof(szFilterMask)); c = szFilterMask + LoadStringW(Globals.hInstance, STRING_FORMAT_GEN, szFilterMask, MAX_STRING_LEN) + 1; wcscpy(c, L"*.clp"); ZeroMemory(&szFileName, sizeof(szFileName)); ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = Globals.hMainWnd; ofn.hInstance = Globals.hInstance; ofn.lpstrFilter = szFilterMask; ofn.lpstrFile = szFileName; ofn.nMaxFile = ARRAYSIZE(szFileName); ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_FILEMUSTEXIST; if (!GetOpenFileNameW(&ofn)) return; LoadClipboardDataFromFile(szFileName); } static void LoadClipboardFromDrop(HDROP hDrop) { WCHAR szFileName[MAX_PATH]; DragQueryFileW(hDrop, 0, szFileName, ARRAYSIZE(szFileName)); DragFinish(hDrop); LoadClipboardDataFromFile(szFileName); } static void SetDisplayFormat(UINT uFormat) { RECT rc; CheckMenuItem(Globals.hMenu, Globals.uCheckedItem, MF_BYCOMMAND | MF_UNCHECKED); Globals.uCheckedItem = uFormat + CMD_AUTOMATIC; CheckMenuItem(Globals.hMenu, Globals.uCheckedItem, MF_BYCOMMAND | MF_CHECKED); if (uFormat == 0) { Globals.uDisplayFormat = GetAutomaticClipboardFormat(); } else { Globals.uDisplayFormat = uFormat; } GetClipboardDataDimensions(Globals.uDisplayFormat, &rc); Scrollstate.CurrentX = Scrollstate.CurrentY = 0; Scrollstate.iWheelCarryoverX = Scrollstate.iWheelCarryoverY = 0; UpdateWindowScrollState(Globals.hMainWnd, rc.right, rc.bottom, &Scrollstate); InvalidateRect(Globals.hMainWnd, NULL, TRUE); } static void InitMenuPopup(HMENU hMenu, LPARAM index) { if ((GetMenuItemID(hMenu, 0) == CMD_DELETE) || (GetMenuItemID(hMenu, 1) == CMD_SAVE_AS)) { if (CountClipboardFormats() == 0) { EnableMenuItem(hMenu, CMD_DELETE, MF_GRAYED); EnableMenuItem(hMenu, CMD_SAVE_AS, MF_GRAYED); } else { EnableMenuItem(hMenu, CMD_DELETE, MF_ENABLED); EnableMenuItem(hMenu, CMD_SAVE_AS, MF_ENABLED); } } DrawMenuBar(Globals.hMainWnd); } static void UpdateDisplayMenu(void) { UINT uFormat; HMENU hMenu; WCHAR szFormatName[MAX_FMT_NAME_LEN + 1]; hMenu = GetSubMenu(Globals.hMenu, DISPLAY_MENU_POS); while (GetMenuItemCount(hMenu) > 1) { DeleteMenu(hMenu, 1, MF_BYPOSITION); } if (CountClipboardFormats() == 0) return; if (!OpenClipboard(Globals.hMainWnd)) return; AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL); /* Display the supported clipboard formats first */ for (uFormat = EnumClipboardFormats(0); uFormat; uFormat = EnumClipboardFormats(uFormat)) { if (IsClipboardFormatSupported(uFormat)) { RetrieveClipboardFormatName(Globals.hInstance, uFormat, TRUE, szFormatName, ARRAYSIZE(szFormatName)); AppendMenuW(hMenu, MF_STRING, CMD_AUTOMATIC + uFormat, szFormatName); } } /* Now display the unsupported clipboard formats */ for (uFormat = EnumClipboardFormats(0); uFormat; uFormat = EnumClipboardFormats(uFormat)) { if (!IsClipboardFormatSupported(uFormat)) { RetrieveClipboardFormatName(Globals.hInstance, uFormat, TRUE, szFormatName, ARRAYSIZE(szFormatName)); AppendMenuW(hMenu, MF_STRING | MF_GRAYED, 0, szFormatName); } } CloseClipboard(); } static int OnCommand(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (LOWORD(wParam)) { case CMD_OPEN: { LoadClipboardFromFile(); break; } case CMD_SAVE_AS: { SaveClipboardToFile(); break; } case CMD_EXIT: { PostMessageW(Globals.hMainWnd, WM_CLOSE, 0, 0); break; } case CMD_DELETE: { if (MessageBoxRes(Globals.hMainWnd, Globals.hInstance, STRING_DELETE_MSG, STRING_DELETE_TITLE, MB_ICONWARNING | MB_YESNO) != IDYES) { break; } DeleteClipboardContent(); break; } case CMD_AUTOMATIC: { SetDisplayFormat(0); break; } case CMD_HELP: { HtmlHelpW(Globals.hMainWnd, L"clipbrd.chm", 0, 0); break; } case CMD_ABOUT: { HICON hIcon; WCHAR szTitle[MAX_STRING_LEN]; hIcon = LoadIconW(Globals.hInstance, MAKEINTRESOURCE(CLIPBRD_ICON)); LoadStringW(Globals.hInstance, STRING_CLIPBOARD, szTitle, ARRAYSIZE(szTitle)); ShellAboutW(Globals.hMainWnd, szTitle, NULL, hIcon); DeleteObject(hIcon); break; } default: { break; } } return 0; } static void OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; COLORREF crOldBkColor, crOldTextColor; RECT rc; if (!OpenClipboard(Globals.hMainWnd)) return; hdc = BeginPaint(hWnd, &ps); /* Erase the background if needed */ if (ps.fErase) FillRect(ps.hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); /* Set the correct background and text colors */ crOldBkColor = SetBkColor(ps.hdc, GetSysColor(COLOR_WINDOW)); crOldTextColor = SetTextColor(ps.hdc, GetSysColor(COLOR_WINDOWTEXT)); /* Realize the clipboard palette if there is one */ RealizeClipboardPalette(ps.hdc); switch (Globals.uDisplayFormat) { case CF_NONE: { /* The clipboard is empty */ break; } case CF_DSPTEXT: case CF_TEXT: case CF_OEMTEXT: case CF_UNICODETEXT: { DrawTextFromClipboard(Globals.uDisplayFormat, ps, Scrollstate); break; } case CF_DSPBITMAP: case CF_BITMAP: { BitBltFromClipboard(ps, Scrollstate, SRCCOPY); break; } case CF_DIB: case CF_DIBV5: { SetDIBitsToDeviceFromClipboard(Globals.uDisplayFormat, ps, Scrollstate, DIB_RGB_COLORS); break; } case CF_DSPMETAFILEPICT: case CF_METAFILEPICT: { GetClientRect(hWnd, &rc); PlayMetaFileFromClipboard(hdc, &rc); break; } case CF_DSPENHMETAFILE: case CF_ENHMETAFILE: { GetClientRect(hWnd, &rc); PlayEnhMetaFileFromClipboard(hdc, &rc); break; } // case CF_PALETTE: // TODO: Draw a palette with squares filled with colors. // break; case CF_OWNERDISPLAY: { HGLOBAL hglb; PPAINTSTRUCT pps; hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(ps)); if (hglb) { pps = GlobalLock(hglb); CopyMemory(pps, &ps, sizeof(ps)); GlobalUnlock(hglb); SendClipboardOwnerMessage(TRUE, WM_PAINTCLIPBOARD, (WPARAM)hWnd, (LPARAM)hglb); GlobalFree(hglb); } break; } default: { GetClientRect(hWnd, &rc); DrawTextFromResource(Globals.hInstance, ERROR_UNSUPPORTED_FORMAT, hdc, &rc, DT_CENTER | DT_WORDBREAK | DT_NOPREFIX); break; } } /* Restore the original colors */ SetTextColor(ps.hdc, crOldTextColor); SetBkColor(ps.hdc, crOldBkColor); EndPaint(hWnd, &ps); CloseClipboard(); } static LRESULT WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: { TEXTMETRICW tm; HDC hDC = GetDC(hWnd); /* * Note that the method with GetObjectW just returns * the original parameters with which the font was created. */ if (GetTextMetricsW(hDC, &tm)) { Globals.CharWidth = tm.tmMaxCharWidth; // tm.tmAveCharWidth; Globals.CharHeight = tm.tmHeight + tm.tmExternalLeading; } ReleaseDC(hWnd, hDC); Globals.hMenu = GetMenu(hWnd); Globals.hWndNext = SetClipboardViewer(hWnd); // For now, the Help dialog item is disabled because of lacking of HTML support EnableMenuItem(Globals.hMenu, CMD_HELP, MF_BYCOMMAND | MF_GRAYED); UpdateLinesToScroll(&Scrollstate); UpdateDisplayMenu(); SetDisplayFormat(0); DragAcceptFiles(hWnd, TRUE); break; } case WM_CLOSE: { DestroyWindow(hWnd); break; } case WM_DESTROY: { ChangeClipboardChain(hWnd, Globals.hWndNext); if (Globals.uDisplayFormat == CF_OWNERDISPLAY) { HGLOBAL hglb; PRECT prc; hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(*prc)); if (hglb) { prc = GlobalLock(hglb); SetRectEmpty(prc); GlobalUnlock(hglb); SendClipboardOwnerMessage(TRUE, WM_SIZECLIPBOARD, (WPARAM)hWnd, (LPARAM)hglb); GlobalFree(hglb); } } PostQuitMessage(0); break; } case WM_PAINT: { OnPaint(hWnd, wParam, lParam); break; } case WM_KEYDOWN: { OnKeyScroll(hWnd, wParam, lParam, &Scrollstate); break; } case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: { OnMouseScroll(hWnd, uMsg, wParam, lParam, &Scrollstate); break; } case WM_HSCROLL: { // NOTE: Windows uses an offset of 16 pixels OnScroll(hWnd, SB_HORZ, wParam, 5, &Scrollstate); break; } case WM_VSCROLL: { // NOTE: Windows uses an offset of 16 pixels OnScroll(hWnd, SB_VERT, wParam, 5, &Scrollstate); break; } case WM_SIZE: { RECT rc; if (Globals.uDisplayFormat == CF_OWNERDISPLAY) { HGLOBAL hglb; PRECT prc; hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(*prc)); if (hglb) { prc = GlobalLock(hglb); if (wParam == SIZE_MINIMIZED) SetRectEmpty(prc); else GetClientRect(hWnd, prc); GlobalUnlock(hglb); SendClipboardOwnerMessage(TRUE, WM_SIZECLIPBOARD, (WPARAM)hWnd, (LPARAM)hglb); GlobalFree(hglb); } break; } GetClipboardDataDimensions(Globals.uDisplayFormat, &rc); UpdateWindowScrollState(hWnd, rc.right, rc.bottom, &Scrollstate); // NOTE: There still are little problems drawing // the background when displaying clipboard text. if (!IsClipboardFormatSupported(Globals.uDisplayFormat) || Globals.uDisplayFormat == CF_DSPTEXT || Globals.uDisplayFormat == CF_TEXT || Globals.uDisplayFormat == CF_OEMTEXT || Globals.uDisplayFormat == CF_UNICODETEXT) { InvalidateRect(Globals.hMainWnd, NULL, TRUE); } else { InvalidateRect(Globals.hMainWnd, NULL, FALSE); } break; } case WM_CHANGECBCHAIN: { /* Transmit through the clipboard viewer chain */ if ((HWND)wParam == Globals.hWndNext) { Globals.hWndNext = (HWND)lParam; } else if (Globals.hWndNext != NULL) { SendMessageW(Globals.hWndNext, uMsg, wParam, lParam); } break; } case WM_DESTROYCLIPBOARD: break; case WM_RENDERALLFORMATS: { /* * When the user has cleared the clipboard via the DELETE command, * we (clipboard viewer) become the clipboard owner. When we are * subsequently closed, this message is then sent to us so that * we get a chance to render everything we can. Since we don't have * anything to render, just empty the clipboard. */ DeleteClipboardContent(); break; } case WM_RENDERFORMAT: // TODO! break; case WM_DRAWCLIPBOARD: { UpdateDisplayMenu(); SetDisplayFormat(0); /* Pass the message to the next window in clipboard viewer chain */ SendMessageW(Globals.hWndNext, uMsg, wParam, lParam); break; } case WM_COMMAND: { if ((LOWORD(wParam) > CMD_AUTOMATIC)) { SetDisplayFormat(LOWORD(wParam) - CMD_AUTOMATIC); } else { OnCommand(hWnd, uMsg, wParam, lParam); } break; } case WM_INITMENUPOPUP: { InitMenuPopup((HMENU)wParam, lParam); break; } case WM_DROPFILES: { LoadClipboardFromDrop((HDROP)wParam); break; } case WM_PALETTECHANGED: { /* Ignore if this comes from ourselves */ if ((HWND)wParam == hWnd) break; /* Fall back to WM_QUERYNEWPALETTE */ } case WM_QUERYNEWPALETTE: { BOOL Success; HDC hDC; if (!OpenClipboard(Globals.hMainWnd)) return FALSE; hDC = GetDC(hWnd); if (!hDC) { CloseClipboard(); return FALSE; } Success = RealizeClipboardPalette(hDC); ReleaseDC(hWnd, hDC); CloseClipboard(); if (Success) { InvalidateRect(hWnd, NULL, TRUE); UpdateWindow(hWnd); return TRUE; } return FALSE; } case WM_SYSCOLORCHANGE: { SetDisplayFormat(Globals.uDisplayFormat); break; } case WM_SETTINGCHANGE: { if (wParam == SPI_SETWHEELSCROLLLINES) { UpdateLinesToScroll(&Scrollstate); } break; } default: { return DefWindowProc(hWnd, uMsg, wParam, lParam); } } return 0; } int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { MSG msg; HACCEL hAccel; HWND hPrevWindow; WNDCLASSEXW wndclass; WCHAR szBuffer[MAX_STRING_LEN]; hPrevWindow = FindWindowW(szClassName, NULL); if (hPrevWindow) { BringWindowToFront(hPrevWindow); return 0; } switch (GetUserDefaultUILanguage()) { case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT): SetProcessDefaultLayout(LAYOUT_RTL); break; default: break; } ZeroMemory(&Globals, sizeof(Globals)); Globals.hInstance = hInstance; ZeroMemory(&wndclass, sizeof(wndclass)); wndclass.cbSize = sizeof(wndclass); wndclass.lpfnWndProc = MainWndProc; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(CLIPBRD_ICON)); wndclass.hCursor = LoadCursorW(0, IDC_ARROW); wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wndclass.lpszMenuName = MAKEINTRESOURCEW(MAIN_MENU); wndclass.lpszClassName = szClassName; if (!RegisterClassExW(&wndclass)) { ShowLastWin32Error(NULL); return 0; } ZeroMemory(&Scrollstate, sizeof(Scrollstate)); LoadStringW(hInstance, STRING_CLIPBOARD, szBuffer, ARRAYSIZE(szBuffer)); Globals.hMainWnd = CreateWindowExW(WS_EX_CLIENTEDGE | WS_EX_ACCEPTFILES, szClassName, szBuffer, WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, Globals.hInstance, NULL); if (!Globals.hMainWnd) { ShowLastWin32Error(NULL); return 0; } ShowWindow(Globals.hMainWnd, nCmdShow); UpdateWindow(Globals.hMainWnd); hAccel = LoadAcceleratorsW(Globals.hInstance, MAKEINTRESOURCEW(ID_ACCEL)); if (!hAccel) { ShowLastWin32Error(Globals.hMainWnd); } /* If the user provided a path to a clipboard data file, try to open it */ if (__argc >= 2) LoadClipboardDataFromFile(__wargv[1]); while (GetMessageW(&msg, 0, 0, 0)) { if (!TranslateAcceleratorW(Globals.hMainWnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessageW(&msg); } } return (int)msg.wParam; }