_pygetwindow_win.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import ctypes
  2. from ctypes import wintypes # We can't use ctypes.wintypes, we must import wintypes this way.
  3. from pygetwindow import PyGetWindowException, pointInRect, BaseWindow, Rect, Point, Size
  4. NULL = 0 # Used to match the Win32 API value of "null".
  5. # These FORMAT_MESSAGE_ constants are used for FormatMesage() and are
  6. # documented at https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-formatmessage#parameters
  7. FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100
  8. FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
  9. FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
  10. # These SW_ constants are used for ShowWindow() and are documented at
  11. # https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-showwindow#parameters
  12. SW_MINIMIZE = 6
  13. SW_MAXIMIZE = 3
  14. SW_HIDE = 0
  15. SW_SHOW = 5
  16. SW_RESTORE = 9
  17. # SetWindowPos constants:
  18. HWND_TOP = 0
  19. # Window Message constants:
  20. WM_CLOSE = 0x0010
  21. # This ctypes structure is for a Win32 POINT structure,
  22. # which is documented here: http://msdn.microsoft.com/en-us/library/windows/desktop/dd162805(v=vs.85).aspx
  23. # The POINT structure is used by GetCursorPos().
  24. class POINT(ctypes.Structure):
  25. _fields_ = [("x", ctypes.c_long),
  26. ("y", ctypes.c_long)]
  27. enumWindows = ctypes.windll.user32.EnumWindows
  28. enumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_int, ctypes.POINTER(ctypes.c_int))
  29. getWindowText = ctypes.windll.user32.GetWindowTextW
  30. getWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
  31. isWindowVisible = ctypes.windll.user32.IsWindowVisible
  32. class RECT(ctypes.Structure):
  33. """A nice wrapper of the RECT structure.
  34. Microsoft Documentation:
  35. https://msdn.microsoft.com/en-us/library/windows/desktop/dd162897(v=vs.85).aspx
  36. """
  37. _fields_ = [('left', ctypes.c_long),
  38. ('top', ctypes.c_long),
  39. ('right', ctypes.c_long),
  40. ('bottom', ctypes.c_long)]
  41. def _getAllTitles():
  42. # This code taken from https://sjohannes.wordpress.com/2012/03/23/win32-python-getting-all-window-titles/
  43. # A correction to this code (for enumWindowsProc) is here: http://makble.com/the-story-of-lpclong
  44. titles = []
  45. def foreach_window(hWnd, lParam):
  46. if isWindowVisible(hWnd):
  47. length = getWindowTextLength(hWnd)
  48. buff = ctypes.create_unicode_buffer(length + 1)
  49. getWindowText(hWnd, buff, length + 1)
  50. titles.append((hWnd, buff.value))
  51. return True
  52. enumWindows(enumWindowsProc(foreach_window), 0)
  53. return titles
  54. def _formatMessage(errorCode):
  55. """A nice wrapper for FormatMessageW(). TODO
  56. Microsoft Documentation:
  57. https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-formatmessagew
  58. Additional information:
  59. https://stackoverflow.com/questions/18905702/python-ctypes-and-mutable-buffers
  60. https://stackoverflow.com/questions/455434/how-should-i-use-formatmessage-properly-in-c
  61. """
  62. lpBuffer = wintypes.LPWSTR()
  63. ctypes.windll.kernel32.FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
  64. NULL,
  65. errorCode,
  66. 0, # dwLanguageId
  67. ctypes.cast(ctypes.byref(lpBuffer), wintypes.LPWSTR),
  68. 0, # nSize
  69. NULL)
  70. msg = lpBuffer.value.rstrip()
  71. ctypes.windll.kernel32.LocalFree(lpBuffer) # Free the memory allocated for the error message's buffer.
  72. return msg
  73. def _raiseWithLastError():
  74. """A helper function that raises PyGetWindowException using the error
  75. information from GetLastError() and FormatMessage()."""
  76. errorCode = ctypes.windll.kernel32.GetLastError()
  77. raise PyGetWindowException('Error code from Windows: %s - %s' % (errorCode, _formatMessage(errorCode)))
  78. def getActiveWindow():
  79. """Returns a Window object of the currently active (focused) Window."""
  80. hWnd = ctypes.windll.user32.GetForegroundWindow()
  81. if hWnd == 0:
  82. # TODO - raise error instead
  83. return None # Note that this function doesn't use GetLastError().
  84. else:
  85. return Win32Window(hWnd)
  86. def getActiveWindowTitle():
  87. """Returns a string of the title text of the currently active (focused) Window."""
  88. # NOTE - This function isn't threadsafe because it relies on a global variable. I don't use nonlocal because I want this to work on Python 2.
  89. global activeWindowTitle
  90. activeWindowHwnd = ctypes.windll.user32.GetForegroundWindow()
  91. if activeWindowHwnd == 0:
  92. # TODO - raise error instead
  93. return None # Note that this function doesn't use GetLastError().
  94. def foreach_window(hWnd, lParam):
  95. global activeWindowTitle
  96. if hWnd == activeWindowHwnd:
  97. length = getWindowTextLength(hWnd)
  98. buff = ctypes.create_unicode_buffer(length + 1)
  99. getWindowText(hWnd, buff, length + 1)
  100. activeWindowTitle = buff.value
  101. return True
  102. enumWindows(enumWindowsProc(foreach_window), 0)
  103. return activeWindowTitle
  104. def getWindowsAt(x, y):
  105. """Returns a list of Window objects whose windows contain the point ``(x, y)``.
  106. * ``x`` (int, optional): The x position of the window(s).
  107. * ``y`` (int, optional): The y position of the window(s)."""
  108. windowsAtXY = []
  109. for window in getAllWindows():
  110. if pointInRect(x, y, window.left, window.top, window.width, window.height):
  111. windowsAtXY.append(window)
  112. return windowsAtXY
  113. def getWindowsWithTitle(title):
  114. """Returns a list of Window objects that substring match ``title`` in their title text."""
  115. hWndsAndTitles = _getAllTitles()
  116. windowObjs = []
  117. for hWnd, winTitle in hWndsAndTitles:
  118. if title.upper() in winTitle.upper(): # do a case-insensitive match
  119. windowObjs.append(Win32Window(hWnd))
  120. return windowObjs
  121. def getAllTitles():
  122. """Returns a list of strings of window titles for all visible windows.
  123. """
  124. return [window.title for window in getAllWindows()]
  125. def getAllWindows():
  126. """Returns a list of Window objects for all visible windows.
  127. """
  128. windowObjs = []
  129. def foreach_window(hWnd, lParam):
  130. if ctypes.windll.user32.IsWindowVisible(hWnd) != 0:
  131. windowObjs.append(Win32Window(hWnd))
  132. return True
  133. enumWindows(enumWindowsProc(foreach_window), 0)
  134. return windowObjs
  135. class Win32Window(BaseWindow):
  136. def __init__(self, hWnd):
  137. self._hWnd = hWnd # TODO fix this, this is a LP_c_long insead of an int.
  138. self._setupRectProperties()
  139. def _getWindowRect(self):
  140. """A nice wrapper for GetWindowRect(). TODO
  141. Syntax:
  142. BOOL GetWindowRect(
  143. HWND hWnd,
  144. LPRECT lpRect
  145. );
  146. Microsoft Documentation:
  147. https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowrect
  148. """
  149. rect = RECT()
  150. result = ctypes.windll.user32.GetWindowRect(self._hWnd, ctypes.byref(rect))
  151. if result != 0:
  152. return Rect(rect.left, rect.top, rect.right, rect.bottom)
  153. else:
  154. _raiseWithLastError()
  155. def __repr__(self):
  156. return '%s(hWnd=%s)' % (self.__class__.__name__, self._hWnd)
  157. def __eq__(self, other):
  158. return isinstance(other, Win32Window) and self._hWnd == other._hWnd
  159. def close(self):
  160. """Closes this window. This may trigger "Are you sure you want to
  161. quit?" dialogs or other actions that prevent the window from
  162. actually closing. This is identical to clicking the X button on the
  163. window."""
  164. result = ctypes.windll.user32.PostMessageA(self._hWnd, WM_CLOSE, 0, 0)
  165. if result == 0:
  166. _raiseWithLastError()
  167. def minimize(self):
  168. """Minimizes this window."""
  169. ctypes.windll.user32.ShowWindow(self._hWnd, SW_MINIMIZE)
  170. def maximize(self):
  171. """Maximizes this window."""
  172. ctypes.windll.user32.ShowWindow(self._hWnd, SW_MAXIMIZE)
  173. def restore(self):
  174. """If maximized or minimized, restores the window to it's normal size."""
  175. ctypes.windll.user32.ShowWindow(self._hWnd, SW_RESTORE)
  176. def show(self):
  177. """If hidden or showing, shows the window on screen and in title bar."""
  178. ctypes.windll.user32.ShowWindow(self._hWnd,SW_SHOW)
  179. def hide(self):
  180. """If hidden or showing, hides the window from screen and title bar."""
  181. ctypes.windll.user32.ShowWindow(self._hWnd,SW_HIDE)
  182. def activate(self):
  183. """Activate this window and make it the foreground (focused) window."""
  184. result = ctypes.windll.user32.SetForegroundWindow(self._hWnd)
  185. if result == 0:
  186. _raiseWithLastError()
  187. def resize(self, widthOffset, heightOffset):
  188. """Resizes the window relative to its current size."""
  189. result = ctypes.windll.user32.SetWindowPos(self._hWnd, HWND_TOP, self.left, self.top, self.width + widthOffset, self.height + heightOffset, 0)
  190. if result == 0:
  191. _raiseWithLastError()
  192. resizeRel = resize # resizeRel is an alias for the resize() method.
  193. def resizeTo(self, newWidth, newHeight):
  194. """Resizes the window to a new width and height."""
  195. result = ctypes.windll.user32.SetWindowPos(self._hWnd, HWND_TOP, self.left, self.top, newWidth, newHeight, 0)
  196. if result == 0:
  197. _raiseWithLastError()
  198. def move(self, xOffset, yOffset):
  199. """Moves the window relative to its current position."""
  200. result = ctypes.windll.user32.SetWindowPos(self._hWnd, HWND_TOP, self.left + xOffset, self.top + yOffset, self.width, self.height, 0)
  201. if result == 0:
  202. _raiseWithLastError()
  203. moveRel = move # moveRel is an alias for the move() method.
  204. def moveTo(self, newLeft, newTop):
  205. """Moves the window to new coordinates on the screen."""
  206. result = ctypes.windll.user32.SetWindowPos(self._hWnd, HWND_TOP, newLeft, newTop, self.width, self.height, 0)
  207. if result == 0:
  208. _raiseWithLastError()
  209. @property
  210. def isMinimized(self):
  211. """Returns ``True`` if the window is currently minimized."""
  212. return ctypes.windll.user32.IsIconic(self._hWnd) != 0
  213. @property
  214. def isMaximized(self):
  215. """Returns ``True`` if the window is currently maximized."""
  216. return ctypes.windll.user32.IsZoomed(self._hWnd) != 0
  217. @property
  218. def isActive(self):
  219. """Returns ``True`` if the window is currently the active, foreground window."""
  220. return getActiveWindow() == self
  221. @property
  222. def title(self):
  223. """Returns the window title as a string."""
  224. textLenInCharacters = ctypes.windll.user32.GetWindowTextLengthW(self._hWnd)
  225. stringBuffer = ctypes.create_unicode_buffer(textLenInCharacters + 1) # +1 for the \0 at the end of the null-terminated string.
  226. ctypes.windll.user32.GetWindowTextW(self._hWnd, stringBuffer, textLenInCharacters + 1)
  227. # TODO it's ambiguous if an error happened or the title text is just empty. Look into this later.
  228. return stringBuffer.value
  229. @property
  230. def visible(self):
  231. """Return ``True`` if the window is currently visible."""
  232. return isWindowVisible(self._hWnd)
  233. def cursor():
  234. """Returns the current xy coordinates of the mouse cursor as a two-integer
  235. tuple by calling the GetCursorPos() win32 function.
  236. Returns:
  237. (x, y) tuple of the current xy coordinates of the mouse cursor.
  238. """
  239. cursor = POINT()
  240. ctypes.windll.user32.GetCursorPos(ctypes.byref(cursor))
  241. return Point(x=cursor.x, y=cursor.y)
  242. def resolution():
  243. """Returns the width and height of the screen as a two-integer tuple.
  244. Returns:
  245. (width, height) tuple of the screen size, in pixels.
  246. """
  247. return Size(width=ctypes.windll.user32.GetSystemMetrics(0), height=ctypes.windll.user32.GetSystemMetrics(1))
  248. '''
  249. def displayWindowsUnderMouse(xOffset=0, yOffset=0):
  250. """This function is meant to be run from the command line. It will
  251. automatically display the location and RGB of the mouse cursor."""
  252. print('Press Ctrl-C to quit.')
  253. if xOffset != 0 or yOffset != 0:
  254. print('xOffset: %s yOffset: %s' % (xOffset, yOffset))
  255. resolution = size()
  256. try:
  257. while True:
  258. # Get and print the mouse coordinates.
  259. x, y = position()
  260. positionStr = 'X: ' + str(x - xOffset).rjust(4) + ' Y: ' + str(y - yOffset).rjust(4)
  261. # TODO - display windows under the mouse
  262. sys.stdout.write(positionStr)
  263. sys.stdout.write('\b' * len(positionStr))
  264. sys.stdout.flush()
  265. except KeyboardInterrupt:
  266. sys.stdout.write('\n')
  267. sys.stdout.flush()
  268. '''