MS Windows Clipboard

Alex Martelli aleaxit at yahoo.com
Tue Oct 24 05:45:48 EDT 2000


"Alex McHale" <daimun at home.com> wrote in message
news:dm7J5.8522$bj.761444 at news1.rdc1.il.home.com...
>   I was just wondering if anyone could clue me in how to read data from
the
> Windows clipboard?  I have been reading through all the past posts
regarding
> the clipboard; but none has had the information I'm needing.  They all
have
> to do with selections within the program itself.
>   The idea is I need to be able to monitor the Windows clipboard for a
> certain type of data.  Is there a way to do this in Python?

Sure, but you'll need Hammond's win32all extensions.  Module
win32clipboard exposes all of the Win32 clipboard API's.

For _monitoring_ the clipboard, specifically, you probably
want win32clipboard.SetClipboardViewer.  You need to have a
window (possibly hidden!) to pass to this function; said
window will then "receive a WM_DRAWCLIPBOARD message
whenever the content of the clipboard changes" (it must
process this message, and also WM_CHANGECBCHAIN, by using
SendMessage to pass them to the next window in the chain,
the handle to said window being the return value of the
SetClipboardViewer call).  When your monitoring ends, your
window must remove itself from the chain via a call to
ChangeClipboardChain.

Alternatively, you can make life simpler for yourself at
the cost of heavier system-load by polling periodically
rather than being alerted via message specifically when
the clipboard changes.

