Search code examples
pythonwindowsclipboardpywin32

How to get and set non-standard formats to clipboard, using python win32clipboard?


Want to copy and save multiple non-text elements from an obscure software, and later set them back in clipboard. (for context, it's a PLC programming environment - UniLogic, and elements in question are ladder rungs).

Copying the elements and using function win32clipboard.EnumClipboardFormats(0)
Returns 49161 or C009 in hex

Using win32clipboard.GetClipboardFormatName(49161)
Returns DataObject

Then

data = win32clipboard.GetClipboardData(49161)
print(data)

Shows b'\x00\x00\x00\x00\x00\x00\x00\x00'

I can then set this data to clipboard, without errors, but the paste function doesn't do anything anymore.

win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(49161,data)
win32clipboard.CloseClipboard()

Seems that I can't access the full data from clipboard.
From what I read, some programs just give the clipboard the pointer to data, not data itself. But why can't I reuse this pointer, with the said program still open? Is there any way around this?

Any help appreciated, thank you.


Solution

  • [MS.Docs]: Clipboard is MS's root reference for clipboard documentation.

    For PyWin32 documentation, check [ME.TimGolden]: Python for Win32 Extensions Help ((officially) referenced by [GitHub]: mhammond/pywin32 - pywin32).

    GetClipboardData returns a handle (pointer) to the clipboard data. Depending on its format, the memory contents should be interpreted and handled. It's impossible to handle all possible formats, because virtually anyone can register their own format (having its data particularities).

    The PyWin32 wrapper (win32clipboard.GetClipboardData) "decodes" data for a few basic formats (that mainly work with text). Check sources (win32clipboardmodule.cpp) for more details.
    For the other formats (including registered ones), it offers:

    1. GetClipboardDataHandle - returns the raw handle (from WinAPI)

    2. GetGlobalMemory - "decodes" handle data

    Now, even if after #2. for some formats (CF_DIB) data would be available, for many, it won't. I assume that your format is in the latter category (an object is composed of other objects, which in turn are composed of others and so on, and each object aggregates sub-objects via pointers (references)), so I don't think you'd be able to achieve your goal OOTB, probably you should use some API (if) provided by that program to serialize / deserialize objects.

    I prepared a dummy example.

    code00.py:

    #!/usr/bin/env python
    
    import sys
    from pprint import pprint as pp
    
    import win32gui as wgui
    import win32clipboard as wcb
    import win32con as wcon
    
    
    def clipboard_formats():
        l = [wcb.EnumClipboardFormats(0)]
        while l[-1]:
            l.append(wcb.EnumClipboardFormats(l[-1]))
        l.pop()
        ret = {}
        for e in l:
            try:
                name = wcb.GetClipboardFormatName(e)
            except:
                name = ""
            ret[e] = name
        return ret
    
    
    def is_simple_format(fmt):  # @TODO: dummy!!!
        return fmt in (
            wcon.CF_TEXT,
            wcon.CF_OEMTEXT,
            wcon.CF_UNICODETEXT,
            wcon.CF_LOCALE
        )
    
    
    def main(*argv):
        try:
            hwnd = int(argv[0])
        except (IndexError, ValueError):
            hwnd = None
        print("Handling clipboard for window: {:} ({:s})".format(hwnd, wgui.GetWindowText(hwnd or wgui.GetActiveWindow())))
        clip = wcb.OpenClipboard(hwnd)
        fmts_dict = clipboard_formats()
        print("Available formats:")
        pp(fmts_dict, sort_dicts=0)
        try:
            fmt = int(argv[1])
            fmts_dict[fmt]
        except (IndexError, ValueError, KeyError):
            fmt = tuple(fmts_dict.keys())[0]  # 1st one
        print("Using format {:d} ({:s})".format(fmt, fmts_dict[fmt]))
    
        if is_simple_format(fmt):
            print("Handling simple (text) format data")
            data = wcb.GetClipboardData(fmt)
            print("--- Data ---\n{:}\n--- Data end ---".format(data))
        else:
            print("Handling custom format data")
            hg = wcb.GetClipboardDataHandle(fmt)
            print("HGLOBAL: {:}".format(hg))
            data = wcb.GetGlobalMemory(hg)
            data_len = getattr(data, "__len__", lambda: -1)()
            print("Data length: {:d}".format(data_len))
            if data_len < 0x100:
                print("--- Data ---\n{:}\n--- Data end ---".format(data))
    
        wcb.EmptyClipboard()
        wcb.SetClipboardData(fmt, data)
    
        wcb.CloseClipboard()
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)
    

    Output:

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q074838210]> sopr.bat
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [prompt]> :: Run the script for an LibreOffice sheet with a couple of dummy cells copied in the clipboard
    [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" ./code00.py 42211480
    Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
    
    Handling clipboard for window: 42211480 (Untitled 1 - LibreOffice Calc)
    Available formats:
    {49161: 'DataObject',
     50348: 'Star Embed Source (XML)',
     50368: 'Star Object Descriptor (XML)',
     49897: 'GDIMetaFile',
     14: '',
     3: '',
     49513: 'PNG',
     8: '',
     50286: 'Windows Bitmap',
     50377: 'HTML (HyperText Markup Language)',
     49435: 'HTML Format',
     4: '',
     50375: 'Link',
     5: '',
     13: '',
     1: '',
     49274: 'Rich Text Format',
     50127: 'Richtext Format',
     49171: 'Ole Private Data',
     16: '',
     7: '',
     2: '',
     17: ''}
    Using format 49161 (DataObject)
    Handling custom format data
    HGLOBAL: 2484318176448
    Data length: 8
    --- Data ---
    b'\x84\r`\x04\x00\x00\x00\x00'
    --- Data end ---
    
    Done.
    
    
    [prompt]> :: Copy some text from above lines and run the script for "current" window
    [prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" ./code00.py
    Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
    
    Handling clipboard for window: None ()
    Available formats:
    {13: '', 16: '', 1: '', 7: ''}
    Using format 13 ()
    Handling simple (text) format data
    --- Data ---
    sing format 49161 (
    andling custom form
    GLOBAL: 24843181764
    ata length: 8
    -- Data ---
    '\x84\r`\x04\x00\x0
    --- Data end ---
    
    Done.
    

    More details on the following tasks: