[App 개발] 맥마메 분석 - 3 마우스 입력
본문
마우스 입력도 키보드 입력과 거의 비슷한 방식으로 구현되어 있습니다. 메인 윈도우 이벤트 핸들러로 마우스 입력 핸들러를 등록하는 것, 그리고 이벤트 핸들러에서 입력된 값을 마우스 상태 배열에 등록하는 것, 그리고 인터페이스 함수에서 현재 상태를 검사하는 것입니다.
마우스 핸들러 루틴은 CarbonMouse.c 에 구현되어 있습니다.
1) 마우스 이벤트 핸들러 등록
이벤트 핸들러 등록은 카본 라이브러리를 이용한 이벤트 핸들러 등록 방식 그대로 사용하고 있습니다. 별반 어려운 것이 없을 것입니다.
EventTypeSpec list[] = { {kEventClassMouse, kEventMouseDown },
{kEventClassMouse, kEventMouseUp },
{kEventClassMouse, kEventMouseMoved }, // deltas while mouse button is up
{kEventClassMouse, kEventMouseDragged }, // deltas while mouse button is down
{kEventClassMouse, kEventMouseWheelMoved } };
// If we don't support Carbon events, bail
if ((Ptr) InstallEventHandler == (Ptr) kUnresolvedCFragSymbolAddress) return false;
// Install an application event handler
sMouseEventHandlerUPP = NewEventHandlerUPP (appMouseEventHandler);
status = InstallApplicationEventHandler(sMouseEventHandlerUPP, 4, list, 0, &ref);
특이한 점은 카본에서 휠 마우스 이벤트도 등록하고 있는데, 실제 핸들러에서는 휠 마우스 이벤트를 처리하지는 않는 것으로 보입니다.
2) 마우스 이벤트 핸들러
마우스 이벤트에 따라서 처리하는 루틴은 appMouseEventHandler() 함수에 구현되어 있습니다. 비교적 주석이 꼼꼼하게 기록되어 있습니다. 버튼 상태 배열을 최대값으로 하지 않은 이유, OS 9 에서는 모든 버튼 입력이 1 로 들어온다는 것, 심지어 카본 라이브러리 1.0.x 부터 1.2.5 까지는 모두 쓰레기라는 것… ㅡㅡ;
먼저 kEventMouseMoved 와 kEventMouseDragged 는 같은 루틴으로 처리하고 있습니다. 사실 버튼이 눌렸는지 안 눌렸는지에 따라서 마우스 좌표 계산이 달라질 이유가 없기 때문이겠지요. 버튼 상태 배열에 현재 버튼 상태가 기록되고 있기 때문입니다.
먼저 이벤트가 들어온 시간을 받고, 이벤트 인수들을 추출합니다. 그 다음 마지막에 받은 이벤트의 타임 스탬프와 비교해서 동일한 시간에 들어온 이벤트일 경우 무시하고 리턴해 버립니다. 한꺼번에 우당탕 들어오는 이벤트는 처음 들어오는 하나만을 처리하게 되겠지요?
EventTime evtTime = GetEventTime (event);
result = GetEventParameter(event, kEventParamMouseDelta, typeQDPoint, NULL,
sizeof(mouseDelta), NULL, &mouseDelta);
if (evtTime == sMouseDeltaTime) return eventNotHandledErr;
주석에는 마우스 좌표의 델타 값은 OS X 에서만 지원한다고 되어 있습니다. 제가 OS 9 용으로 컴파일하지 못하고 있어서 확인할 길은 없습니다만, 일부러 9 용 프로그램을 만들 일은 없을 것 같기에 그냥 받아들이고 넘어가기로 했습니다. ^^
구조체 mouseDelta 에는 h 와 v 값이 있어서 수평 델타값, 수직 델타값이 저장되어 있는 것 같습니다. 델타값이란 바로 이전 마우스 좌표를 x(n-1),y(n-1) 이라고 놓았을 때, 현재 마우스 좌표 x(n), y(n) 과의 차이를 계산한 값이 되죠. 이렇게 말입니다.
h = x(n) – x(n-1)
v = y(n) – y(n-1)
델타값이 입력되는 것은 상당히 유용하게 쓸 수 있습니다. 현재 마우스 좌표가 무엇인지를 전혀 신경쓸 필요가 없기 때문에 어떠한 해상도의 프로그램이라도 쉽게 응용이 가능하죠. 그리고 게임에서는 마우스 커서보다는 마우스가 어떤 방향으로 움직였는가 하는 정보만 사용되는 경우가 허다하기 때문에 커서를 신경쓸 필요 없이 마우스 입력을 게임에 적용할 수 있다는 장점이 있습니다.
일단 맥마메에서는 마우스 델타 값을 계속 누적하여 마우스 절대값을 만듭니다. 마메 메인 프로그램과의 호환성 때문일 것으로 생각됩니다만… 그 대신 마우스 좌표의 절대값은 화면 좌표가 아니고 정수 좌표 -32767 부터 32767 까지의 값을 가지게 됩니다. 만약 마우스를 계속 오른쪽으로 옮기더라도 x 값이 32767 을 넘어가지 않도록 값을 잘라주는 루틴이 삽입되어 있습니다. 마지막으로 이벤트가 입력된 타임스탬프를 sMouseDeltaTime 에 저장하고 핸들러는 끝납니다.
if ((SInt32) sMouseDelta.h + (SInt32) mouseDelta.h > 32767)
sMouseDelta.h = 32767;
else if ((SInt32) sMouseDelta.h + (SInt32) mouseDelta.h < -32767)
sMouseDelta.h = -32767;
else
sMouseDelta.h += mouseDelta.h;
if ((SInt32) sMouseDelta.v + (SInt32) mouseDelta.v > 32767)
sMouseDelta.h = 32767;
else if ((SInt32) sMouseDelta.v + (SInt32) mouseDelta.v < -32767)
sMouseDelta.v = -32767;
else
sMouseDelta.v += mouseDelta.v;
sMouseDeltaTime = evtTime;
kEventMouseDown 과 kEventMouseUp 이벤트 핸들러는 거의 동일합니다. 차이점은 마우스 버튼 상태 배열에 Down 일 때에는 1, Up 일 때에는 0 을 넣는 것 뿐입니다. 먼저 입력된 버튼 값 theButton 을 추출합니다. 이 값은 1, 2, 3… 으로 증가하고, C 의 배열은 0부터 시작하기 때문에 theButton 에서 1 을 빼줍니다. 그 다음 마우스 상태 배열 값을 변경하고 이벤트 타임 배열에 타임 스탬프를 기록하면 끝입니다.
case kEventMouseDown:
{
(void)GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL,
sizeof(theButton), NULL, &theButton);
// the button # is 1-based, our array is 0-based
theButton -= 1;
if (theButton < MAX_BUTTONS)
{
EventTime evtTime = GetEventTime (event);
if (evtTime != sMouseTime[theButton])
{
sMouseButtons[theButton] = 1;
sMouseTime[theButton] = evtTime;
// We handled the event, eat it
result = noErr;
}
}
break;
}
case kEventMouseUp:
{
(void)GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL,
sizeof(theButton), NULL, &theButton);
// the button # is 1-based, our array is 0-based
theButton -= 1;
if (theButton < MAX_BUTTONS)
{
EventTime evtTime = GetEventTime (event);
if (evtTime != sMouseTime[theButton])
{
sMouseButtons[theButton] = 0;
sMouseTime[theButton] = evtTime;
// We handled the event, eat it
result = noErr;
}
}
break;
}
3) 인터페이스 함수
마우스 값 인터페이스 함수는 간단합니다. 현재 마우스의 좌표, 그리고 마우스 상태 변수의 값을 꺼내서 리턴하는 것으로 끝입니다.
void Carbon_ReadMouseDeltas (SInt32 *deltaX, SInt32 *deltaY)
{
*deltaX = sMouseDelta.h;
*deltaY = sMouseDelta.v;
// Reset them for the next time through
sMouseDelta.h = sMouseDelta.v = 0;
}
Boolean Carbon_ReadMouseButton (int inButtonNum)
{
if (inButtonNum >= MAX_BUTTONS) return false;
return sMouseButtons[inButtonNum];
}
4) OSD 마우스 함수
엄밀히 말해서 CarbonMouse.c 모듈은 정형적인 카본 루틴입니다. 이것을 맥마메는 마메의 루틴과 연결해 줘야 하지요. 지난 시간에 살펴보았던 macinput.c 에서 바로 그 일을 하고 있습니다.
신기하게도 CarbonMouse.c 에 구현된 Carbon_ReadMouseDeltas() 함수는 PollJoysticks() 함수에서 호출되고 있었습니다. 주석에는 플레이어 1 의 델타 값이라고 되어 있습니다.
이야기는 이렇게 되는군요. 마우스로 조이스틱을 에뮬레이션 하고 있는 셈입니다. 애플 II 시절부터 미국 조이스틱의 대세는 아날로그 입력이죠. 애플 II 의 조이스틱은 사실 옛날 벽돌깨기 (이걸 아시는 분이 진짜 매니아… ^^) 에 사용되는 다이얼을 두 개 붙이고 가운데 막대기를 꽂아서 상하좌우 움직임을 아날로그 다이얼로 입력받는 구조였습니다. 그 당시는 A/D 컨버터도 조악하기 그지 없게 구성되었고 0부터 255까지의 값을 받도록 되어 있었죠. 조이스틱이 가운데 있으면 127, 127 이 되겠죠?
마우스로 조이스틱을 에뮬레이션한다면 이렇게 되겠습니다. 처음 상태를 0, 0 으로 놓고 마우스를 움직이는 것이 조이스틱을 움직이는 것과 동일한 개념으로 놓는 거죠. 마우스를 원래 위치로 가져오면 0, 0 으로 되어 조이스틱이 중앙에 놓이는 것이고, 어느 방향으로 마우스를 움직이냐에 따라 조이스틱이 움직이는 것 같은 효과를 얻을 수 있겠습니다.
PollJoysticks() 함수는 기계에 달려 있는 조이스틱 (마우스 포함) 의 상태를 계속해서 주기적으로 점검하는 함수입니다. 아마도 마메에서 호출되겠지요? PollJoysticks() 함수를 보면 Carbon_ReadMouseDeltas() 함수는 맨 밑에 한 줄 호출되고, 나머지 부분은 HID 조이스틱의 값을 구하는 루틴으로 구성되어 있습니다. HID 조이스틱 중에는 일본식 조이패드 (디지털 입력), 미국식 조이스틱 (아날로그 입력) 을 모두 받아들이도록 루틴이 구성되어 있습니다. 이 부분을 시작으로 HID 장비의 구현을 살펴보면 조이스틱 루틴의 실마리가 풀릴 것 같습니다.
최신글이 없습니다.
최신글이 없습니다.
댓글목록 0