창 관리 방법

창 관리 방법 (How to Manage Windows)

AutoHotkey로 창을 활성화·종료·최소화·이동하는 방법과, 제목·클래스·프로세스·ID 등으로 창을 식별하는 방법을 설명하는 문서예요.

출처: 문서

본문

AutoHotkey가 할 수 있는 가장 쉽고 유용한 일 중 하나는 창을 조작하는 키보드 단축키(핫키)를 만들 수 있게 해주는 것이에요. 스크립트는 거의 모든 창을 활성화, 종료, 최소화, 최대화, 복원, 숨기기, 표시하거나 이동할 수 있어요. 이것은 제목이나 다른 기준으로 창을 지정하면서 적절한 Win 함수를 호출해서 이루어져요:

Run "notepad.exe"
WinWait "Untitled - Notepad"
WinActivate "Untitled - Notepad"
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, "Untitled - Notepad"

이 예시는 새 메모장 창을 연 다음 주 화면의 일부(너비의 ¼, 높이의 ½)를 채우도록 이동해야 해요. 시험해 보는 방법을 배우려면 How to Run Example Code를 참조해요.

창을 조작하는 많은 함수에 대해 자세히 다루지는 않을 거예요. 별로 복잡할 게 없기 때문이에요. 예를 들어, 창을 활성화하는 대신 최소화하려면 WinActivateWinMinimize로 바꾸면 돼요. 창을 조작하거나 정보를 검색할 수 있는 함수 목록은 Win functions을 참조해요.

이 튜토리얼의 대부분은 대상으로 삼을 창을 식별(identify) 하는 방법에 관한 것이에요, 가장 골치 아픈 부분이기 때문이에요. 예를 들어, 위 예시에는 여러 가지 문제가 있어요:

  • 제목이 불필요하게 반복됨.
  • 제목은 UI 언어가 영어로 설정된 시스템에서만 정확함.
  • 새 창이 아니라 이미 존재하는 제목 없는 메모장 창을 이동할 수 있음.
  • 어떤 이유로 일치하는 창이 나타나지 않으면 스크립트가 무한정 멈춤.

몇 가지 기본을 다룬 후에 이 문제들을 하나씩 다룰게요.

팁: AutoHotkey에는 Window Spy라는 스크립트가 함께 제공되며, 창의 제목·클래스·프로세스 이름을 확인하는 데 사용할 수 있어요. 클래스와 프로세스 이름은 제목만으로 창을 식별하는 것이 실용적이지 않을 때 자주 사용돼요. Window Spy는 스크립트의 트레이 메뉴나 AutoHotkey Dash에서 찾을 수 있어요.

제목 일치 (Title Matching)

제목으로 창을 지정할 때 알아야 할 몇 가지가 있어요:

  • 창 제목은 항상 대소문자를 구분해요. 단, i) 수정자를 가진 RegEx 일치 모드를 사용할 때는 예외예요.
  • 기본적으로 함수는 창 제목의 부분 문자열을 기대해요. 예를 들어, "Notepad"는 "Untitled - Notepad"와 "C:\A\B.ahk - Notepad++" 둘 다와 일치할 수 있어요. SetTitleMatchMode를 사용해 함수가 접두사, 정확한 일치, 또는 RegEx 패턴을 기대하게 할 수 있어요.
  • 기본적으로 (WinShow를 제외하고) 숨겨진 창은 무시돼요. DetectHiddenWindows를 사용해 이것을 바꿀 수 있어요.

자세한 내용은 Matching Behaviour를 참조해요.

활성 창 (Active Window)

활성 창을 가리키려면 창 제목 대신 문자 "A"를 사용해요. 예를 들어, 이것은 활성 창을 최소화해요:

WinMinimize "A"

마지막으로 찾은 창 (Last Found Window)

WinWait, WinExist, WinActive, WinWaitActive 또는 WinWaitNotActive가 일치하는 창을 찾으면, 그것을 **마지막으로 찾은 창(last found window)**으로 설정해요. 대부분의 창 함수는 창 제목(및 관련 매개변수)을 생략할 수 있으며, 그 경우 마지막으로 찾은 창을 기본값으로 사용해요. 예를 들어:

Run "notepad.exe"
WinWait "Untitled - Notepad"
WinActivate
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2

이것은 창 제목을 반복하지 않게 해서 약간의 시간을 절약하고, 창 제목을 바꿔야 할 때 스크립트를 업데이트하기 쉽게 만들며, 코드를 읽기 쉽게 할 수 있어요. 또한 일치하는 창이 여러 개일 때나 창이 "찾아진" 후에 제목이 바뀌어도 매번 같은 창에서 동작하도록 보장해서 스크립트를 더 안정적으로 만들어요. 또한 스크립트를 더 효율적으로 실행하게 하지만, 그 차이는 크지 않아요.

창 클래스 (Window Class)

