2 * PROJECT: ReactOS Clipboard Viewer
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Provides a view of the contents of the ReactOS clipboard.
5 * COPYRIGHT: Copyright 2015-2018 Ricardo Hanke
6 * Copyright 2015-2018 Hermes Belusca-Maito
11 static const WCHAR szClassName
[] = L
"ClipBookWClass";
13 CLIPBOARD_GLOBALS Globals
;
14 SCROLLSTATE Scrollstate
;
16 static void SaveClipboardToFile(void)
20 WCHAR szFileName
[MAX_PATH
];
21 WCHAR szFilterMask
[MAX_STRING_LEN
+ 10];
23 ZeroMemory(&szFilterMask
, sizeof(szFilterMask
));
24 c
= szFilterMask
+ LoadStringW(Globals
.hInstance
, STRING_FORMAT_NT
, szFilterMask
, MAX_STRING_LEN
) + 1;
27 ZeroMemory(&szFileName
, sizeof(szFileName
));
28 ZeroMemory(&sfn
, sizeof(sfn
));
29 sfn
.lStructSize
= sizeof(sfn
);
30 sfn
.hwndOwner
= Globals
.hMainWnd
;
31 sfn
.hInstance
= Globals
.hInstance
;
32 sfn
.lpstrFilter
= szFilterMask
;
33 sfn
.lpstrFile
= szFileName
;
34 sfn
.nMaxFile
= ARRAYSIZE(szFileName
);
35 sfn
.Flags
= OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_OVERWRITEPROMPT
;
36 sfn
.lpstrDefExt
= L
"clp";
38 if (!GetSaveFileNameW(&sfn
))
41 if (!OpenClipboard(Globals
.hMainWnd
))
43 ShowLastWin32Error(Globals
.hMainWnd
);
47 WriteClipboardFile(szFileName
, CLIP_FMT_NT
/* CLIP_FMT_31 */);
52 static void LoadClipboardDataFromFile(LPWSTR lpszFileName
)
54 if (MessageBoxRes(Globals
.hMainWnd
, Globals
.hInstance
,
55 STRING_DELETE_MSG
, STRING_DELETE_TITLE
,
56 MB_ICONWARNING
| MB_YESNO
) != IDYES
)
61 if (!OpenClipboard(Globals
.hMainWnd
))
63 ShowLastWin32Error(Globals
.hMainWnd
);
68 ReadClipboardFile(lpszFileName
);
73 static void LoadClipboardFromFile(void)
77 WCHAR szFileName
[MAX_PATH
];
78 WCHAR szFilterMask
[MAX_STRING_LEN
+ 10];
80 ZeroMemory(&szFilterMask
, sizeof(szFilterMask
));
81 c
= szFilterMask
+ LoadStringW(Globals
.hInstance
, STRING_FORMAT_GEN
, szFilterMask
, MAX_STRING_LEN
) + 1;
84 ZeroMemory(&szFileName
, sizeof(szFileName
));
85 ZeroMemory(&ofn
, sizeof(ofn
));
86 ofn
.lStructSize
= sizeof(ofn
);
87 ofn
.hwndOwner
= Globals
.hMainWnd
;
88 ofn
.hInstance
= Globals
.hInstance
;
89 ofn
.lpstrFilter
= szFilterMask
;
90 ofn
.lpstrFile
= szFileName
;
91 ofn
.nMaxFile
= ARRAYSIZE(szFileName
);
92 ofn
.Flags
= OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_FILEMUSTEXIST
;
94 if (!GetOpenFileNameW(&ofn
))
97 LoadClipboardDataFromFile(szFileName
);
100 static void LoadClipboardFromDrop(HDROP hDrop
)
102 WCHAR szFileName
[MAX_PATH
];
104 DragQueryFileW(hDrop
, 0, szFileName
, ARRAYSIZE(szFileName
));
107 LoadClipboardDataFromFile(szFileName
);
110 static void SetDisplayFormat(UINT uFormat
)
114 CheckMenuItem(Globals
.hMenu
, Globals
.uCheckedItem
, MF_BYCOMMAND
| MF_UNCHECKED
);
115 Globals
.uCheckedItem
= uFormat
+ CMD_AUTOMATIC
;
116 CheckMenuItem(Globals
.hMenu
, Globals
.uCheckedItem
, MF_BYCOMMAND
| MF_CHECKED
);
120 Globals
.uDisplayFormat
= GetAutomaticClipboardFormat();
124 Globals
.uDisplayFormat
= uFormat
;
127 GetClipboardDataDimensions(Globals
.uDisplayFormat
, &rc
);
128 Scrollstate
.CurrentX
= Scrollstate
.CurrentY
= 0;
129 Scrollstate
.iWheelCarryoverX
= Scrollstate
.iWheelCarryoverY
= 0;
130 UpdateWindowScrollState(Globals
.hMainWnd
, rc
.right
, rc
.bottom
, &Scrollstate
);
132 InvalidateRect(Globals
.hMainWnd
, NULL
, TRUE
);
135 static void InitMenuPopup(HMENU hMenu
, LPARAM index
)
137 if ((GetMenuItemID(hMenu
, 0) == CMD_DELETE
) || (GetMenuItemID(hMenu
, 1) == CMD_SAVE_AS
))
139 if (CountClipboardFormats() == 0)
141 EnableMenuItem(hMenu
, CMD_DELETE
, MF_GRAYED
);
142 EnableMenuItem(hMenu
, CMD_SAVE_AS
, MF_GRAYED
);
146 EnableMenuItem(hMenu
, CMD_DELETE
, MF_ENABLED
);
147 EnableMenuItem(hMenu
, CMD_SAVE_AS
, MF_ENABLED
);
151 DrawMenuBar(Globals
.hMainWnd
);
154 static void UpdateDisplayMenu(void)
158 WCHAR szFormatName
[MAX_FMT_NAME_LEN
+ 1];
160 hMenu
= GetSubMenu(Globals
.hMenu
, DISPLAY_MENU_POS
);
162 while (GetMenuItemCount(hMenu
) > 1)
164 DeleteMenu(hMenu
, 1, MF_BYPOSITION
);
167 if (CountClipboardFormats() == 0)
170 if (!OpenClipboard(Globals
.hMainWnd
))
173 AppendMenuW(hMenu
, MF_SEPARATOR
, 0, NULL
);
175 /* Display the supported clipboard formats first */
176 for (uFormat
= EnumClipboardFormats(0); uFormat
;
177 uFormat
= EnumClipboardFormats(uFormat
))
179 if (IsClipboardFormatSupported(uFormat
))
181 RetrieveClipboardFormatName(Globals
.hInstance
, uFormat
, TRUE
,
182 szFormatName
, ARRAYSIZE(szFormatName
));
183 AppendMenuW(hMenu
, MF_STRING
, CMD_AUTOMATIC
+ uFormat
, szFormatName
);
187 /* Now display the unsupported clipboard formats */
188 for (uFormat
= EnumClipboardFormats(0); uFormat
;
189 uFormat
= EnumClipboardFormats(uFormat
))
191 if (!IsClipboardFormatSupported(uFormat
))
193 RetrieveClipboardFormatName(Globals
.hInstance
, uFormat
, TRUE
,
194 szFormatName
, ARRAYSIZE(szFormatName
));
195 AppendMenuW(hMenu
, MF_STRING
| MF_GRAYED
, 0, szFormatName
);
202 static int OnCommand(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
204 switch (LOWORD(wParam
))
208 LoadClipboardFromFile();
214 SaveClipboardToFile();
220 PostMessageW(Globals
.hMainWnd
, WM_CLOSE
, 0, 0);
226 if (MessageBoxRes(Globals
.hMainWnd
, Globals
.hInstance
,
227 STRING_DELETE_MSG
, STRING_DELETE_TITLE
,
228 MB_ICONWARNING
| MB_YESNO
) != IDYES
)
233 DeleteClipboardContent();
245 HtmlHelpW(Globals
.hMainWnd
, L
"clipbrd.chm", 0, 0);
252 WCHAR szTitle
[MAX_STRING_LEN
];
254 hIcon
= LoadIconW(Globals
.hInstance
, MAKEINTRESOURCE(CLIPBRD_ICON
));
255 LoadStringW(Globals
.hInstance
, STRING_CLIPBOARD
, szTitle
, ARRAYSIZE(szTitle
));
256 ShellAboutW(Globals
.hMainWnd
, szTitle
, NULL
, hIcon
);
269 static void OnPaint(HWND hWnd
, WPARAM wParam
, LPARAM lParam
)
273 COLORREF crOldBkColor
, crOldTextColor
;
276 if (!OpenClipboard(Globals
.hMainWnd
))
279 hdc
= BeginPaint(hWnd
, &ps
);
281 /* Erase the background if needed */
283 FillRect(ps
.hdc
, &ps
.rcPaint
, (HBRUSH
)(COLOR_WINDOW
+ 1));
285 /* Set the correct background and text colors */
286 crOldBkColor
= SetBkColor(ps
.hdc
, GetSysColor(COLOR_WINDOW
));
287 crOldTextColor
= SetTextColor(ps
.hdc
, GetSysColor(COLOR_WINDOWTEXT
));
289 /* Realize the clipboard palette if there is one */
290 RealizeClipboardPalette(ps
.hdc
);
292 switch (Globals
.uDisplayFormat
)
296 /* The clipboard is empty */
305 DrawTextFromClipboard(Globals
.uDisplayFormat
, ps
, Scrollstate
);
312 BitBltFromClipboard(ps
, Scrollstate
, SRCCOPY
);
319 SetDIBitsToDeviceFromClipboard(Globals
.uDisplayFormat
, ps
, Scrollstate
, DIB_RGB_COLORS
);
323 case CF_DSPMETAFILEPICT
:
324 case CF_METAFILEPICT
:
326 GetClientRect(hWnd
, &rc
);
327 PlayMetaFileFromClipboard(hdc
, &rc
);
331 case CF_DSPENHMETAFILE
:
334 GetClientRect(hWnd
, &rc
);
335 PlayEnhMetaFileFromClipboard(hdc
, &rc
);
340 // TODO: Draw a palette with squares filled with colors.
343 case CF_OWNERDISPLAY
:
348 hglb
= GlobalAlloc(GMEM_MOVEABLE
, sizeof(ps
));
351 pps
= GlobalLock(hglb
);
352 CopyMemory(pps
, &ps
, sizeof(ps
));
355 SendClipboardOwnerMessage(TRUE
, WM_PAINTCLIPBOARD
,
356 (WPARAM
)hWnd
, (LPARAM
)hglb
);
365 GetClientRect(hWnd
, &rc
);
366 DrawTextFromResource(Globals
.hInstance
, ERROR_UNSUPPORTED_FORMAT
,
367 hdc
, &rc
, DT_CENTER
| DT_WORDBREAK
| DT_NOPREFIX
);
372 /* Restore the original colors */
373 SetTextColor(ps
.hdc
, crOldTextColor
);
374 SetBkColor(ps
.hdc
, crOldBkColor
);
381 static LRESULT WINAPI
MainWndProc(HWND hWnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
388 HDC hDC
= GetDC(hWnd
);
391 * Note that the method with GetObjectW just returns
392 * the original parameters with which the font was created.
394 if (GetTextMetricsW(hDC
, &tm
))
396 Globals
.CharWidth
= tm
.tmMaxCharWidth
; // tm.tmAveCharWidth;
397 Globals
.CharHeight
= tm
.tmHeight
+ tm
.tmExternalLeading
;
399 ReleaseDC(hWnd
, hDC
);
402 Globals
.hMenu
= GetMenu(hWnd
);
403 Globals
.hWndNext
= SetClipboardViewer(hWnd
);
405 // For now, the Help dialog item is disabled because of lacking of HTML support
406 EnableMenuItem(Globals
.hMenu
, CMD_HELP
, MF_BYCOMMAND
| MF_GRAYED
);
408 UpdateLinesToScroll(&Scrollstate
);
413 DragAcceptFiles(hWnd
, TRUE
);
425 ChangeClipboardChain(hWnd
, Globals
.hWndNext
);
427 if (Globals
.uDisplayFormat
== CF_OWNERDISPLAY
)
432 hglb
= GlobalAlloc(GMEM_MOVEABLE
, sizeof(*prc
));
435 prc
= GlobalLock(hglb
);
439 SendClipboardOwnerMessage(TRUE
, WM_SIZECLIPBOARD
,
440 (WPARAM
)hWnd
, (LPARAM
)hglb
);
452 OnPaint(hWnd
, wParam
, lParam
);
458 OnKeyScroll(hWnd
, wParam
, lParam
, &Scrollstate
);
465 OnMouseScroll(hWnd
, uMsg
, wParam
, lParam
, &Scrollstate
);
471 // NOTE: Windows uses an offset of 16 pixels
472 OnScroll(hWnd
, SB_HORZ
, wParam
, 5, &Scrollstate
);
478 // NOTE: Windows uses an offset of 16 pixels
479 OnScroll(hWnd
, SB_VERT
, wParam
, 5, &Scrollstate
);
487 if (Globals
.uDisplayFormat
== CF_OWNERDISPLAY
)
492 hglb
= GlobalAlloc(GMEM_MOVEABLE
, sizeof(*prc
));
495 prc
= GlobalLock(hglb
);
496 if (wParam
== SIZE_MINIMIZED
)
499 GetClientRect(hWnd
, prc
);
502 SendClipboardOwnerMessage(TRUE
, WM_SIZECLIPBOARD
,
503 (WPARAM
)hWnd
, (LPARAM
)hglb
);
510 GetClipboardDataDimensions(Globals
.uDisplayFormat
, &rc
);
511 UpdateWindowScrollState(hWnd
, rc
.right
, rc
.bottom
, &Scrollstate
);
513 // NOTE: There still are little problems drawing
514 // the background when displaying clipboard text.
515 if (!IsClipboardFormatSupported(Globals
.uDisplayFormat
) ||
516 Globals
.uDisplayFormat
== CF_DSPTEXT
||
517 Globals
.uDisplayFormat
== CF_TEXT
||
518 Globals
.uDisplayFormat
== CF_OEMTEXT
||
519 Globals
.uDisplayFormat
== CF_UNICODETEXT
)
521 InvalidateRect(Globals
.hMainWnd
, NULL
, TRUE
);
525 InvalidateRect(Globals
.hMainWnd
, NULL
, FALSE
);
531 case WM_CHANGECBCHAIN
:
533 /* Transmit through the clipboard viewer chain */
534 if ((HWND
)wParam
== Globals
.hWndNext
)
536 Globals
.hWndNext
= (HWND
)lParam
;
538 else if (Globals
.hWndNext
!= NULL
)
540 SendMessageW(Globals
.hWndNext
, uMsg
, wParam
, lParam
);
546 case WM_DESTROYCLIPBOARD
:
549 case WM_RENDERALLFORMATS
:
552 * When the user has cleared the clipboard via the DELETE command,
553 * we (clipboard viewer) become the clipboard owner. When we are
554 * subsequently closed, this message is then sent to us so that
555 * we get a chance to render everything we can. Since we don't have
556 * anything to render, just empty the clipboard.
558 DeleteClipboardContent();
562 case WM_RENDERFORMAT
:
566 case WM_DRAWCLIPBOARD
:
571 /* Pass the message to the next window in clipboard viewer chain */
572 SendMessageW(Globals
.hWndNext
, uMsg
, wParam
, lParam
);
578 if ((LOWORD(wParam
) > CMD_AUTOMATIC
))
580 SetDisplayFormat(LOWORD(wParam
) - CMD_AUTOMATIC
);
584 OnCommand(hWnd
, uMsg
, wParam
, lParam
);
589 case WM_INITMENUPOPUP
:
591 InitMenuPopup((HMENU
)wParam
, lParam
);
597 LoadClipboardFromDrop((HDROP
)wParam
);
601 case WM_PALETTECHANGED
:
603 /* Ignore if this comes from ourselves */
604 if ((HWND
)wParam
== hWnd
)
607 /* Fall back to WM_QUERYNEWPALETTE */
610 case WM_QUERYNEWPALETTE
:
615 if (!OpenClipboard(Globals
.hMainWnd
))
625 Success
= RealizeClipboardPalette(hDC
);
627 ReleaseDC(hWnd
, hDC
);
632 InvalidateRect(hWnd
, NULL
, TRUE
);
639 case WM_SYSCOLORCHANGE
:
641 SetDisplayFormat(Globals
.uDisplayFormat
);
645 case WM_SETTINGCHANGE
:
647 if (wParam
== SPI_SETWHEELSCROLLLINES
)
649 UpdateLinesToScroll(&Scrollstate
);
656 return DefWindowProc(hWnd
, uMsg
, wParam
, lParam
);
663 int WINAPI
wWinMain(HINSTANCE hInstance
, HINSTANCE hPrevInstance
, LPWSTR lpCmdLine
, int nCmdShow
)
668 WNDCLASSEXW wndclass
;
669 WCHAR szBuffer
[MAX_STRING_LEN
];
671 hPrevWindow
= FindWindowW(szClassName
, NULL
);
674 BringWindowToFront(hPrevWindow
);
678 switch (GetUserDefaultUILanguage())
680 case MAKELANGID(LANG_HEBREW
, SUBLANG_DEFAULT
):
681 SetProcessDefaultLayout(LAYOUT_RTL
);
688 ZeroMemory(&Globals
, sizeof(Globals
));
689 Globals
.hInstance
= hInstance
;
691 ZeroMemory(&wndclass
, sizeof(wndclass
));
692 wndclass
.cbSize
= sizeof(wndclass
);
693 wndclass
.lpfnWndProc
= MainWndProc
;
694 wndclass
.hInstance
= hInstance
;
695 wndclass
.hIcon
= LoadIconW(hInstance
, MAKEINTRESOURCEW(CLIPBRD_ICON
));
696 wndclass
.hCursor
= LoadCursorW(0, IDC_ARROW
);
697 wndclass
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+ 1);
698 wndclass
.lpszMenuName
= MAKEINTRESOURCEW(MAIN_MENU
);
699 wndclass
.lpszClassName
= szClassName
;
701 if (!RegisterClassExW(&wndclass
))
703 ShowLastWin32Error(NULL
);
707 ZeroMemory(&Scrollstate
, sizeof(Scrollstate
));
709 LoadStringW(hInstance
, STRING_CLIPBOARD
, szBuffer
, ARRAYSIZE(szBuffer
));
710 Globals
.hMainWnd
= CreateWindowExW(WS_EX_CLIENTEDGE
| WS_EX_ACCEPTFILES
,
713 WS_OVERLAPPEDWINDOW
| WS_HSCROLL
| WS_VSCROLL
,
722 if (!Globals
.hMainWnd
)
724 ShowLastWin32Error(NULL
);
728 ShowWindow(Globals
.hMainWnd
, nCmdShow
);
729 UpdateWindow(Globals
.hMainWnd
);
731 hAccel
= LoadAcceleratorsW(Globals
.hInstance
, MAKEINTRESOURCEW(ID_ACCEL
));
734 ShowLastWin32Error(Globals
.hMainWnd
);
737 /* If the user provided a path to a clipboard data file, try to open it */
739 LoadClipboardDataFromFile(__wargv
[1]);
741 while (GetMessageW(&msg
, 0, 0, 0))
743 if (!TranslateAcceleratorW(Globals
.hMainWnd
, hAccel
, &msg
))
745 TranslateMessage(&msg
);
746 DispatchMessageW(&msg
);
750 return (int)msg
.wParam
;