MS Windows Clipboard

221 views
Skip to first unread message

Alex McHale

unread,
Oct 23, 2000, 11:29:13 PM10/23/00
to
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?

Alex


Alex Martelli

unread,
Oct 24, 2000, 3:00:00 AM10/24/00
to
"Alex McHale" <dai...@home.com> wrote in message
news:dm7J5.8522$bj.7...@news1.rdc1.il.home.com...

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


Mark Hammond

unread,
Oct 24, 2000, 11:27:15 PM10/24/00
to
[Alex]

> [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...]

It will be in 137 ;-)

> I'm not sure where the standard formats (CF_TEXT, etc)
> are defined as symbolic constants in win32all -- they're

I've added these to win32clipboard.

> 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

Fixed the docs ;-)

> handy! Careful, though: the undocumented conversion to
> string seems to take place somewhat indiscriminately; if
> asking for format 49159, the FileNameW (clearly meant to

Note that if you ask for CF_UNICODETEXT, you should get back a Unicode
object.

That was a very useful post! Thanks...

Mark.

Alex Martelli

unread,
Oct 25, 2000, 3:00:00 AM10/25/00
to
"Mark Hammond" <Ma...@ActiveState.com> wrote in message
news:nqsJ5.14162$Ab3....@news-server.bigpond.net.au...

> [Alex]
> > [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...]
>
> It will be in 137 ;-)

Thanks!


> > I'm not sure where the standard formats (CF_TEXT, etc)
> > are defined as symbolic constants in win32all -- they're
>
> I've added these to win32clipboard.

Excellent idea! Thanks again.


> > 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
>
> Fixed the docs ;-)

A third Very Good Thing. Although getting the handle might
not be a bad idea either (e.g, for a DragQueryFile I'd like
the bare HDROP...).


