0

The main answer from How to paste a Numpy array to Excel works to copy a Numpy array in the clipboard, ready to be pasted in Excel:

import numpy as np
import win32clipboard as clipboard
def toClipboardForExcel(array):
    array_string = "\r\n".join("\t".join(line.astype(str)).replace("\n","") for line in array)
    clipboard.OpenClipboard()
    clipboard.EmptyClipboard()
    clipboard.SetClipboardText(array_string)
    clipboard.CloseClipboard()
Y = np.arange(64).reshape((8, 8))
toClipboardForExcel(Y)

Is it possible to do this without the extra win32 package dependency? (I don't use it currently in my project and I'd like to avoid adding it only for clipboard, NB: I don't use pandas either)

I already tried with os.system(`echo string | clip`) but it doesn't work for multiline content (containing \n).

Or maybe is OpenClipboard, SetClipboardText, etc. accessible via ctypes (which I already use)?

NB: this is not a duplicate of Python script to copy text to clipboard because the latter is general, and with extra dependencies, and here in my question, we would like to avoid new dependencies.

8
  • Does this answer your question? Python script to copy text to clipboard Commented Dec 12, 2023 at 16:47
  • @0stone0 No, this is specific for Numpy + no extra dependency. Commented Dec 12, 2023 at 16:50
  • 1
    Why tho? You're just making a string that you need to copy to the clipboard right? The marked duplicate show multiple options if you scroll further ;) Commented Dec 12, 2023 at 16:52
  • @0stone0 Yes I also looked the other answers - which answer specifically is nearly dependency-free on Windows (on only dependent on Numpy or ctypes)? Commented Dec 12, 2023 at 16:59
  • @0stone0 I just looked again, there is no solution in the linked question with only Numpy + ctypes. Thus it's not a duplicate. Commented Dec 12, 2023 at 17:18

2 Answers 2

4

Here is a ctypes-only example to set any Python str-type text on the clipboard and read it back. Note that text should not contain nulls as the CF_UNICODETEXT type expects to be null-terminated text.

import ctypes as ct
import ctypes.wintypes as w

CF_UNICODETEXT = 13
NO_ERROR = 0
SIZE_T = ct.c_size_t
GMEM_MOVEABLE = 0x0002

# Error handlers to raise exceptions on failure.

def boolcheck(result, func, args):
    if not result:
        raise ct.WinError(ct.get_last_error())

def nullcheck(result, func, args):
    if result is None:
        raise ct.WinError(ct.get_last_error())
    return result

def zeroerrorcheck(result, func, args):
    if not result:
        err = ct.get_last_error()
        if err != NO_ERROR:
            raise ct.WinError(err)
    return result

# Capture GetLastError() code after each call.
# Fully specify argtypes and restype for ctypes type-checking.

kernel32 = ct.WinDLL('kernel32', use_last_error=True)
GlobalLock = kernel32.GlobalLock
GlobalLock.argtypes = w.HGLOBAL,
GlobalLock.restype = w.LPVOID
GlobalLock.errcheck = nullcheck
GlobalAlloc = kernel32.GlobalAlloc
GlobalAlloc.argtypes = w.UINT, SIZE_T
GlobalAlloc.restype = w.HGLOBAL
GlobalAlloc.errcheck = nullcheck
GlobalUnlock = kernel32.GlobalUnlock
GlobalUnlock.argtypes = w.HGLOBAL,
GlobalUnlock.restype = w.BOOL
GlobalUnlock.errcheck = zeroerrorcheck

user32 = ct.WinDLL('user32', use_last_error=True)
OpenClipboard = user32.OpenClipboard
OpenClipboard.argtypes = w.HWND,
OpenClipboard.restype = w.BOOL
OpenClipboard.errcheck = boolcheck
GetClipboardData = user32.GetClipboardData
GetClipboardData.argtypes = w.UINT,
GetClipboardData.restype = w.HANDLE
GetClipboardData.errcheck = nullcheck
SetClipboardData = user32.SetClipboardData
SetClipboardData.argtypes = w.UINT, w.HANDLE
SetClipboardData.restype = w.HANDLE
SetClipboardData.errcheck = nullcheck
CloseClipboard = user32.CloseClipboard
CloseClipboard.argtypes = ()
CloseClipboard.restype = w.BOOL
CloseClipboard.errcheck = boolcheck
EmptyClipboard = user32.EmptyClipboard
EmptyClipboard.argtypes = ()
EmptyClipboard.restype = w.BOOL
EmptyClipboard.errcheck = boolcheck
GetForegroundWindow = user32.GetForegroundWindow
GetForegroundWindow.argtypes = ()
GetForegroundWindow.restype = w.HWND

def get_clipboard_text():
    OpenClipboard(GetForegroundWindow())
    hmem = GetClipboardData(CF_UNICODETEXT)
    pmem = GlobalLock(hmem)
    text = ct.wstring_at(pmem)
    GlobalUnlock(hmem)
    CloseClipboard()
    return text

def set_clipboard_text(text):
    ztext = text + '\x00'  # null terminator required
    OpenClipboard(None)
    EmptyClipboard()
    hmem = GlobalAlloc(GMEM_MOVEABLE, len(ztext) * ct.sizeof(w.WCHAR))
    pmem = GlobalLock(hmem)
    btext = ztext.encode('utf-16le')
    ct.memmove(pmem, btext, len(btext))
    GlobalUnlock(hmem)
    SetClipboardData(CF_UNICODETEXT, hmem)
    CloseClipboard()
    
set_clipboard_text('马克')
print(get_clipboard_text())

Per OpenClipboard documentation remarks:

If an application calls OpenClipboard with hwnd set to NULL, EmptyClipboard sets the clipboard owner to NULL; this causes SetClipboardData to fail.

My experience is that using NULL (None in Python) with OpenClipboard does not cause SetClipboardData to fail, but doesn't prevent other apps using the clipboard while open (potential race condition?). When a valid Window handle is used, other apps will fail to open the clipboard until CloseClipboard is called, so some retrying and error checking will be needed. Probably best to use a valid owner. I've used GetForegroundWindow above.

Sign up to request clarification or add additional context in comments.

Comments

0

I have not looked at all the libraries which provide windows clipboard functionality, but it is in general not an extremely straightforward thing. Windows requires some boilerplate which is typically more useful for building a full GUI application. The clipboard is an inherently user experience thing, so it is quite natural to tie it with GUI related functions (like requiring a window handle to open the clipboard in the first place).

If you have distribution challenges (ie: distributing code to non-networked computers) which make adding pip dependencies difficult, I would simply take an existing project with a suitable license and copy it into your project as a submodule. The linked duplicate mentions Pyperclip which itself requires no external dependencies for use with windows, and effectively does what you want (interact with windows clipboard via ctypes).

If you really need to implement it yourself, I would still refer to Pyperclip to learn all the details of the process:

  1. create window handle
  2. acquire clipboard control
  3. allocate memory for your string
  4. copy data from python string to allocated memory
  5. pass data to the clipboard
  6. cleanup (and don't forget error handling all along the way)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.