windows.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """
  2. This module implements clipboard handling on Windows using ctypes.
  3. """
  4. import contextlib
  5. import ctypes
  6. from ctypes import c_size_t, c_wchar, c_wchar_p, get_errno, sizeof
  7. import time
  8. from .exceptions import PyperclipWindowsException
  9. class CheckedCall(object):
  10. def __init__(self, f):
  11. super(CheckedCall, self).__setattr__("f", f)
  12. def __call__(self, *args):
  13. ret = self.f(*args)
  14. if not ret and get_errno():
  15. raise PyperclipWindowsException("Error calling " + self.f.__name__)
  16. return ret
  17. def __setattr__(self, key, value):
  18. setattr(self.f, key, value)
  19. def init_windows_clipboard():
  20. from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
  21. HINSTANCE, HMENU, BOOL, UINT, HANDLE)
  22. windll = ctypes.windll
  23. safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
  24. safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
  25. INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
  26. safeCreateWindowExA.restype = HWND
  27. safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
  28. safeDestroyWindow.argtypes = [HWND]
  29. safeDestroyWindow.restype = BOOL
  30. OpenClipboard = windll.user32.OpenClipboard
  31. OpenClipboard.argtypes = [HWND]
  32. OpenClipboard.restype = BOOL
  33. safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
  34. safeCloseClipboard.argtypes = []
  35. safeCloseClipboard.restype = BOOL
  36. safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
  37. safeEmptyClipboard.argtypes = []
  38. safeEmptyClipboard.restype = BOOL
  39. safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
  40. safeGetClipboardData.argtypes = [UINT]
  41. safeGetClipboardData.restype = HANDLE
  42. safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
  43. safeSetClipboardData.argtypes = [UINT, HANDLE]
  44. safeSetClipboardData.restype = HANDLE
  45. safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
  46. safeGlobalAlloc.argtypes = [UINT, c_size_t]
  47. safeGlobalAlloc.restype = HGLOBAL
  48. safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
  49. safeGlobalLock.argtypes = [HGLOBAL]
  50. safeGlobalLock.restype = LPVOID
  51. safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
  52. safeGlobalUnlock.argtypes = [HGLOBAL]
  53. safeGlobalUnlock.restype = BOOL
  54. GMEM_MOVEABLE = 0x0002
  55. CF_UNICODETEXT = 13
  56. @contextlib.contextmanager
  57. def window():
  58. """
  59. Context that provides a valid Windows hwnd.
  60. """
  61. # we really just need the hwnd, so setting "STATIC"
  62. # as predefined lpClass is just fine.
  63. hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
  64. None, None, None, None)
  65. try:
  66. yield hwnd
  67. finally:
  68. safeDestroyWindow(hwnd)
  69. @contextlib.contextmanager
  70. def clipboard(hwnd):
  71. """
  72. Context manager that opens the clipboard and prevents
  73. other applications from modifying the clipboard content.
  74. """
  75. # We may not get the clipboard handle immediately because
  76. # some other application is accessing it (?)
  77. # We try for at least 500ms to get the clipboard.
  78. t = time.time() + 0.5
  79. success = False
  80. while time.time() < t:
  81. success = OpenClipboard(hwnd)
  82. if success:
  83. break
  84. time.sleep(0.01)
  85. if not success:
  86. raise PyperclipWindowsException("Error calling OpenClipboard")
  87. try:
  88. yield
  89. finally:
  90. safeCloseClipboard()
  91. def copy_windows(text):
  92. # This function is heavily based on
  93. # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard
  94. with window() as hwnd:
  95. # http://msdn.com/ms649048
  96. # If an application calls OpenClipboard with hwnd set to NULL,
  97. # EmptyClipboard sets the clipboard owner to NULL;
  98. # this causes SetClipboardData to fail.
  99. # => We need a valid hwnd to copy something.
  100. with clipboard(hwnd):
  101. safeEmptyClipboard()
  102. if text:
  103. # http://msdn.com/ms649051
  104. # If the hMem parameter identifies a memory object,
  105. # the object must have been allocated using the
  106. # function with the GMEM_MOVEABLE flag.
  107. count = len(text) + 1
  108. handle = safeGlobalAlloc(GMEM_MOVEABLE,
  109. count * sizeof(c_wchar))
  110. locked_handle = safeGlobalLock(handle)
  111. ctypes.memmove(c_wchar_p(locked_handle),
  112. c_wchar_p(text), count * sizeof(c_wchar))
  113. safeGlobalUnlock(handle)
  114. safeSetClipboardData(CF_UNICODETEXT, handle)
  115. def paste_windows():
  116. with clipboard(None):
  117. handle = safeGetClipboardData(CF_UNICODETEXT)
  118. if not handle:
  119. # GetClipboardData may return NULL with errno == NO_ERROR
  120. # if the clipboard is empty.
  121. # (Also, it may return a handle to an empty buffer,
  122. # but technically that's not empty)
  123. return ""
  124. return c_wchar_p(handle).value
  125. return copy_windows, paste_windows