> > handy! Careful, though: the undocumented conversion to
> > string seems to take place somewhat indiscriminately; if
> > asking for format 49159, the FileNameW (clearly meant to
>
> Note that if you ask for CF_UNICODETEXT, you should get back a Unicode
> object.

Oh, good! But I don't think the CF_UNICODETEXT is among
the available formats when what's on the clipboard is a
file or group of files (CF_TEXT definitely isn't).


> That was a very useful post! Thanks...

You're most welcome, thank *you* for all the wonderful things
you've done for Python users on Windows, and please feel free
to use the text in that post, or an edited version of it, in
any way you like (as an example of how to approach a Windows
programming task with Python and win32all, for example:-).


Alex


Mark Hammond

unread,
Oct 25, 2000, 8:01:16 PM10/25/00
to
"Alex Martelli" <ale...@yahoo.com> wrote in message
news:8t6fm...@news1.newsguy.com...

> "Mark Hammond" <Ma...@ActiveState.com> wrote in message

> > > 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
> >
> > Fixed the docs ;-)
>

> A third Very Good Thing. Although getting the handle might
> not be a bad idea either (e.g, for a DragQueryFile I'd like
> the bare HDROP...).

Maybe it would make sense to add a new optional param, indicating the
type of the result. You could ask the the "default" (current
behaviour), or explicitly for one of either string, unicode, or
integer handle. I'm not sure we expose enough functions to make the
handle useful, but it does provide room for growth.

unicode would _not_ provide an encoding option - it would assume
standard Windows UCS2. If you want to assume an encoded Unicode
string, you would ask for a string object, and make the conversion
yourself.

What would this param look like - maybe a simple string "string",
"unicode", "handle", with None or "" == default?

> and please feel free
> to use the text in that post, or an edited version of it, in
> any way you like (as an example of how to approach a Windows
> programming task with Python and win32all, for example:-).

Well, please feel free to submit to me a little, simple HTML file with
any content you like. Ensure you are attributed as author. I will
then ensure that it gets in the win32all and ActivePython
documentation sets. John Nielsen has contributed a large amount of
documentation that way...

Thanks,

Mark.

Alex Martelli

unread,
Oct 26, 2000, 3:36:09 PM10/26/00
to
"Mark Hammond" <Ma...@ActiveState.com> wrote in message
news:gvKJ5.15107$Ab3....@news-server.bigpond.net.au...
[snip]

> > > > 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
[snip]

> > A third Very Good Thing. Although getting the handle might
> > not be a bad idea either (e.g, for a DragQueryFile I'd like
> > the bare HDROP...).
>
> Maybe it would make sense to add a new optional param, indicating the
> type of the result. You could ask the the "default" (current
> behaviour), or explicitly for one of either string, unicode, or
> integer handle. I'm not sure we expose enough functions to make the
> handle useful, but it does provide room for growth.

What an excellent idea!


> unicode would _not_ provide an encoding option - it would assume
> standard Windows UCS2. If you want to assume an encoded Unicode
> string, you would ask for a string object, and make the conversion
> yourself.

Makes sense. Non-UCS2 Unicode would only come from custom
datasources anyway.


> What would this param look like - maybe a simple string "string",
> "unicode", "handle", with None or "" == default?

Or maybe type(""), type(u""), type(PyHandle)...?


Alex

Mark Hammond

unread,
Oct 26, 2000, 7:13:20 PM10/26/00
to
"Alex Martelli" <ale...@yahoo.com> wrote in message
news:8ta16...@news2.newsguy.com...

> "Mark Hammond" <Ma...@ActiveState.com> wrote in message

We are discussing a new optional param for the Win32 clipboard
functions that would specify the type of the returned data.

I proposed a simple string:


> > What would this param look like - maybe a simple string
> > "string", "unicode", "handle", with None or "" == default?

Alex suggested a type object:


> Or maybe type(""), type(u""), type(PyHandle)...?

Your idea sounds more "correct", but also more verbose and less
"handy"

OTOH, using the types module makes it better:

GetClipboardText(..., types.UnicodeType)

I would probably use types.IntType for a handle, as we may not be able
to provide automatic handle cleanup. I'm afraid I can't recall the
exact semantics, and haven't the time to look.

Any votes on this? Should we go for:

GetClipboardText(..., types.UnicodeType)
or
GetClipboardText(..., "unicode")

?

Mark.

Steve Holden

unread,
Oct 26, 2000, 9:39:14 PM10/26/00
to

The former seems cleaner. It would also be quicker, though I'm not
usually one to obsess about a few wasted machine cycles.

When I use Tkinter I always shudder to think about all the data that's
being passed as strings when enumerated constants or simple integers
would be better.

Some-things-are-worth-not-inheriting-from-your-predecessor-ly y'rs - steve
--
Helping people meet their information needs with training and technology.
703 967 0887 sho...@bellatlantic.net http://www.holdenweb.com/


Fredrik Lundh

unread,
Oct 27, 2000, 2:58:46 AM10/27/00
to
Steve Holden wrote:
> When I use Tkinter I always shudder to think about all the data that's
> being passed as strings when enumerated constants or simple integers
> would be better.

help is on the way:

http://w1.132.telia.com/~u13208596/tkinter

</F>


Alex Martelli

unread,
Oct 27, 2000, 7:23:17 AM10/27/00
to
"Mark Hammond" <Ma...@ActiveState.com> wrote in message
news:kU2K5.900$Tq6....@news-server.bigpond.net.au...
[snip]

> > > What would this param look like - maybe a simple string
> > > "string", "unicode", "handle", with None or "" == default?
>
> Alex suggested a type object:
> > Or maybe type(""), type(u""), type(PyHandle)...?
>
> Your idea sounds more "correct", but also more verbose and less
> "handy"
>
> OTOH, using the types module makes it better:
>
> GetClipboardText(..., types.UnicodeType)

Is types.UnicodeType "less verbose and handier" than type(u'')...?!
I count 8 more characters plus the need to import types.

Anyway, that stylistic choice is up to client-code, if you use the
type object as the discriminator of what type of result is desired
in the GetClipboardData function you expose; calling it with either
expression will pass it the same object and get the same result.


> GetClipboardText(..., types.UnicodeType)
> or
> GetClipboardText(..., "unicode")

GetClipboardData is more what I had in mind, although a
utility function called GetClipboardText could, I guess,
surely also be introduced (it's not currently in the
module win32clipboard -- or, at least, not in its docs),
for symmetry with the Set functions (which do exist in
both flavours -- convenient ...Text, general ...Data).


Alex

Steve Holden

unread,
Oct 27, 2000, 8:49:53 AM10/27/00
to

All this, just to stop me shuddering? How kind! :~)

I'm sure a 2- to 10-times speedup won;t hurt, either. Great stuff.

regards
Steve

Paul Moore

unread,
Oct 27, 2000, 9:00:24 AM10/27/00
to
On Fri, 27 Oct 2000 01:13:20 +0200, "Mark Hammond"
<Ma...@ActiveState.com> wrote:

