본문 바로가기
Window Tool

Window Focus Track

by 暻煥 2024. 4. 27.

게임을 하다가 갑자기 키보드 입력이 끊기는 경우가 종종 발생하였다.

(예를 들어, 앞으로 움직이는 키를 계속 누르고 있는데 잠깐 멈췄다가 다시 앞으로 움직이는 현상)

 

Back Ground에서 실행되는 프로세스가 창을 생성하여 키보드 포커스를 뺐어가는 것으로 의심되어, window focus event를 수신하는 python script를 실행해서 추적해보았다.

 

"""
FileName : trackwindow.py

Log window focus and appearance.

Written to try to debug some window popping up and stealing focus from my
Spelunky game for a split second.

Developed with 32-bit python on Windows 7. Might work in other environments,
but some of these APIs might not exist before Vista.

Much credit to Eric Blade for this:
https://mail.python.org/pipermail/python-win32/2009-July/009381.html
and David Heffernan:
http://stackoverflow.com/a/15898768/9585
"""

# using pywin32 for constants and ctypes for everything else seems a little
# indecisive, but whatevs.
import win32con

import sys
import ctypes
import ctypes.wintypes

user32 = ctypes.windll.user32
ole32 = ctypes.windll.ole32
kernel32 = ctypes.windll.kernel32

WinEventProcType = ctypes.WINFUNCTYPE(
    None,
    ctypes.wintypes.HANDLE,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.HWND,
    ctypes.wintypes.LONG,
    ctypes.wintypes.LONG,
    ctypes.wintypes.DWORD,
    ctypes.wintypes.DWORD
)


# The types of events we want to listen for, and the names we'll use for
# them in the log output. Pick from
# http://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx
eventTypes = {
    win32con.EVENT_SYSTEM_FOREGROUND: "Foreground",
    win32con.EVENT_OBJECT_FOCUS: "Focus",
    win32con.EVENT_OBJECT_SHOW: "Show",
    win32con.EVENT_SYSTEM_DIALOGSTART: "Dialog",
    win32con.EVENT_SYSTEM_CAPTURESTART: "Capture",
    win32con.EVENT_SYSTEM_MINIMIZEEND: "UnMinimize"
}

# limited information would be sufficient, but our platform doesn't have it.
processFlag = getattr(win32con, 'PROCESS_QUERY_LIMITED_INFORMATION',
                      win32con.PROCESS_QUERY_INFORMATION)

threadFlag = getattr(win32con, 'THREAD_QUERY_LIMITED_INFORMATION',
                     win32con.THREAD_QUERY_INFORMATION)


# store last event time for displaying time between events
lastTime = 0

def log(msg):
    print(msg)

def logError(msg):
    sys.stdout.write(msg + '\n')


def getProcessID(dwEventThread, hwnd):
    # It's possible to have a window we can get a PID out of when the thread
    # isn't accessible, but it's also possible to get called with no window,
    # so we have two approaches.

    hThread = kernel32.OpenThread(threadFlag, 0, dwEventThread)

    if hThread:
        try:
            processID = kernel32.GetProcessIdOfThread(hThread)
            if not processID:
                logError("Couldn't get process for thread %s: %s" %
                         (hThread, ctypes.WinError()))
        finally:
            kernel32.CloseHandle(hThread)
    else:
        errors = ["No thread handle for %s: %s" %
                  (dwEventThread, ctypes.WinError(),)]

        if hwnd:
            processID = ctypes.wintypes.DWORD()
            threadID = user32.GetWindowThreadProcessId(
                hwnd, ctypes.byref(processID))
            if threadID != dwEventThread:
                logError("Window thread != event thread? %s != %s" %
                         (threadID, dwEventThread))
            if processID:
                processID = processID.value
            else:
                errors.append(
                    "GetWindowThreadProcessID(%s) didn't work either: %s" % (
                    hwnd, ctypes.WinError()))
                processID = None
        else:
            processID = None

        if not processID:
            for err in errors:
                logError(err)

    return processID


