Skip to content

Instantly share code, notes, and snippets.

@keturn
Created September 25, 2013 05:56
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save keturn/6695625 to your computer and use it in GitHub Desktop.
Save keturn/6695625 to your computer and use it in GitHub Desktop.
Log Windows focus changes.
"""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()
@Zod-
Copy link

Zod- commented Apr 30, 2017

officebackgroundtaskhandler.exe has been stealing the focus of my game recently. Cheers for the script to find out what it was.

@matgott
Copy link

matgott commented Sep 23, 2023

is there any event to detect when a window is resized?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment