네이트온을 보면 전체 화면 사용시에 상태를 자동으로 다른 용무 중으로 변경해 주는 기능이 있다. 이 기능을 어떻게 구현하는지 살펴보도록 하자. 

네이트온은 3가지 메시지 훅을 설치한다. 키보드 훅(WH_KEYBOARD), 마우스 훅(WH_MOUSE), 쉘 훅(WH_SHELL)이 그것이다. 각각의 후킹 함수가 수행 되면, 네이트온은 본체로 메시지를 전달한다. 해당 메시지 함수에서 타이머를 발생시키고, 타이머 함수에서 GetForegroundWindow를 사용해서 현재 윈도우가 천체 화면인지를 판단한다. 전체 화면인지 판단하는 부분의 의사 코드는 아래와 같다. 

// GetDesktopWindow 
// GetForegroundWindow 
// GetParent 
// GetWindowRect // GetClientRect 
// GetClassName // WorkerW // ProgMan 
// Ini 파일 체크 
// 변경 
// GetDesktopWindow // GetForegroundWindow 
// GetParent 
// GetWindowRect // GetClientRect 
// GetClassName // WorkerW // ProgMan 
// Ini 파일 체크 
// 변경 GetDesktopWindow로 데스크탑의 크기를 구한것과 GetForegroundWindow의 클라이언트 영역을 구해서 큰지를 비교하는 것이다. WorkerW와 ProgMan은 전체화면이 아닌 바탕 화면 윈도우다. 바탕 화면에 포커스가 간 경우에 오판하는 것을 방지하기 위해서 클래스 명을 비교하는 것이다. Ini 파일에서는 환경 설정에서 해당 기능을 사용하도록 설정했는지를 체크한다. 

그리고 한 가지 더 체크해야 할 점은 전체 화면인지를 판단하기 위해서 굳이 훅을 설치할 필요가 없다는 점이다. 훅 수행에 들어가는 비용과 위험성을 고려했을 때 타이머 등을 사용하는 것이 훨씬 더 경제적이다. 1초에 한번씩만 체크해도 거의 실시간으로 체크하는 것과 똑같이 보인다는 점을 생각해야 한다. 물론 사용자 인풋이 없는 경우에는 타이머를 사용해서 폴링하는 것이 더 많은 CPU 자원을 소모한다는 단점이 있긴 하다. 

Codes 위에서 제시한 의사 코드를 바탕으로 실제 FullScreen 윈도우를 찾는 함수를 만들어 보면 아래와 같다. 전체 화면을 사용하는 윈도우가 있는 경우에는 해당 윈도우의 핸들을 없는 경우에는 NULL을 리턴 한다. 

  HWND GetFullScreenWindow()   
 {   
 HWND activeWnd = GetForegroundWindow();   
 if(!activeWnd)   
 return NULL;   

 activeWnd = GetAncestor(activeWnd, GA_ROOT);   
 if(!activeWnd)   
 return NULL;   

 TCHAR className[MAX_PATH] = {0};   
 if(!GetClassName(activeWnd, className, MAX_PATH))   
 return NULL;   

 if(_tcsicmp(className, _T("WorkerW" )) == 0)   
 return NULL;   

 if(_tcsicmp(className, _T("ProgMan" )) == 0)   
 return NULL;   

 HWND desktopWnd = GetDesktopWindow();   
 if(!desktopWnd)   
 return NULL;   

 RECT desktop;   
 if(!GetWindowRect(desktopWnd, &desktop))   
 return NULL;   

 RECT client;   
 if(!GetClientRect(activeWnd, &client))   
 return NULL;   

 SIZE clientSize = { client.right - client.left, client.bottom - client.top };   
 SIZE desktopSize = { desktop.right - desktop.left, desktop.bottom - desktop.top };   

 if(clientSize.cx < desktopSize.cx || clientSize.cy < desktopSize.cy)   
 return NULL;   

 return activeWnd;      
 }   

 HWND GetFullScreenWindow()
 {
 HWND activeWnd = GetForegroundWindow();
 if(!activeWnd)
 return NULL;

 activeWnd = GetAncestor(activeWnd, GA_ROOT);
 if(!activeWnd)
 return NULL;

 TCHAR className[MAX_PATH] = {0};
 if(!GetClassName(activeWnd, className, MAX_PATH))
 return NULL;

 if(_tcsicmp(className, _T("WorkerW" )) == 0)
 return NULL;

 if(_tcsicmp(className, _T("ProgMan" )) == 0)
 return NULL;

 HWND desktopWnd = GetDesktopWindow();
 if(!desktopWnd)
 return NULL;

 RECT desktop;
 if(!GetWindowRect(desktopWnd, &desktop))
 return NULL;

 RECT client;
 if(!GetClientRect(activeWnd, &client))
 return NULL;

 SIZE clientSize = { client.right - client.left, client.bottom - client.top };
 SIZE desktopSize = { desktop.right - desktop.left, desktop.bottom - desktop.top };

 if(clientSize.cx < desktopSize.cx || clientSize.cy < desktopSize.cy)
 return NULL;

 return activeWnd;   
 }

 //GetAncestor API를 사용할 수 없다면 아래와 같이 while문을 사용해서 동일한 기능을 작성할 수 있다. while의 조건식이 마음에 들지 않는다면 아래와 같이 for문을 사용해서 최대 탐색 횟수를 제한할 수 있다.

 HWND parent;   
 while((parent = GetParent(activeWnd)))   
 {   
 activeWnd = parent;   
 }   


 HWND parent;
 while((parent = GetParent(activeWnd)))
 {
 activeWnd = parent;
 }

 HWND parent;   
 for(int i=0; i<50; ++i)   
 {   
 parent = GetParent(activeWnd);   
 if(!parent)   
 break;   

 activeWnd = parent;   
 }   

 HWND parent;
 for(int i=0; i<50; ++i)
 {
 parent = GetParent(activeWnd);
 if(!parent)
 break;

 activeWnd = parent;
 }