창 클래스는 창을 만들기 위한 템플릿으로 사용되는 속성 집합이에요. 종종 창의 클래스 이름은 응용 프로그램이나 창의 목적과 관련돼요. 창의 클래스는 창이 존재하는 동안 절대 바뀌지 않으므로, 제목으로 식별하는 것이 비현실적이거나 불가능할 때 창을 식별하는 데 자주 사용할 수 있어요.

예를 들어, 창 제목 "Untitled - Notepad" 대신 이 경우 시스템 언어와 무관하게 "Notepad"인 창의 클래스를 사용할 수 있어요:

Run "notepad.exe"
WinWait "ahk_class Notepad"
WinActivate
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2

창 클래스는 위에 보인 것처럼 "ahk_class"라는 단어를 사용해 제목과 구별돼요. 여러 기준을 결합하려면 창 제목을 먼저 나열해요. 예: "Untitled ahk_class Notepad".

관련: ahk_class

프로세스 이름/경로 (Process Name/Path)

창은 "ahk_exe"라는 단어 뒤에 프로세스의 이름이나 경로를 붙여 프로세스로 식별될 수 있어요. 예를 들어:

Run "notepad.exe"
WinWait "ahk_exe notepad.exe"
WinActivate
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2

관련: ahk_exe

프로세스 ID (Process ID, PID)

각 프로세스는 프로세스가 종료될 때까지 고유하게 유지되는 ID 번호를 가져요. 이것을 사용해 새 프로세스가 만든 메모장 창을 제외한 다른 메모장 창은 무시하도록 함으로써 메모장 예시를 더 안정적으로 만들 수 있어요:

Run "notepad.exe",,, &notepad_pid
WinWait "ahk_pid " notepad_pid
WinActivate
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2

쉼표가 세 개 연속으로 필요해요. 그중 두 개는 원하는 매개변수(OutputVarPID)가 네 번째이므로 Run 함수의 사용하지 않는 WorkingDirOptions 매개변수를 건너뛰기 위한 것이에요.

앰퍼샌드(&)는 참조 연산자(reference operator)예요. 이것은 notepad_pid 변수를 Run 함수에 참조로(reference) 전달하는 데 사용돼요(즉 값 대신 변수 자체를 전달), 함수가 변수에 새 값을 할당할 수 있게 해줘요. 그런 다음 notepad_pid는 실제 프로세스 ID의 자리 표시자가 돼요.

문자열 "ahk_pid "는 공백으로 구분해 차례로 쓰기만 해서 notepad_pid 변수가 담은 프로세스 ID와 연결돼요. 결과는 "ahk_pid 1040" 같은 문자열이지만, 그 숫자는 예측할 수 없어요.

새 프로세스가 여러 창을 만들 수도 있다면, 창 제목과 다른 기준을 공백으로 구분해 결합할 수 있어요. 창 제목은 항상 먼저 와야 해요. 예: "Untitled ahk_class Notepad ahk_pid " notepad_pid.

관련: ahk_pid

창 ID (Window ID, HWND)

각 창은 창이 파괴될 때까지 고유하게 유지되는 ID 번호를 가져요. 프로그래밍 용어로 이것을 "창 핸들(window handle)" 또는 HWND라고 불러요. 마지막으로 찾은 창을 사용하는 것만큼 편리하지는 않지만, 제목이 바뀌어도 스크립트가 마음대로 이름으로 참조할 수 있도록 창의 ID를 변수에 저장할 수 있어요. 한 번에 마지막으로 찾은 창은 하나만 있을 수 있지만, 변수 이름을 만들 수 있는 만큼(또는 배열을 사용해서) 많은 창 ID를 사용할 수 있어요.

창 ID는 WinWait, WinExist 또는 WinActive가 반환하거나, 다른 출처에서 올 수 있어요. 메모장 예시는 이것을 활용하도록 다시 쓸 수 있어요:

Run "notepad.exe"
notepad_id := WinWait("Untitled - Notepad")
WinActivate notepad_id
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, notepad_id

이것은 WinWait의 반환 값을 변수 "notepad_id"에 할당해요. 즉, WinWait가 창을 찾으면 결과로 창의 ID를 만들고, 스크립트는 그 결과를 변수에 저장해요. "notepad_id"는 이 예시를 위해 내가 만든 이름일 뿐이에요. 여러분에게 맞게(일정한 제약 안에서) 어떤 변수 이름이든 사용할 수 있어요.

함수 이름 바로 뒤에 창 제목 주위에 괄호를 추가했다는 것에 주목하세요. 괄호는 함수 호출 문장에서(즉 줄 맨 앞의 함수 호출에서) 생략할 수 있지만, 그 경우 함수의 반환 값을 얻을 수 없어요.

스크립트는 또한 notepad_id 변수를 나중에 사용하기 위해 보관할 수 있어요. 예를 들어 창을 닫거나 다시 활성화하거나 다른 곳으로 이동하는 데 말이에요.

관련: ahk_id

타임아웃 (Timeout)

