import ctypes import ctypes.wintypes as wintypes from ctypes import POINTER, byref, windll, c_void_p from PIL import Image import io import struct import sys import os # Windows APIの準備 ole32 = windll.ole32 kernel32 = windll.kernel32 user32 = windll.user32 # テキスト取得用に追加 # --- 64bitアドレスを正しく扱うための型定義 --- kernel32.GlobalLock.argtypes = [c_void_p] kernel32.GlobalLock.restype = c_void_p kernel32.GlobalUnlock.argtypes = [c_void_p] kernel32.GlobalSize.argtypes = [c_void_p] kernel32.GlobalSize.restype = ctypes.c_size_t # テキスト取得用API定義 user32.OpenClipboard.argtypes = [wintypes.HWND] user32.OpenClipboard.restype = wintypes.BOOL user32.CloseClipboard.restype = wintypes.BOOL user32.GetClipboardData.argtypes = [wintypes.UINT] user32.GetClipboardData.restype = wintypes.HANDLE user32.IsClipboardFormatAvailable.argtypes = [wintypes.UINT] user32.IsClipboardFormatAvailable.restype = wintypes.BOOL # 定数定義 TYMED_HGLOBAL = 1 DVASPECT_CONTENT = 1 CF_DIB = 8 CF_UNICODETEXT = 13 # WindowsのUnicodeテキスト形式 GMEM_MOVEABLE = 0x0002 class FORMATETC(ctypes.Structure): _fields_ = [ ("cfFormat", wintypes.WORD), ("ptd", c_void_p), ("dwAspect", wintypes.DWORD), ("lindex", wintypes.LONG), ("tymed", wintypes.DWORD), ] class STGMEDIUM(ctypes.Structure): _fields_ = [ ("tymed", wintypes.DWORD), ("u", c_void_p), # data handle (HGLOBAL) ("pUnkForRelease", c_void_p), ] class IDataObject(ctypes.Structure): pass IDataObject._fields_ = [("lpVtbl", POINTER(c_void_p))] def get_clipboard_text(): """クリップボードからテキストを取得する(User32 API使用)""" if not user32.OpenClipboard(None): return None try: if not user32.IsClipboardFormatAvailable(CF_UNICODETEXT): return None h_global = user32.GetClipboardData(CF_UNICODETEXT) if not h_global: return None ptr = kernel32.GlobalLock(c_void_p(h_global)) if not ptr: return None try: # Unicode (UTF-16) としてデータを読み込む text = ctypes.c_wchar_p(ptr).value return text finally: kernel32.GlobalUnlock(c_void_p(h_global)) finally: user32.CloseClipboard() def get_clipboard_image_fixed(): """クリップボードからDIB画像を取得する(OLE API使用)""" # 既にOleInitializeされている前提、または明示的に呼ぶ # ole32.OleInitialize(None) # メイン側で制御 data_obj_ptr = POINTER(IDataObject)() hr = ole32.OleGetClipboard(byref(data_obj_ptr)) if hr != 0: return None try: # IDataObject::GetData のVtableインデックスは通常3 get_data_addr = data_obj_ptr.contents.lpVtbl[3] GetDataProto = ctypes.WINFUNCTYPE( ctypes.HRESULT, POINTER(IDataObject), POINTER(FORMATETC), POINTER(STGMEDIUM) ) get_data_func = GetDataProto(get_data_addr) except (IndexError, AttributeError): return None fmt = FORMATETC() fmt.cfFormat = CF_DIB fmt.ptd = None fmt.dwAspect = DVASPECT_CONTENT fmt.lindex = -1 fmt.tymed = TYMED_HGLOBAL stg = STGMEDIUM() hr = get_data_func(data_obj_ptr, byref(fmt), byref(stg)) img = None if hr == 0 and stg.tymed == TYMED_HGLOBAL: h_global = stg.u ptr = kernel32.GlobalLock(c_void_p(h_global)) size = kernel32.GlobalSize(c_void_p(h_global)) if ptr: try: dib_data = ctypes.string_at(ptr, size) if len(dib_data) >= 40: header_size = struct.unpack("`_") print("\n2. 画像表示 (imageディレクティブ):") print(f".. image:: {rel_path}\n :alt: {filename}") print("\n3. 図表表示 (figureディレクティブ - キャプション付き):") print(f".. figure:: {rel_path}\n :alt: {filename}\n :align: center\n\n {filename} の図面") print("="*60 + "\n") def print_rst_snippets_for_text(filepath): """テキストファイル用のrstスニペットを表示""" filename = os.path.basename(filepath) rel_path = "./" + filename # 相対パスと仮定 # ファイル拡張子から言語を推測(簡易版) _, ext = os.path.splitext(filename) lang_map = {'.py': 'python', '.js': 'javascript', '.bat': 'bat', '.sh': 'bash', '.cpp': 'cpp', '.h': 'cpp', '.txt': 'text'} language = lang_map.get(ext.lower(), 'text') print("\n" + "="*60) print(f"--- reStructuredText (rst) 用表示コード ({filename}) ---") print("="*60) print("\n1. 折り畳み表示 (sphinx-design dropdown) ※要拡張機能:") print(f".. dropdown:: {filename} の内容を表示(クリックで展開)\n :color: info\n :icon: file-code\n") print(f" .. literalinclude:: {rel_path}\n :language: {language}\n :linenos:") print("\n2.
タグ相当での埋め込み (parsed-literal):")
    print(".. parsed-literal::\n")
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            # 最初の数行だけサンプルとして表示
            lines = f.readlines()
            for line in lines[:5]:
                print(f"   {line.rstrip()}")
            if len(lines) > 5:
                print("   ...")
    except Exception:
         print("   (ファイル内容の読み込みに失敗しました)")

    print("\n3. 通常の外部ファイル取り込み (literalinclude):")
    print(f".. literalinclude:: {rel_path}\n   :language: {language}")
    
    print("="*60 + "\n")

