Python 3.4からAPIをがしがし呼び出してWindowsのクリップボードを操作するクラスです。tkinterを使った書き込みが出来なかったので勉強がてら作ってみました。ウィンドウ関係の関数は敢えて外してあります。
大雑把な更新履歴
- 2020/04/15 RtlCopyMemoryをctypes.memmoveに置き換えました。Windows 10環境ではkernel32.dllにRtlCopyMemoryが見つからずにエラーが発生するためです。
- 2020/04/15 GlobalLock周りの致命的なミスを修正しました。
- 2020/04/16 RegisterClipboardFormat->RegisterClipboardFormatW
ソースコード
clipboard.py
from ctypes import * from enum import IntEnum from array import array class Clipboard(object): def __init__(self, default_ansi_encoding="sjis"): self.default_ansi_encoding = default_ansi_encoding def open(self): if not windll.user32.OpenClipboard(c_void_p(0)): raise WindowsError() return def close(self): if not windll.User32.CloseClipboard(): raise WindowsError() return def __enter__(self): self.open() return self def __exit__(self, exception_type, exception_value, traceback): self.close() return False def get_data_handle(self, format): GetClipboardData = windll.user32.GetClipboardData GetClipboardData.restype = c_void_p if isinstance(format, int): handle = GetClipboardData(c_uint(format)) elif isinstance(format, str): handle = GetClipboardData(c_wchar_p(format)) else: raise ArgumentError() if handle == c_void_p(0) and get_last_error() != 0: raise WindowsError() return handle def get_data_size(self, format): handle = self.get_data_handle(format) size = windll.kernel32.GlobalSize(c_uint32(handle)) if handle == c_void_p(0) and get_last_error() != 0: raise WindowsError() return size.value def get_data(self, format): handle = self.get_data_handle(format) if handle == c_void_p(0) and get_last_error() != 0: raise WindowsError() kernel32 = windll.kernel32 GlobalSize = kernel32.GlobalSize GlobalSize.restype = c_uint32 size = GlobalSize(handle) if size == 0 and get_last_error() != 0: raise WindowsError() pointer = kernel32.GlobalLock(c_void_p(handle)) data = bytearray(size) if pointer == c_void_p(0) and get_last_error() != 0: raise WindowsError() try: memmove((c_byte*size).from_buffer(data), pointer, size) finally: kernel32.GlobalUnlock(handle) return data def get_ansi_text(self, encoding=None): if encoding == None: encoding = self.default_ansi_encoding data = self.get_data(ClipboardFormats.text.value) return data[0:len(data)-1].decode(encoding) def get_unicode_text(self): data = self.get_data(ClipboardFormats.unicode_text.value) return data[0:len(data)-2].decode("utf-16") def set_data(self, format, data): user32 = windll.user32 kernel32 = windll.kernel32 GMEM_MOVEABLE = 0x0002 GlobalAlloc = kernel32.GlobalAlloc GlobalAlloc.restype = c_void_p handle = GlobalAlloc(GMEM_MOVEABLE, len(data)) if handle == c_void_p(0): raise WindowsError() try: GlobalLock = kernel32.GlobalLock GlobalLock.restype = c_void_p pointer = GlobalLock(handle) if pointer == c_void_p(0): raise WindowsError() try: memmove(pointer, data, len(data)) finally: kernel32.GlobalUnlock(handle) SetClipboardData = user32.SetClipboardData SetClipboardData.restype = c_void_p if SetClipboardData(format, handle) == c_void_p(0): raise WindowsError() except: kernel32.GlobalFree(handle) raise return def set_ansi_text(self, data, encoding=None): if encoding == None: encoding = self.default_ansi_encoding if not isinstance(data, str): raise ArgumentError() buf = (data + "\0").encode(encoding) self.set_data(ClipboardFormats.text.value, buf) return def set_unicode_text(self, data): if not isinstance(data, str): raise ArgumentError() buf = (data + "\0").encode("utf-16") self.set_data(ClipboardFormats.unicode_text.value, buf) return def empty(self): user32 = windll.user32 if not user32.EmptyClipboard(): raise WindowsError() return def count_formats(self): user32 = windll.user32 return user32.CountClipboardFormats() def register_format(self, format_name): user32 = windll.user32 return user32.RegisterClipboardFormatW(c_wchar_p(format_name)) def get_formats(self): EnumClipboardFormats = windll.user32.EnumClipboardFormats fmt = EnumClipboardFormats(0) formats = array("i") while fmt != 0: formats.append(fmt) fmt = EnumClipboardFormats(fmt) return formats def is_format_available(self, format): user32 = windll.user32 IsClipboardFormatAvailable = user32.IsClipboardFormatAvailable IsClipboardFormatAvailable.restype = c_bool return IsClipboardFormatAvailable(format_name).value def get_format_name(self, format, expand_size=256): user32 = windll.user32 GetClipboardFormatNameW = user32.GetClipboardFormatNameW GetClipboardFormatNameW.argtypes = [c_uint32, c_wchar_p, c_int32] buffer = create_unicode_buffer(expand_size) ret = GetClipboardFormatNameW(format, buffer, len(buffer)) while ret + 1 == len(buffer): if get_last_error() != 0: raise WindowsError() buffer = create_unicode_buffer(len(buffer) + expand_size) ret = GetClipboardFormatNameW(format, buffer, len(buffer)) return buffer.value def get_format_names(self, formats, expand_size=256): return [self.get_format_name(format, expand_size) for format in self.get_formats()] #class Clipboard class ClipboardFormats(IntEnum): text = 1 bitmap = 2 metafilepict = 3 ylk = 4 dif = 5 tiff = 6 oem_text = 7 dib = 8 palette = 9 pen_data = 10 riff = 11 wave = 12 unicode_text = 13 enhmetafile = 14 drop_handle = 15 locale = 16 dib_v5 = 17 owner_display = 0x0080 dsp_text = 0x0081 dsp_bitmap = 0x0082 dsp_metafilepict = 0x0083 dsp_enhmetafile = 0x008f private_first = 0x0200 private_last = 0x02ff gdi_object_first = 0x0300 gdi_object_last = 0x03ff
サンプルコード
文字列を書き込む
from clipboard import Clipboard if __name__ == "__main__": with Clipboard() as clipboard: clipboard.empty() clipboard.set_ansi_text("あいうえお", "Shift_JIS")
格納されているデータのフォーマットを列挙する
from clipboard import Clipboard if __name__ == "__main__": with Clipboard() as clipboard: formats = clipboard.get_formats() for format in formats: print("\"{0:s}\" ({1:0>4X})".format( clipboard.get_format_name(format), format))
蛇足
UTF-8とUTF-16
Python何かでファイル先頭に文字コードとして指定するのはUTF-8ですが、Windows(XP以降)の内部文字コード(W版関数の扱うエンコード)はUTF-16です。
WCHARやwchar_tの実体がushort(unsigned short、16ビット符号無し整数)なので当たり前といえば当たり前なのですが、思い込みで数時間悩んでいたので今後の為にメモしておきます。