다중 모니터

위 코드를 살펴보면 알 수 있지만, 다중 모니터 상황에서는 위 코드가 항상 최선의 답을 제공하는 것은 아니다. 물론 대부분의 풀 스크린 화면으로 전환하는 윈도우가 1번 모니터에 바인딩 된다는 점에서 큰 문제는 없으나 아래와 같은 방식으로 풀 스크린을 사용하는 프로그램에 대해서는 문제가 생길 수 있다. 

HMONITOR monitor = MonitorFromWindow(GetSafeHwnd(), MONITOR_DEFAULTTONEAREST);   
if(monitor)   
{   
MONITORINFO info;   
info.cbSize = sizeof(info);   
GetMonitorInfo(monitor, &info);   

MoveWindow(&info.rcMonitor);   
}  


HMONITOR monitor = MonitorFromWindow(GetSafeHwnd(), MONITOR_DEFAULTTONEAREST);
if(monitor)
{
MONITORINFO info;
info.cbSize = sizeof(info);
GetMonitorInfo(monitor, &info);

MoveWindow(&info.rcMonitor);
}


위 코드는 현재 윈도우가 있는 모니터의 크기로 윈도우를 옮기는 코드다. 다음과 같은 상황을 생각해 볼 수 있다. 두 개의 모니터를 사용하는 상황에서 1번 모니터보다 2번 모니터의 해상도가 낮은 경우다. 그리고 그런 상황에서 위와 같은 코드로 특정 프로그램이 2번 모니터에서 풀 스크린을 사용하게 되면 위에서 제공한 코드는 NULL을 리턴한다. 2번 모니터에서 최대로 큰 화면이 되어 봤자, GetDesktopWindow의 크기 보다는 작기 때문이다. 따라서 이러한 경우를 처리해 준다면 멀티 모니터 상황에서도 좀 더 유연하게 동작할 수 있을 것이다. 아래의 코드는 단순히 GetDesktopWindow로 전체 화면 크기를 구하던 것을 모니터에 맞춰서 구하도록 수정한 것이다. 

HWND GetFullScreenWindowEx()   
{   
HWND activeWnd = GetForegroundWindow();   
if(!activeWnd)   
return NULL;   

activeWnd = GetAncestor(activeWnd, GA_ROOT);   
if(!activeWnd)   
return NULL;   

TCHAR className[MAX_PATH] = {0};   
if(!GetClassName(activeWnd, className, MAX_PATH))   
return NULL;   

if(_tcsicmp(className, _T("WorkerW" )) == 0)   
return NULL;   

if(_tcsicmp(className, _T("ProgMan" )) == 0)   
return NULL;   


RECT desktop;   
HMONITOR monitor = MonitorFromWindow(activeWnd, MONITOR_DEFAULTTONEAREST);   
if(monitor)   
{   
MONITORINFO info;   
info.cbSize = sizeof(info);   
if(!GetMonitorInfo(monitor, &info))   
return NULL;   

desktop = info.rcMonitor;   
}   
else  
{   
HWND desktopWnd = GetDesktopWindow();   
if(!desktopWnd)   
return NULL;   

if(!GetWindowRect(desktopWnd, &desktop))   
return NULL;   
}   

RECT client;   
if(!GetClientRect(activeWnd, &client))   
return NULL;   

SIZE clientSize = { client.right - client.left, client.bottom - client.top };   
SIZE desktopSize = { desktop.right - desktop.left, desktop.bottom - desktop.top };   

if(clientSize.cx < desktopSize.cx || clientSize.cy < desktopSize.cy)   
return NULL;   

return activeWnd;      
}  

HWND GetFullScreenWindowEx()
{
HWND activeWnd = GetForegroundWindow();
if(!activeWnd)
return NULL;

activeWnd = GetAncestor(activeWnd, GA_ROOT);
if(!activeWnd)
return NULL;

TCHAR className[MAX_PATH] = {0};
if(!GetClassName(activeWnd, className, MAX_PATH))
return NULL;

if(_tcsicmp(className, _T("WorkerW" )) == 0)
return NULL;

if(_tcsicmp(className, _T("ProgMan" )) == 0)
return NULL;


RECT desktop;
HMONITOR monitor = MonitorFromWindow(activeWnd, MONITOR_DEFAULTTONEAREST);
if(monitor)
{
MONITORINFO info;
info.cbSize = sizeof(info);
if(!GetMonitorInfo(monitor, &info))
return NULL;

desktop = info.rcMonitor;
}
else
{
HWND desktopWnd = GetDesktopWindow();
if(!desktopWnd)
return NULL;

if(!GetWindowRect(desktopWnd, &desktop))
return NULL;
}

RECT client;
if(!GetClientRect(activeWnd, &client))
return NULL;

SIZE clientSize = { client.right - client.left, client.bottom - client.top };
SIZE desktopSize = { desktop.right - desktop.left, desktop.bottom - desktop.top };

if(clientSize.cx < desktopSize.cx || clientSize.cy < desktopSize.cy)
return NULL;

return activeWnd;   

'프로그래밍 팁 > etc' 카테고리의 다른 글

전치행렬 프로그램  (0) 2011.06.15
서비스와 통신  (0) 2011.06.14
권한 정보 조회(UAC)  (0) 2011.05.13
my_strtok_s 구현  (0) 2011.03.24
컨텍스트 정보  (0) 2011.03.18
Posted by 띠깜
,