if __name__ == "__main__":
    # OleInitializeはプロセス内で1回呼ぶ必要がある(OLE API使用のため)
    ole32.OleInitialize(None)
    
    # デフォルトのファイル名ベース
    base_filename = "clipboard_output"
    
    # 引数がある場合はそれをファイル名にする
    if len(sys.argv) > 1:
        # 安全なファイル名にするための簡易処理(パス区切り文字などを除去)
        requested_name = sys.argv[1]
        base_filename = os.path.splitext(os.path.basename(requested_name))[0]

    try:
        print(f"--- クリップボードの内容を確認中... ---")
        
        # 1. まずテキストがあるか確認(優先度が高い場合が多い)
        text_content = get_clipboard_text()
        if text_content is not None:
            print("[情報] クリップボード内にテキストが見つかりました。")
            
            # テキストファイルとして保存
            out_name = f"{base_filename}.txt"
            # Windowsの改行コード(CRLF)で保存
            with open(out_name, "w", encoding="utf-8", newline='\r\n') as f:
                f.write(text_content)
            
            print(f"成功: テキストを '{out_name}' として保存しました (UTF-8)。")
            print(f"文字数: {len(text_content)}")
            
            # rstスニペット表示
            print_rst_snippets_for_text(out_name)
            sys.exit(0)

        # 2. 次に画像があるか確認
        img = get_clipboard_image_fixed()
        if img:
            print("[情報] クリップボード内に画像(DIB)が見つかりました。")
            
            # 画像ファイルとして保存 (PNG推奨)
            out_name = f"{base_filename}.png"
            img.save(out_name, "PNG")
            
            print(f"成功: 画像を '{out_name}' として保存しました。")
            print(f"画像サイズ: {img.size[0]}x{img.size[1]}, モード: {img.mode}")
            
            # rstスニペット表示
            print_rst_snippets_for_image(out_name)
            sys.exit(0)

        # 3. どちらもない場合
        print("[情報] クリップボードにテキストまたは画像(CF_DIB)は見つかりませんでした。")
        # 形式リストを取得して表示(デバッグ用)
        if user32.OpenClipboard(None):
            try:
                formats = []
                fnum = user32.EnumClipboardFormats(0)
                while fnum:
                    formats.append(fnum)
                    fnum = user32.EnumClipboardFormats(fnum)
                if formats:
                    print(f"利用可能な形式ID: {formats}")
            finally:
                user32.CloseClipboard()

    except Exception as e:
        import traceback
        print(f"\n[エラー] 予期せぬエラーが発生しました。")
        traceback.print_exc()
    finally:
        # OleUninitializeはプロセス終了時に呼ぶ
        ole32.OleUninitialize()