Showing posts with label listener. Show all posts
Showing posts with label listener. Show all posts

Tuesday, February 9, 2010

OSGi Clipboard Monitor for Java (on Windows using JNA)

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 AtomicReference ref = 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?

Tuesday, January 12, 2010

Notification Listeners

To let other components in the system know about displayed notifications we use the White-Board-Pattern to consume listener services in our NotificationServiceImpl class. A listener for notifications has a very easy interface:
public interface NotificationListener extends EventListener {
  void onEvent(NotificationEvent event);
}
It extends the default Java (empty) EventListener base interface and contains a single method. This single method and its associated event object allows us to extend the kind of events we can publish without changing the interface for each new event like "Show" or "Hide". For now we will only publish "Show" events. But if in the future we decide to also notify listeners about the close/hide of a notification popup we would have to add another method to the interface "onHide" and breaking the API somehow. Using an event object we can put the type of the event inside the event object and the client would then have to decide what to do for each event type. It would still only implement a single method "onEvent" instead of having to implement a new method for each new event type such as "onShow" or "onHide". The event object itself looks like this:
public class NotificationEvent extends EventObject {
private static final long serialVersionUID = -8633958140848611658L;

private Notification notification;

private NotificationEventType type;

public NotificationEvent(NotificationService source, Notification notification, NotificationEventType type) {
  super(source);
  this.notification = notification;
  this.type = type;
}

public Notification getNotification() {
  return notification;
}

public NotificationEventType getType() {
  return type;
}
}
The event type is an enum:
public enum NotificationEventType {
Show
}
The NotificationServiceImpl is only slightly changed:
Object listeners[] = context.locateServices("NotificationListener"); //$NON-NLS-1$
if (listeners != null) {
 NotificationEvent event = new NotificationEvent(this, notification, NotificationEventType.Show);
 for (Object listener : listeners) {
   try {
     ((NotificationListener)listener).onEvent(event);
   } catch (Throwable e) {          
   }
 }
}
Right before we display the notification we get all the NotificationListener registered in the OSGi system and (safely) call their onEvent method. There are several ways to get the list of currently registered NotificationListener services. One way is to use the BundleContext.getServiceReferences(String, String) method. But this would involve getting the service object ourself and not forget to "unget" it also. Another way is to use a ServiceTracker. But since our implementation already is a Component with bindings we can just add a new binding to our service component definition XML:
<reference cardinality="0..n" interface="ui.notification.NotificationListener" name="NotificationListener" policy="dynamic"/>
We then use the ComponentContext object to get an array of services that could be bound to "NotificationListener" (as named in the component XML). This context we get from OSGi in a new protected method:
protected void activate(ComponentContext context) {
 this.context = context;
}
That's all for notifying listeners about notification display. We can now create a simple new bundle that will register a NotificationListener into the system and simply System.out.println the notifications data.
public class SysoutNotificationListener implements NotificationListener {

  public void onEvent(NotificationEvent event) {
    if (event.getType() == NotificationEventType.Show) {
      System.out.println(event.getNotification().getTitle() + ": " //$NON-NLS-1$
          + event.getNotification().getMessage());
    }
  }
}
Its component definition would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ui.notification.listener.sysout">
 <implementation class="ui.notification.listener.sysout.internal.SysoutNotificationListener"/>
 <service>
    <provide interface="ui.notification.NotificationListener"/>
 </service>
</scr:component>
Pretty simple. Here is what the result looks like: Listeners could also be used to log a history of notifications in various ways. In the next chapter of this series we will develop a View for Eclipse RCP apps that logs a history of notifications in a table.