기본적으로 WinWait는 일치하는 창이 나타날 때까지 무한정 기다려요. 이것이 실제로 일어났는지는(비활성화하지 않는 한) 트레이 아이콘을 통해 스크립트의 메인 창을 열어서 확인할 수 있어요. 창은 보통 기본적으로 ListLines 보기로 열려요. WinWait가 여전히 기다리고 있다면, 그 줄은 줄 목록 맨 아래에 기다리기 시작한 이후의 초 수와 함께 나타나요. 숫자는 View 메뉴에서 "Refresh"(새로 고침)를 선택하지 않는 한 업데이트되지 않아요.

이 예시를 실행하고 위에 설명한 대로 메인 창을 열어 보세요:

WinWait "Untitled - Notpad"  *; (의도적인 오타)*

스크립트가 창을 기다리며 멈춰 있으면, 보통 스크립트를 종료하거나 다시 로드해서 멈춤을 풀어야 해요. 처음부터(또는 다시) 그런 일이 일어나지 않게 하려면 WinWaitTimeout 매개변수를 사용할 수 있어요. 예를 들어, 이것은 창이 나타날 때까지 최대 5초를 기다려요:

Run "notepad.exe",,, &notepad_pid
if WinWait("ahk_pid " notepad_pid,, 5)
{
    WinActivate
    WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2
}

if 문장 아래의 블록은 WinWait가 일치하는 창을 찾을 때만 실행돼요. 타임아웃되면 블록은 건너뛰고(그 뒤에 코드가 있다면) 마지막 중괄호 뒤에서 실행이 계속돼요.

표현식(if 문장의 조건 같은 것)에서 함수의 결과를 사용하려면 "WinWait" 뒤의 괄호가 필요하다는 점을 주목하세요. 함수 호출 자체를 함수의 결과를 대체하는 것으로 생각할 수 있어요. 예를 들어, WinWait가 타임아웃되기 전에 일치하는 것을 찾으면 결과는 0이 아니에요. if 1은 if 문장 아래의 블록을 실행하는 반면 if 0은 그것을 건너뛰어요.

또 다른 작성 방법은 대기가 타임아웃되면 일찍 반환(즉 중단)하는 것이에요. 예를 들어:

Run "notepad.exe",,, &notepad_pid
if !WinWait("ahk_pid " notepad_pid,, 5)
    return
WinActivate
WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2

결과는 논리적 부정 연산자(! 또는 not)를 적용해서 뒤집혀요. WinWait가 타임아웃되면 그 결과는 0이에요. !0의 결과는 1이므로, WinWait가 타임아웃되면 if 문장은 return을 실행해요.

WinWait의 결과는 실제로 창의 ID(위에 설명한 대로)이거나 타임아웃되면 0이에요. 창을 ID로도 참조하고 싶다면 if 문장에서 직접 사용하는 대신 결과를 변수에 할당할 수 있어요:

Run "notepad.exe",,, &notepad_pid
notepad_id := WinWait("ahk_pid " notepad_pid,, 5)
if notepad_id
{
    WinActivate notepad_id
    WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, notepad_id
}

변수 이름을 반복하지 않으려면 결과를 변수에 할당하고 그것이 0이 아닌지(**true) 동시에 확인할 수 있어요:

Run "notepad.exe",,, &notepad_pid
if notepad_id := WinWait("ahk_pid " notepad_pid,, 2)
{
    WinActivate notepad_id
    WinMove 0, 0, A_ScreenWidth/4, A_ScreenHeight/2, notepad_id
}

그 경우 :=(할당)를 = 또는 ==(비교)와 혼동하지 않도록 주의해요. 예를 들어, if myvar := 0은 새 값을 할당하고 매번 같은 결과(false)를 주는 반면, if myvar = 0은 이전에 할당된 값을 0과 비교해요.

표현식 (Expressions, 수학 등)

창을 이동하려 할 때, WinGetPos 함수로 검색할 수 있는 이전 위치나 크기에 상대적으로 창을 이동하거나 크기를 조정하는 것이 유용한 경우가 많아요. 예를 들어, 다음 핫키 집합은 RCtrl을 누른 채 화살표 키를 눌러 활성 창을 각 방향으로 10픽셀씩 이동하는 데 사용될 수 있어요:

>^Left::    MoveActiveWindowBy(-10,   0)
>^Right::   MoveActiveWindowBy(+10,   0)
>^Up::      MoveActiveWindowBy(  0, -10)
>^Down::    MoveActiveWindowBy(  0, +10)

MoveActiveWindowBy(x, y) {
    WinExist "A"  *; 활성 창을 마지막으로 찾은 창으로 만듦*
    WinGetPos &current_x, &current_y
    WinMove current_x + x, current_y + y
}

이 예시는 코드를 여러 번 반복하지 않기 위해 함수를 정의해요. xy는 각 핫키에 지정된 두 숫자의 자리 표시자가 돼요. WinGetPos는 현재 위치를 current_xcurrent_y에 저장하며, 우리는 그것을 xy에 더해요.

이런 간단한 표현식은 꽤 익숙하게 보여야 해요. 자세한 내용은 Expressions를 참조하세요. 하지만 당장 배울 필요가 없는 세부 사항이 많다는 점을 유의하세요.

더 알아보기 (Learn more)