123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- """
- This module implements clipboard handling on Windows using ctypes.
- """
- import contextlib
- import ctypes
- from ctypes import c_size_t, c_wchar, c_wchar_p, get_errno, sizeof
- import time
- from .exceptions import PyperclipWindowsException
- class CheckedCall(object):
- def __init__(self, f):
- super(CheckedCall, self).__setattr__("f", f)
- def __call__(self, *args):
- ret = self.f(*args)
- if not ret and get_errno():
- raise PyperclipWindowsException("Error calling " + self.f.__name__)
- return ret
- def __setattr__(self, key, value):
- setattr(self.f, key, value)
- def init_windows_clipboard():
- from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
- HINSTANCE, HMENU, BOOL, UINT, HANDLE)
- windll = ctypes.windll
- safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
- safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
- INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
- safeCreateWindowExA.restype = HWND
- safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
- safeDestroyWindow.argtypes = [HWND]
- safeDestroyWindow.restype = BOOL
- OpenClipboard = windll.user32.OpenClipboard
- OpenClipboard.argtypes = [HWND]
- OpenClipboard.restype = BOOL
- safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
- safeCloseClipboard.argtypes = []
- safeCloseClipboard.restype = BOOL
- safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
- safeEmptyClipboard.argtypes = []
- safeEmptyClipboard.restype = BOOL
- safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
- safeGetClipboardData.argtypes = [UINT]
- safeGetClipboardData.restype = HANDLE
- safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
- safeSetClipboardData.argtypes = [UINT, HANDLE]
- safeSetClipboardData.restype = HANDLE
- safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
- safeGlobalAlloc.argtypes = [UINT, c_size_t]
- safeGlobalAlloc.restype = HGLOBAL
- safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
- safeGlobalLock.argtypes = [HGLOBAL]
- safeGlobalLock.restype = LPVOID
- safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
- safeGlobalUnlock.argtypes = [HGLOBAL]
- safeGlobalUnlock.restype = BOOL
- GMEM_MOVEABLE = 0x0002
- CF_UNICODETEXT = 13
- @contextlib.contextmanager
- def window():
- """
- Context that provides a valid Windows hwnd.
- """
- # we really just need the hwnd, so setting "STATIC"
- # as predefined lpClass is just fine.
- hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
- None, None, None, None)
- try:
- yield hwnd
- finally:
- safeDestroyWindow(hwnd)
- @contextlib.contextmanager
- def clipboard(hwnd):
- """
- Context manager that opens the clipboard and prevents
- other applications from modifying the clipboard content.
- """
- # We may not get the clipboard handle immediately because
- # some other application is accessing it (?)
- # We try for at least 500ms to get the clipboard.
- t = time.time() + 0.5
- success = False
- while time.time() < t:
- success = OpenClipboard(hwnd)
- if success:
- break
- time.sleep(0.01)
- if not success:
- raise PyperclipWindowsException("Error calling OpenClipboard")
- try:
- yield
- finally:
- safeCloseClipboard()
- def copy_windows(text):
- # This function is heavily based on
- # http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard
- with window() as hwnd:
- # http://msdn.com/ms649048
- # If an application calls OpenClipboard with hwnd set to NULL,
- # EmptyClipboard sets the clipboard owner to NULL;
- # this causes SetClipboardData to fail.
- # => We need a valid hwnd to copy something.
- with clipboard(hwnd):
- safeEmptyClipboard()
- if text:
- # http://msdn.com/ms649051
- # If the hMem parameter identifies a memory object,
- # the object must have been allocated using the
- # function with the GMEM_MOVEABLE flag.
- count = len(text) + 1
- handle = safeGlobalAlloc(GMEM_MOVEABLE,
- count * sizeof(c_wchar))
- locked_handle = safeGlobalLock(handle)
- ctypes.memmove(c_wchar_p(locked_handle),
- c_wchar_p(text), count * sizeof(c_wchar))
- safeGlobalUnlock(handle)
- safeSetClipboardData(CF_UNICODETEXT, handle)
- def paste_windows():
- with clipboard(None):
- handle = safeGetClipboardData(CF_UNICODETEXT)
- if not handle:
- # GetClipboardData may return NULL with errno == NO_ERROR
- # if the clipboard is empty.
- # (Also, it may return a handle to an empty buffer,
- # but technically that's not empty)
- return ""
- return c_wchar_p(handle).value
- return copy_windows, paste_windows
|