>"Alex Martelli" <ale...@yahoo.com> wrote in message
>news:8ta16...@news2.newsguy.com...
>> "Mark Hammond" <Ma...@ActiveState.com> wrote in message
>
>We are discussing a new optional param for the Win32 clipboard
>functions that would specify the type of the returned data.

While we're on the subject, I recall from Perl's clipboard module a
function which waits for the clipboard to change. The Python module
doesn't have this, and I'm not sure how I would implement it. (It's
not the sort of thing which is needed very often, I guess, but I have
a specific application which would be cleaner with it...) I think I
need a clipboard viewer, with a hidden window to receive notification
messages. But it sounds quite messy to implement. Two questions -
first, are all the pieces available in win32all to implement this
myself, and second, would it be worth adding a method like this to
win32clipboard?

Thanks,
Paul Moore

Alex Martelli

unread,
Oct 28, 2000, 6:40:49 PM10/28/00
to
"Paul Moore" <paul....@uk.origin-it.com> wrote in message
news:33r5OSRm=gidrgnmJUqLe=tsb...@4ax.com...
[snip]

> While we're on the subject, I recall from Perl's clipboard module a
> function which waits for the clipboard to change. The Python module
> doesn't have this, and I'm not sure how I would implement it. (It's

The architecture you sketch below is just fine, I think, if
your problem-statement is complete and accurate.

> not the sort of thing which is needed very often, I guess, but I have
> a specific application which would be cleaner with it...) I think I

What doesn't happen often at all is to want to wait indefinitely
and for just one event. Normally, one wants some timeout, and
multiple events can be of interest to terminate the wait. The
former, at least, is easy to obtain; the latter is subtler to get
in full generality, IMHO, exactly because the variations are so
many (and include both WM_whatevers you could receive, and
kernel-objects of various kinds becoming signaled -- possibly
COM events you may be monitoring, too).

Turning all kinds of 'things I may be waiting for' into one kind
is probably the cleanest general solution (and using a further,
_artificial_ 'kind' is a choice that has its advantages -- 'event
objects' can carry all sort of type-information and rich data --
particularly when much of the application can be written in
scripting languages; it's the choice we've recently taken in
reorganizing our own big app) -- but we're talking about a
few thousand lines of pretty subtle code, and it needs full
control of the application's event-loop.

So, anyway, back to the simple case...:

> need a clipboard viewer, with a hidden window to receive notification
> messages. But it sounds quite messy to implement. Two questions -

Not really. It _is_ quite a lot of code, actually.

> first, are all the pieces available in win32all to implement this

Yes. But generating a window and hooking its messages may
need a bit more 'buy-in' into Pythonwin than you might like,
although it's surely doable.

> myself, and second, would it be worth adding a method like this to
> win32clipboard?

Depends on the generality one can reach without excessive
effort, I guess. Here, as an alternative, is a stand-alone VC++
implementation which I hacked together by modifying a
component I had once written to turn clipboard changes into
COM events (removing the COM stuff, adding the Python
interface, and a simple event-loop with timeout...). You can
save it to clipwait.cpp, and build and install clipwait.pyd with
the following setup.py:

-- start setup.py
from distutils.core import setup, Extension

setup (name = "clipwait",
version = "1.0",
author = "Alex Martelli",
author_email = "ale...@yahoo.com",
description = "Wait for clipboard change (Win32 only)",

ext_modules = [
Extension('clipwait',
sources = ['clipwait.cpp',
],
)
]
)
-- finis setup.py

-- start clipwait.cpp
// clipwait -- wait for a clipboard change (Win32 only)
// version 1.0, A. Martelli 2000/10/28, ale...@yahoo.com
#include <Python.h>

#include <atlbase.h>
CComModule _Module;
#include <atlwin.h>

// global variables
HINSTANCE hinst;
HWND hPrev;
int nCbcs;

// DLL startup
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID)
{
if(fdwReason==DLL_PROCESS_ATTACH) hinst=hinstDLL;
return TRUE;
}

// ATL-derived window implementation class for clipboard-watching
class Csiwin: public CWindowImpl<Csiwin>
{
BEGIN_MSG_MAP(Csiwin)
MESSAGE_HANDLER(WM_DRAWCLIPBOARD, OnDrawCb)
MESSAGE_HANDLER(WM_CHANGECBCHAIN, OnCcbc)
END_MSG_MAP()

LRESULT OnDrawCb(UINT msg, WPARAM wp, LPARAM lp, BOOL&)
{
++nCbcs;
if(hPrev) return SendMessage(hPrev, msg, wp, lp);
else return 0;
}

LRESULT OnCcbc(UINT msg, WPARAM wp, LPARAM lp, BOOL&)
{
if(hPrev == HWND(wp)) hPrev = HWND(lp);
else if(hPrev) return SendMessage(hPrev, msg, wp, lp);
else return 0;
}

};

