검색결과 리스트
글
이제 몇 개만 더 구현해 주면 끝이 납니다.
하나씩 구현해 보도록 하죠.
핵심적인 부분만 설명하도록 하겠습니다. 자세한건 패키지 전체 소스를 받아서 보시기 바랍니다.
1. Keyboard Event 받기
- 후킹을 통해서 입력된 키값을 얻어와야 합니다. 그리고나서 기획 의도대로 'Win+C', 'Win+V' 가 눌러진 것을 인식해야 하죠.
후킹 프로시져를 보면 다음과 같습니다.
// Keyboard Hook Proc
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 0 은 무시
if(nCode < 0) { return 1; }
int return_code = 1;
// 키값 가져옴
DWORD vKey = ((tagKBDLLHOOKSTRUCT *)lParam)->vkCode;
// ESC 는 허용
if(wParam == WM_KEYDOWN && vKey != VK_ESCAPE && g_IsCaptureMode == TRUE) { return -1; }
switch(wParam) {
case WM_KEYDOWN:
// 윈도우 키가 함께 눌러진 경우에만
if(GetAsyncKeyState(VK_LWIN) != NULL) {
if(vKey == 0x43 && g_IsCaptureMode == FALSE && m_bIsSaveMode == FALSE) { // Win + C
g_IsCaptureMode = TRUE;
PostMessage(m_hMainWnd, WM_WIN_C, wParam, lParam);
return_code = -1;
} else if(vKey == 0x56 && g_IsCaptureMode == FALSE && m_bIsSaveMode == FALSE) { // Win + V
PostMessage(m_hMainWnd, WM_WIN_V, wParam, lParam);
return_code = -1;
}
}
break;
}
return return_code;
}
일단 'Win' 키가 함께 눌러졌는지 확인하기 위해서 GetAsyncKeyState(VK_LWIN) 를 사용했습니다. 이는 특정 키값이 눌러진 상태인지 아닌지를 알려 줍니다.
즉, 'C' 가 눌러졌을때, Win 키도 눌러진 상태인지 확인하면 되겠지요.
그리고 원하는 이벤트(Win+C / Win+V)가 발생했을 때, 메인 윈도우로 PostMessage 를 해 줍니다.
물론 메세지는 사용자 등록 메세지입니다.(WM_WIN_C / WM_WIN_V)
이런 방식을 쓸 수 밖에 없는 이유는 후킹 프로시저는 global 함수라서 Class 에 접근할 수 없기 때문입니다.
사용자 메세지를 수신받아 처리하기 위해서 핸들러를 등록해 주었습니다.
BEGIN_MESSAGE_MAP(CEasyClipperDlg, CDialog)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
//}}AFX_MSG_MAP
ON_MESSAGE(WM_TRAY_ICON, OnTrayNotify)
ON_MESSAGE(WM_WIN_C, OnWinCPressed)
ON_MESSAGE(WM_WIN_V, OnWinVPressed)
ON_MESSAGE(WM_CTRL_V,OnCtrlCPressed)
ON_WM_DESTROY()
ON_WM_WINDOWPOSCHANGING()
END_MESSAGE_MAP()
각 핸들러는 아래와 같습니다.
LRESULT CEasyClipperDlg::OnWinCPressed(WPARAM wParam, LPARAM lParam)
{
// Mode Check
if(g_IsCaptureMode == FALSE || m_bIsSaveMode == TRUE) { return 1; }
// Canvas Display
CanvasDlg canvas;
canvas.DoModal();
// Clip Area
RECT area = canvas.GetClipRect();
if((area.bottom | area.left | area.right | area.top) != 0) {
ScreenCapture(area);
SaveToClipboard();
m_ImageCaptured = TRUE;
} else {
m_ImageCaptured = FALSE;
}
g_IsCaptureMode = FALSE;
return 1;
}
LRESULT CEasyClipperDlg::OnWinVPressed(WPARAM wParam, LPARAM lParam)
{
// Capture 된 Image 가 없을 경우
if(m_ImageCaptured == FALSE || m_bIsSaveMode == TRUE) { return 1; }
m_bIsSaveMode = TRUE;
// Dialog 를 최상위로 올림
if(::GetForegroundWindow() != this->m_hWnd){
HWND h_active_wnd = ::GetForegroundWindow();
if(h_active_wnd != NULL){
DWORD thread_id = GetWindowThreadProcessId(h_active_wnd, NULL);
DWORD current_thread_id = GetCurrentThreadId();
if(current_thread_id != thread_id){
if(AttachThreadInput(current_thread_id, thread_id, TRUE)){
BringWindowToTop();
AttachThreadInput(current_thread_id, thread_id, FALSE);
}
}
}
}
// File Dialog
CFileDialog dlg(FALSE, NULL, NULL, OFN_EXPLORER | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("JPEG (*.jpg)|*.jpg|PNG (*.png)|*.png|Bitmap (*.bmp)|*.bmp||"), this, 0, 0);
if(dlg.DoModal() == IDOK) {
CString fileName = dlg.GetPathName();
switch(dlg.m_pOFN->nFilterIndex) {
case 1: fileName.Append(_T(".jpg")); SaveImage(fileName, m_jpegClsid); break;
case 2: fileName.Append(_T(".png")); SaveImage(fileName, m_pngClsid); break;
case 3: fileName.Append(_T(".bmp")); SaveImage(fileName, m_bmpClsid); break;
}
}
m_bIsSaveMode = FALSE;
return 1;
}
:: 후킹이 어느 순간부터 동작하지 않는 문제
후킹을 하다보면, 어느 순간부터 후킹이 되지 않는 경우가 발생합니다.
즉 자동으로 후킹이 제거되는 경우입니다. 원인은 MSDN 에서 찾아 볼 수 있습니다.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx
The hook procedure should process a message in less time than the data entry specified in the LowLevelHooksTimeout value in the following registry key:
HKEY_CURRENT_USER\Control Panel\Desktop
The value is in milliseconds. If the hook procedure times out, the system passes the message to the next hook. However, on Windows 7 and later, the hook is silently removed without being called. There is no way for the application to know whether the hook is removed.
즉, 윈도우 7 이후부터는 후킹 프로시저에서 너무 오랜 시간을 빼앗는다고 생각되면, 자동적으로 훅을 remove 시켜버립니다.
그렇기 때문에 프로시저는 최대한 간단한 작업만 해야 합니다.
이런 이유로 제가 프로시저에서 SendMessage() 가 아닌 PostMessage() 를 사용한 것입니다.
실제로 개발할때 SendMessage() 로 사용했더니, 어느 순간부터 훅이 자동으로 제거 되었습니다. =_=;
2. Screen Capture 하기
윈도우 화면을 캡처하기 위해서는 아래와 같이 하시면 됩니다.
void CEasyClipperDlg::ScreenCapture(RECT area)
{
// 기존 Bitmap 리소스 해제
if(m_hCaptureBitmap != NULL) {
DeleteObject(m_hCaptureBitmap);
m_hCaptureBitmap = NULL;
}
// Capture
int nClipWidth = area.right - area.left;
int nClipHeight = area.bottom - area.top;
HWND hDesktopWnd = ::GetDesktopWindow();
HDC hDesktopDC = ::GetDC(hDesktopWnd);
HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
m_hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
nClipWidth, nClipHeight);
::SelectObject(hCaptureDC,m_hCaptureBitmap);
::BitBlt(hCaptureDC,0,0,nClipWidth,nClipHeight,
hDesktopDC,area.left,area.top,SRCCOPY|CAPTUREBLT);
// 리소스 해제
::ReleaseDC(hDesktopWnd,hDesktopDC);
DeleteDC(hCaptureDC);
}
3. HBITMAP 을 Image(jpg, png, bmp)로 저장
캡처한 이미지를 저장하려면 아래처럼 해 줍니다.
참고로 코드가 복잡해 지는 것을 막기 위해서 GDI+ 를 사용하였습니다.
GDI+ 사용을 하기 위한 초기화, 리소스 해제는 전체코드를 참고하시기 바랍니다.
BOOL CEasyClipperDlg::SaveImage(LPCWSTR filePath, CLSID clsid)
{
// 이미지가 없는 경우.
if(m_hCaptureBitmap == NULL) { return FALSE; }
// Image 저장
HPALETTE hpal; //팔레트 설정
hpal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);
Gdiplus::Bitmap image(m_hCaptureBitmap, hpal);
image.Save(filePath, &clsid, NULL);
return TRUE;
}
4. Clipboard 에 캡처한 이미지 저장
캡처한 이미지를 Clipboard 에 등록하는 코드입니다.
BOOL CEasyClipperDlg::SaveToClipboard(void)
{
if (OpenClipboard()) {
EmptyClipboard();
CBitmap * bit = CBitmap::FromHandle(m_hCaptureBitmap);
SetClipboardData(CF_BITMAP, bit->m_hObject);
CloseClipboard();
return TRUE;
}
return FALSE;
}
5. 보안프로그램 우회
5.1 Code Injection 의 경우(윈도우 버젼에 따라 binary 코드는 다를 수 있음)
void CEasyClipperDlg::RestoreBitBlt(void)
{
FARPROC org = GetProcAddress(GetModuleHandle(L"GDI32.dll"), "BitBlt");
if(org != NULL) {
DWORD dwOldProcted;
if(VirtualProtect((LPVOID)org, 120, PAGE_EXECUTE_READWRITE, &dwOldProcted) == TRUE) {
WriteProcessMemory(GetCurrentProcess(), org, "\x8B\xFF\x55\x8B\xEC\x51\x8B\x45\x28\x8B\xC8\xC1\xE1\x02\x33\xC8\xF7\xC1\x00\x00\xCC\x00\x0F\x84\x4A\x1E\x01\x00\x53\x8B\x5D\x08\x56\x33\xF6\x57\xBF\x00\x00\x01\x00\x39\x35\xC4\x9C\xE6\x75\x0F\x85\x09\x03\x02\x00\x8B\xC3\x25\x00\x00\x7F\x00\x3B\xC7\x0F\x85\xEC\x03\x02\x00\x56\x6A\xFF\xFF\x75\x28\x64\xA1\x18\x00\x00\x00\xFF\x75\x24\x89\xB0\xD0\x06\x00\x00\xFF\x75\x20\xFF\x75\x1C\xFF\x75\x18\xFF\x75\x14\xFF\x75\x10\xFF\x75\x0C\x53\xE8\x0C\x00\x00\x00\x5F\x5E\x5B\xC9\xC2\x24\x00", 120, NULL);
VirtualProtect((LPVOID)org, 120, dwOldProcted, &dwOldProcted);
}
}
}
전체 프로젝트 코드는 첨부파일을 참고하시기 바랍니다. :)
EasyClipper_src.zip
'개발 프로그램 > EasyClipper' 카테고리의 다른 글
Easy Clipper ver 2.0 (3) | 2015.05.27 |
---|---|
EasyClipper - 화면 캡처프로그램 (1) | 2011.12.15 |
EasyClipper 개발 - Canvas (0) | 2011.12.14 |
EasyClipper 개발 - Hooking (0) | 2011.12.13 |
EasyClipper 기획 (0) | 2011.12.13 |
RECENT COMMENT