Before you read on you should be familiar with JNA and OSGi as I will make heavy use of it in this posting.
Preface
For my CopyTo Eclipse plugin's preference page I wanted to enable the "Paste" button for label/URL combinations only if there is some text in the clipboard (and that text can be converted to a "CopyTo" target). So I would need to monitor the content of the systems clipboard.
SWT to the rescue! ... not!
So I checked if SWT can already help me here. There should be a Clipboard.addListener method that would allow me to register a listener with the clipboard and let SWT notify me about changed clipboard content. Unfortunately there is no such functionality in SWT. And I would soon know why that is ;)
The long way... using a Windows Clipboard "Viewer"
Since I am developing on a Windows machine, I fired up the Windows SDK help to see what it offers in regard to the clipboard. There is a SetClipboardViewer function that in a somewhat awkward way let your window become part of the clipboard viewer chain. Windows will then send 2 specific messages to this windows message proc to inform it about clipboard changes (WM_DRAWCLIPBOARD) or changes in the clipboard viewer chain (WM_CHANGECBCHAIN , if someone else calls SetClipboardViewer).
The first thing that bugged me was, that I do not have a window that I could give to the function and that could receive messages from Windows. Then, I remembered my old Win32 coding days in C++ and that one can create a hidden window just for messages. Instead of creating my own window class for that I decided to use an existing one. The "STATIC" window class does not need a lot of resources and does not receive a lot of messages so its the perfect candidate. We will just create an invisible instance of such window using JNA:
viewer = User32.INSTANCE.CreateWindowEx(0, "STATIC", "", 0, 0, 0, 0, 0, null, 0, 0, null);
Then we register the window with the clipboard viewer chain according to the API specs:
nextViewer = User32.INSTANCE.SetClipboardViewer(viewer);
The next thing to do is to redirect all messages to a custom window proc instead of the default one of the "STATIC" window class. This is called "subclassing" in Windows and in a sense is something like class subclassing in the Java world. Only on a "message only" basis.
Our new window proc will handle the two clipboard related messages and redirect the other messages to the original window proc of the "STATIC" window class.
User32.INSTANCE.SetWindowLong(viewer, User32.GWL_WNDPROC, this.ourProc);
Please note that ourProc is a member field, so that the Java GC will not remove its reference and JNA would no longer be able to send messages to our callback!
Thats how the callback looks like:
class OurProc implements WNDPROC { public int callback(HWND hWnd, int uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case User32.WM_CHANGECBCHAIN: // If the next window is closing, repair the chain. if (nextViewer.toNative().equals(wParam.toNative())) { nextViewer = new HWND(Pointer.createConstant(lParam.longValue())); } // Otherwise, pass the message to the next link. else if (nextViewer != null) { User32.INSTANCE.SendMessage(nextViewer, uMsg, wParam, lParam); } return 0; case User32.WM_DRAWCLIPBOARD: try { onChange(new ClipboardEvent(this)); } finally { User32.INSTANCE.SendMessage(nextViewer, uMsg, wParam, lParam); } return 0; case User32.WM_DESTROY: User32.INSTANCE.ChangeClipboardChain(viewer, nextViewer); break; } return User32.INSTANCE.DefWindowProc(hWnd, uMsg, wParam, lParam); }
That's pretty much the code from the Windows SDK help for programming a Clipboard Viewer. There is some strange chain repairing code included. Speaking of bad API design. How easily could that break, if one programmer gets it wrong?
Polling the message queue without wasting CPU cycles
Now, for the sub-classed static window to receive any messages we need to poll the message queue. In Windows you can only poll your own threads message queue. And, important to know, Windows only creates a message queue if a thread creates a window or calls one of the message queue related functions like GetMessage or PeekMessage. So a simple message poller in our threads run() method would look like this:
while (User32.INSTANCE.GetMessage(msg, null, 0, 0)>0) { User32.INSTANCE.TranslateMessage(msg); User32.INSTANCE.DispatchMessage(msg); }
However, GetMessage is a blocking call and so our Thread would consume all the available CPU cycles it could get until a new message arrives in the queue. That's certainly not what we want. Instead we have to create more sophisticated message queue polling logic. We also want to be able to end our clipboard monitor at any given time. We do that by signaling a special Win32 kernel event object. The MsgWaitForMultipleObjects allows us to wait for message to arrive in the queue as well as events that get signaled. So our new message polling loop that cost (almost) no CPU cycles looks like this:
final HANDLE handles[] = { event }; while (true) { int result = User32.INSTANCE.MsgWaitForMultipleObjects(handles.length, handles, false, Kernel32.INFINITE, User32.QS_ALLINPUT); if (result == Kernel32.WAIT_OBJECT_0) { User32.INSTANCE.DestroyWindow(viewer); return; } if (result != Kernel32.WAIT_OBJECT_0 + handles.length) { // Serious problem, end the thread's run() method! break; } while (User32.INSTANCE.PeekMessage(msg, null, 0, 0, User32.PM_REMOVE)) { User32.INSTANCE.TranslateMessage(msg); User32.INSTANCE.DispatchMessage(msg); } }
Please note that we poll all messages out of the queue until there are no more left and only then return to another round of MsgWaitForMultipleObjects until our halt event is signaled.
That's it about the gory details of implementing such a "viewer" in Windows (without displaying anything). The hard thing to figure out was, that all user level related calls had to be made from the thread that will read out the message queue.
Why Microsoft decided to put the responsibility of a chain into the hands of the programmer is beyond me. Clearly bad API design.
Adding some spin - OSGi services
Of course when programming in Java I will always use OSGi whenever possible. The Clipboard Monitor is no exception. Granted, the whole code can be used without OSGi too. But then you would have to implement a kind of listener management yourself. That's up to the reader of this entry and I welcome everyone to contribute a plain old Java implementation of AbstractWindowsClipboardMonitor that integrates hand crafted listener management instead of using the OSGi service registry.
So there is a monitor component that will consume ClipboardListener services registered in the system and inform them about changes in the clipboard.
Listener that publishes an event using the EventAdmin
Now we can have a fairly simple listener that publishes an event on every clipboard change:
public class EventAdminClipboardListener implements ClipboardListener { private static final String TOPIC = "clipboard/monitor/event"; //$NON-NLS-1$ private final AtomicReferenceref = new AtomicReference (); protected void bind(EventAdmin eventAdmin) { ref.set(eventAdmin); } protected void unbind(EventAdmin eventAdmin) { ref.compareAndSet(eventAdmin, null); } public void onEvent(ClipboardEvent event) { EventAdmin eventAdmin = ref.get(); if (eventAdmin != null) { eventAdmin.postEvent(new Event(TOPIC, (Map) null)); } } }
Pretty simple.
The SWT Clipboard Listener service
Then we have this listener service that will examine the content of the clipboard and use the OSGi EventAdmin to publish specialized events with topics describing the content of the clipboard. EventHandler services can so easily react on specific changes in the clipboard. The following event topics are currently implemented:
- clipboard/monitor/swt/TEXT
- clipboard/monitor/swt/URL
- clipboard/monitor/swt/IMAGE
- clipboard/monitor/swt/RTF
- clipboard/monitor/swt/HTML
- clipboard/monitor/swt/FILE
So you could register an EventHandler that reacts on changes in the clipboard, and only if the new clipboard content contains (also) text. Your EventHandler service would register with the topic set to "clipboard/monitor/swt/TEXT". The beauty of this componentized approach is that you do not even have to know that there is a clipboard monitor installed and running in the system. You have no dependencies on it. You simply register for events of a specific topic and get informed about changes. You can then use SWT to read out the clipboard.
Conclusion
I hope this rather lengthy posting gave you another clue what can be done using clean OSGi component design. And maybe some of you can even put the components to some use in your own projects. It's all licensed under EPL 1.0 so feel free to use the code.
Here you can watch a little video demonstration (if you cannot see the video below).
Code at GitHub
You are invited to check out the full source code over at GitHub and you are invited to fork/clone and contribute, if you want. Maybe someone can add a GTK/Linux clipboard viewer?