// Python entry point
static PyObject*
clipwait(PyObject* self, PyObject* args)
{
int timeout_msec = 5000;
int nChanges = 2;

if(PyArg_ParseTuple(args, "|ii", &timeout_msec, &nChanges)) {
// setup the clipboard-viewer object, &c
_Module.Init(0,hinst);
Csiwin siwin;
siwin.Create(0, CWindow::rcDefault, "", WS_OVERLAPPEDWINDOW);
UINT timid = siwin.SetTimer(-1, 20);
nCbcs=0;
hPrev=siwin.SetClipboardViewer();
DWORD endtime = timeout_msec + GetTickCount();

// message-loop, with timeout & conditional exit
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage( &msg );
DispatchMessage( &msg );
if(nCbcs>=nChanges ||
(timeout_msec && GetTickCount()>=endtime))
break;
}

// remove stuff & terminate
siwin.KillTimer(timid);
siwin.ChangeClipboardChain(hPrev);
siwin.DestroyWindow();
_Module.Term();
return Py_BuildValue("i", nCbcs);
} else {
return 0;
}
}

// Module functions
static PyMethodDef methods[] = {
{"wait", clipwait, METH_VARARGS},
{NULL, NULL}
};

// Module init function
extern "C"
void initclipwait()
{
Py_InitModule("clipwait", methods);
}
-- finis clipwait.cpp

After an "import clipwait", the function clipwait.wait becomes
available; it takes 2 optional arguments -- a timeout value in
milliseconds (default 5000; pass 0 for "no timeout", i.e.,
infinite wait) and a number-of-clipboard-changes-to-wait-for
(default 2, as a first 'clipboard-change' event is normally
generated at once when the clipboard viewer is installed --
unless, I believe, the clipboard is originally empty -- maybe
I should check for that, now that I think of it... oh well,
that's for forthcoming version 1.1, I guess!-).

It returns the number of clipboard changes it has observed
(it will be the requested number, unless the timeout has
intervened). Note the timeout is approximate, not precise
(at least 20 milliseconds' approximation, or worse).


Alex

Paul Moore

unread,
Oct 29, 2000, 4:26:23 PM10/29/00
to
On Sun, 29 Oct 2000 00:40:49 +0200, "Alex Martelli"
<ale...@yahoo.com> wrote:

>Depends on the generality one can reach without excessive
>effort, I guess. Here, as an alternative, is a stand-alone VC++
>implementation which I hacked together by modifying a
>component I had once written to turn clipboard changes into
>COM events (removing the COM stuff, adding the Python
>interface, and a simple event-loop with timeout...). You can
>save it to clipwait.cpp, and build and install clipwait.pyd with
>the following setup.py:

Wow! Thank you for this... I will look at it in detail later (no time
right now), but it seems like exactly the sort of thing I was trying
to do. I could probably have coded the Windows bits myself, but seeing
it in a proper Python format, with a setup.py as well, is very useful.

Paul Moore

Alex Martelli

unread,
Oct 29, 2000, 5:43:33 PM10/29/00
to
"Paul Moore" <gus...@morpheus.demon.co.uk> wrote in message
news:uhlovsojacb0vocah...@4ax.com...
[snip]

> >interface, and a simple event-loop with timeout...). You can
> >save it to clipwait.cpp, and build and install clipwait.pyd with
> >the following setup.py:
>
> Wow! Thank you for this... I will look at it in detail later (no time

You're welcome!

> right now), but it seems like exactly the sort of thing I was trying
> to do. I could probably have coded the Windows bits myself, but seeing

Yes, there's nothing very special here in Win32 API terms!

> it in a proper Python format, with a setup.py as well, is very useful.

Now, doing it in Python itself is not a bad idea, actually. I suspect
one could do it all with calldll, and with win32all it should be far
easier. Of course, if one's application does not otherwise need any
Win32 access, clipwait.pyd has the advantage of taking up only
20k byte -- this may make one's app smaller when packed, and
hence easier to redistribute, than including all of win32all. But
calldll.pyd is only 16k...:-)


Alex

Neil Hodgson

unread,
Oct 29, 2000, 9:14:30 PM10/29/00
to
Alex Martelli:

> Now, doing it in Python itself is not a bad idea, actually. I suspect
> one could do it all with calldll, and with win32all it should be far
> easier.