def getProcessFilename(processID):
    hProcess = kernel32.OpenProcess(processFlag, 0, processID)
    if not hProcess:
        logError("OpenProcess(%s) failed: %s" % (processID, ctypes.WinError()))
        return None

    try:
        filenameBufferSize = ctypes.wintypes.DWORD(4096)
        filename = ctypes.create_unicode_buffer(filenameBufferSize.value)
        kernel32.QueryFullProcessImageNameW(hProcess, 0, ctypes.byref(filename),
                                            ctypes.byref(filenameBufferSize))

        return filename.value
    finally:
        kernel32.CloseHandle(hProcess)


def callback(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread,
             dwmsEventTime):
    global lastTime
    length = user32.GetWindowTextLengthW(hwnd)
    title = ctypes.create_unicode_buffer(length + 1)
    user32.GetWindowTextW(hwnd, title, length + 1)

    processID = getProcessID(dwEventThread, hwnd)

    shortName = '?'
    if processID:
        filename = getProcessFilename(processID)
        if filename:
            shortName = '\\'.join(filename.rsplit('\\', 2)[-2:])

    if hwnd:
        hwnd = hex(hwnd)
    elif idObject == win32con.OBJID_CURSOR:
        hwnd = '<Cursor>'

    log(u"%s:%04.2f\t%-10s\t"
        u"W:%-8s\tP:%-8d\tT:%-8d\t"
        u"%s\t%s" % (
        dwmsEventTime, float(dwmsEventTime - lastTime)/1000, eventTypes.get(event, hex(event)),
        hwnd, processID or -1, dwEventThread or -1,
        shortName, title.value))

    lastTime = dwmsEventTime


def setHook(WinEventProc, eventType):
    return user32.SetWinEventHook(
        eventType,
        eventType,
        0,
        WinEventProc,
        0,
        0,
        win32con.WINEVENT_OUTOFCONTEXT
    )


def main():
    ole32.CoInitialize(0)

    WinEventProc = WinEventProcType(callback)
    user32.SetWinEventHook.restype = ctypes.wintypes.HANDLE

    hookIDs = [setHook(WinEventProc, et) for et in eventTypes.keys()]
    if not any(hookIDs):
        print('SetWinEventHook failed')
        sys.exit(1)

    msg = ctypes.wintypes.MSG()
    while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) != 0:
        user32.TranslateMessageW(msg)
        user32.DispatchMessageW(msg)

    for hookID in hookIDs:
        user32.UnhookWinEvent(hookID)
    ole32.CoUninitialize()


if __name__ == '__main__':
    main()

 

명령어 프롬프트를 열어서 python script를 실행한다. (명령어 프롬프트를 관리자 권한으로 실행해야 한다.)

python .\trackwindow.py

 

실행 결과를 확인해 보니, TDepend 프로세스가 창을 짧은 시간동안 생성해서 Focus를 가져가는 것으로 확인하였다.

58179500:0.88   Foreground      W:0xf0a9c       P:22056         T:34236         SysWOW64\TDepend.exe    TDependPsc32
58179500:0.00   Foreground      W:0x170858      P:30788         T:30448         SysWOW64\TDepend64.exe  TDependPsc64
58179500:0.00   Focus           W:0xf0a9c       P:22056         T:34236         SysWOW64\TDepend.exe    TDependPsc32
58179515:0.01   Show            W:0xf0a9c       P:22056         T:34236         SysWOW64\TDepend.exe    TDependPsc32
58179515:0.00   Focus           W:0x170858      P:30788         T:30448         SysWOW64\TDepend64.exe  TDependPsc64
58179515:0.00   Focus           W:None          P:22056         T:34236         SysWOW64\TDepend.exe
58179515:0.00   Show            W:0x170858      P:30788         T:30448         SysWOW64\TDepend64.exe  TDependPsc64

 

추가로 검색 해보니, Tcube라는 캡처 방지 프로그램(회사 재택근무 용)이 생성하는 프로세스 였다.

http://www.teruten.com/kr/solution/virtualization.php

 

테르텐 - 제품 - 화면 보안

안전한 보안 기술을 개발하는 보안 전문 회사 테르텐

www.teruten.com

 

Tcube 프로그램을 제거하니, 키보드 입력이 끊기는 현상이 없어졌다.

(재택근무 할때마다 Tcube를 재설치 해야한다.... ㅠㅠ)


출처