One way or another, IsClipboardFormatAvailable is the API
you can use to check if your supported format is on the
clipboard, if there is only one such format (it takes as
its argument the formatcode); if you want to support more
than one clipboard format, GetPriorityClipboardFormat will
take a tuple of clipboard format codes, in your preferred
priority order, and return the first one of these that is
currently in the clipboard, 0 if clipboard empty, -1 if
clipboard non-empty but none of the requested formats is
on it.  You need to OpenClipboard before you start looking
at it (so nobody else will be able to modify it while
you're looking!) and CloseClipboard when you're done looking.
OpenClipboard also takes a window-handle parameter, but you
can pass it a 0 to mean "no specific window, just give the
clipboard to `this thread'".
Note that opening/closing the clipboard does not affect
the data on it in any way, so, no worry -- just don't
call EmptyClipboard if all you want to do is look...!!!

Here is a very-toy-level example.

First, how to ensure a function of ours is called each
time the clipboard changes (assume all the import of
win32all modules implied from the calls...):

>>> def trac(*a):
...  print "trac",a
>>> dummywin=win32ui.CreateWnd()
>>> parent=win32ui.CreateWindowFromHandle(win32gui.GetActiveWindow())
>>> dummywin.CreateWindow(None,None,0,(0,)*4,parent,0)
>>> myhandle=dummywin.GetSafeHwnd()
>>> x.HookMessage(trac,win32con.WM_DRAWCLIPBOARD)
>>> previoushandle=win32clipboard.SetClipboardViewer(x.GetSafeHwnd())
trac ((208340352, 776, 4130772, 0, 0, (0, 0)),)

[I'd really like to use .GetDesktopWindow() rather than
.GetActiveWindow() to create the dummy window's parent,
but for some reason win32gui is not exposing GetDesktopWindow
in win32all build 135, so I fudged it...]

Anyway, we're now getting a stream of traceback prints
such as
>>> trac ((208340352, 776, 4130772, 0, 0, (0, 0)),)
trac ((208340352, 776, 4130772, 0, 0, (0, 0)),)
trac ((208340352, 776, 4130772, 0, 0, (0, 0)),)
trac ((208340352, 776, 4130772, 0, 0, (0, 0)),)
trac ((208340352, 776, 217252274, 0, 0, (0, 0)),)
>>>
>>> trac ((208340352, 776, 217252274, 0, 0, (0, 0)),)
trac ((208340352, 776, 4130772, 0, 0, (0, 0)),)

as the clipboard gets changed, so, we see that our
"hooking" has worked.  Now, of course, we want to
do something better than just tracing when our
clipboard-monitoring reveals a need for it...:

>>> def tracecb(*a):
...  win32clipboard.OpenClipboard()
...  res=[]; fmt=win32clipboard.EnumClipboardFormats(0)
...  while fmt:
...   res.append(fmt)
...   fmt=win32clipboard.EnumClipboardFormats(fmt)
...   win32clipboard.CloseClipboard()
...   print "fmts on cb:", res
...
>>> x.HookMessage(tracecb,win32con.WM_DRAWCLIPBOARD)
<function trac at 013580EC>

(note that HookMessage returns the previously hooking
callable for that message, if any).

Now the messages we get are things like
fmts on cb: [1, 13, 16, 7]
when text is copied to the clipboard,
fmts on cb: [49161, 49224, 49327, 49322, 15, 49158, 49159, 49171]
when files are copied to it, etc.


I'm not sure where the standard formats (CF_TEXT, etc)
are defined as symbolic constants in win32all -- they're
not in win32con, anyway.  Worst case, you can get the
numeric values by grepping through windows' include
files (get the Windows Platform SDK if needed... it's a
free download from the Microsoft site) and getting:

/*
 * Predefined Clipboard Formats
 */
#define CF_TEXT             1
#define CF_BITMAP           2
#define CF_METAFILEPICT     3
#define CF_SYLK             4
#define CF_DIF              5
#define CF_TIFF             6
#define CF_OEMTEXT          7
#define CF_DIB              8
#define CF_PALETTE          9
#define CF_PENDATA          10
#define CF_RIFF             11
#define CF_WAVE             12
#define CF_UNICODETEXT      13
#define CF_ENHMETAFILE      14
#if(WINVER >= 0x0400)
#define CF_HDROP            15
#define CF_LOCALE           16
#define CF_MAX              17
#endif /* WINVER >= 0x0400 */

#define CF_OWNERDISPLAY     0x0080
#define CF_DSPTEXT          0x0081
#define CF_DSPBITMAP        0x0082
#define CF_DSPMETAFILEPICT  0x0083
#define CF_DSPENHMETAFILE   0x008E

So, we see that the formats available when copying a text to
clipboard (1, 13, 16, 7) are text, Unicode-text, locale, and
oem-text.  Those for copying files, apart from the predictable
HDROP ('handle for drop-files operation'), are more mysterious.
But we can learn a little bit about them by adding one line
to our tracing-function (in the while-loop, after the append):

  if fmt>17: print fmt,win32clipboard.GetClipboardFormatName(fmt)

now the trace we get on file-copy-to-clipboard is:

>>> 49161 DataObject
49224 Shell IDList Array
49327 Preferred DropEffect
49322 Shell Object Offsets
49158 FileName
49159 FileNameW
49171 Ole Private Data
fmts on cb: [49161, 49224, 49327, 49322, 15, 49158, 49159, 49171]

which is rather interesting.  However, we still have not
shown we can _fetch_ the data of a certain type once we do
know it's there.  Let's take CF_TEXT for that, i.e., the
code number 1, clearly easiest.

Calling win32clipboard.GetClipboardData(1) is clearly the
way to get at the data we want.  It's documented as
returning "the handle of a clipboard object in the specified
format", so we might worry about using a PyHANDLE to
extract the text.  But, no!  Again programming in our
explorative way, we see, experimentally, that a *string*
is returned.  Thus, adding just one more line to our
function after the "if fmt>17" one...:

  elif fmt==1: print 'CB',win32clipboard.GetClipboardData(1)

gives us just the feedback we want...!

Similarly, if we try (after copying a file to the clipboard)
with format 49158, the one named 'FileName', we do get a
string with the filename -- and a trailing 0.  This can be
handy!  Careful, though: the undocumented conversion to
string seems to take place somewhat indiscriminately; if
asking for format 49159, the FileNameW (clearly meant to
be Unicode) we still get a string back (not a Unicode
string) and displaying its repr clearly shows all of the
zero-bytes in it.  So, care must be taken to get at the
data, unless we know it IS string-data, more or less.
If you ask for format 15 (HDROP), you get back a "string"
of 54 mysterious bytes -- so you may have some problems
exploring it unless you know the format from elsewhere
and are handy with the struct module:-).

But still -- I hope I've remedied the deficiency you have
noticed in "past posts"!-)


Alex






More information about the Python-list mailing list