ActiveState should include calldll or equivalent functionality in their
distribution of Python. It does create a safety hole but it is /so/ useful.

Neil


Alex Martelli

unread,
Oct 30, 2000, 5:54:08 AM10/30/00
to
"Alex Martelli" <ale...@yahoo.com> wrote in message
news:8ti9c...@news1.newsguy.com...
[snip]

> Now, doing it in Python itself is not a bad idea, actually. I suspect
> one could do it all with calldll, and with win32all it should be far
> easier. Of course, if one's application does not otherwise need any
[snip]

Well, here's an attempt at pretty-literal translation (from C++/ATL
to Python/win32all), but, there are some peculiarities...:


import win32ui, win32clipboard as clip, win32con, win32api, win32gui

hPrev = 0
nCbcs = 0

def onDrawCb(*a):
global nCbcs
nCbcs += 1
# print "Draw %d" % nCbcs
if hPrev: return win32api.SendMessage(hPrev,*a[-1][1:4])
else: return 1

def onCcbc(*a):
global hPrev
if hPrev==a[-1][2]: hPrev=a[-1][3]
elif hPrev: return win32api.SendMessage(hPrev,*a[-1][1:4])
else: return 0

def wait(timeout_msec=5000, need_changes=1):
global hPrev, nCbcs
win=win32ui.CreateFrame()
win.CreateWindow(None,'',win32con.WS_OVERLAPPEDWINDOW)
win.HookMessage(onDrawCb,win32con.WM_DRAWCLIPBOARD)
win.HookMessage(onCcbc,win32con.WM_CHANGECBCHAIN)
try:
hPrev=clip.SetClipboardViewer(win.GetSafeHwnd())
except win32api.error, err:
if err.args[0]: raise
nCbcs=0
if timeout_msec:
timid = win.SetTimer(1,20)
curtime = win32api.GetTickCount()
endtime = timeout_msec + curtime

while nCbcs<need_changes:
win32gui.PumpWaitingMessages()
if timeout_msec and win32api.GetTickCount()>endtime: break

if timeout_msec:
win.KillTimer(timid)
clip.ChangeClipboardChain(win.GetSafeHwnd(), hPrev)
win.DestroyWindow()
return nCbcs


Decommenting the print statement within onDrawCb shows a
peculiar "stutter" -- it seems to be called _twice_ for
each clipboard change (plus, twice at the start if the
clipboard is non-empty at entry -- which is why I zero
out the nCbcs counter _after_ the SetClipboardViewer
that causes the first 2 calls). I don't know why that
is -- no equivalent in the C++ version, no mention in
the docs either (that I can see).

The try/except around SetClipboardViewer is another
peculiarity, but a documented one: win32clipboard raises
a win32api.error if there was no previously set
clipboard-viewer (...?), so I must test for a 0
error-code in order to propagate only 'true' errors.

The *a arguments to the message-hook functions are
due to a documentation error (I think!): under the
docs for HookMessage, it's said that the handling
callable will be called with TWO arguments (a "handler
object", then a tuple of message-arguments). In fact,
it seems only one object (the tuple) is being passed.

So, I'm taking whatever arguments are being passed,
and hoping the tuple-of-args is the last (or only)
one -- just in case the docs are in fact right and
it's a bug in the current win32all build (for sure,
either the docs or the win32all code will have to
be changed, but I don't know which, because I do not
understand what that 'handler object' is supposed to
be).

All in all, then, we're talking about 40+ lines of
code for Python (using win32all), versus 95 for C++
(using ATL) -- the latter includes a bit more
comments and some amount of Python-interfacing
code that the Python version doesn't need, though:-).

Looking specifically at the heart of the wait
function, it's 25 lines for C++ and 23 for Python --
basically just the difference in comments and
whitespace. Makes sense, since we're programming
at just about the same level in either language.


I think this argues in favour of doing the
non-portable-anyway system-level programming
in the system-level language (C++), as the VHLL
buys you nothing much here, but, as this surely
isn't performance-critical code, I guess somebody
else might argue "might as well do it in Python"
instead:-). However, the win32all/Pythonwin
framework appears to be somewhat less 'stable'
(considering documentation issues too, as well
as that mysterious 'stutter') than the ATL fw
I'm using in C++ -- particularly when coding at
system level, I'd rather have a clue about what
IS going on rather than take a purely empirical
"tweak until it seems to work" approach:-).


Alex

Reply all
Reply to author
Forward
0 new messages