[Prev] Thread [Next]  |  [Prev] Date [Next]

MacOS TransferListener problem - a patch/fix Kustaa Nyholm Wed Dec 06 06:08:04 2006

Some weeks ago there was discussion on this list about clipboard problems on Mac OS. One of the issues was that FlavorListener did not get called properly.

This indeed seems to be the case. For my own application framework I've implemented a fix while I wait Apple to get their act together (it is still broken on versions I cannot talk about).

I'm sharing it here in case someone finds it useful.

The main idea is that instead of directly calling Toolkit.getSystemClipboard() I
call my MacOSClipboard.getClipboard() which instantiates a 'proxy' clipboard (if it has not already been instantiated) that sits between my application code
and the system clipboard. This 'proxy' clipboard implements calling the
flavor listeners as appropriate. To make it work it needs some help. I call
the MacOSClipboard.fireFlavorsChanged() whenever one of my windows becomes
active because in most real life cases no new data can be placed on the clipboard unless my windows first become de-active and I won't be using it before one of my windows becomes active. In this way I get notified if some other application places data on the clipboard. Of course I my application
puts data on the clipboard this is directly captured (within the fix) and
reported to FlavorListeners as it appropriate. Thats all that I need to make it
work for me.

An alternative is to use a timer daemon to periodically check the clipboard. To start it call startClipboardDaemon().

Notice that most methods in my code have package visibility because I call this from within my framework code and I do not want application code directly to
call this fix-code.

Of course I only instantiate fix inside a public method within the framework
and only if running on Mac OS.

        public Clipboard createClipboard() {
                if (Application.inMacOS())
                        return MacOSClipboard.getClipboard();
                        return Toolkit.getDefaultToolkit().getSystemClipboard();


        static public boolean inMacOS() {
                String lcOSName = System.getProperty("os.name").toLowerCase();
                return lcOSName.indexOf("mac os") >= 0;


Once Apple fixes their stuff I'll just quietly remove this fix.

In this way I can mostly code as if the problem wasn't there and the fix won't
get spread allover the code.

Everyone is of course welcome to change the code as they please.

While on the subject, someone was wondering weather to use a local or system clipboard. My take on this is always to use system clipboard as overhead implementation wise is minimal and one never knows if the user is running two instances of my application so a local clipboard wont cut it across the JVM boundary.

Hope someone finds this usefull.

br Kusti

MacOSClipboard class source:
package jApp;

import java.awt.Toolkit;
import java.awt.datatransfer.*;
import java.util.Timer;
import java.util.TimerTask;
import java.io.IOException;

/*package*/class MacOSClipboard extends Clipboard {
        static private Clipboard m_SystemClipboard;

        static private MacOSClipboard m_MacOSClipboard;

        private static DataFlavor[] m_Flavors;

        static public Clipboard getClipboard() {
                if (m_MacOSClipboard == null)
                        m_MacOSClipboard = new MacOSClipboard();
                return m_MacOSClipboard;

        private MacOSClipboard() {
                m_SystemClipboard = 
                m_MacOSClipboard = this;


        public synchronized void addFlavorListener(FlavorListener listener) {
                // TODO Auto-generated method stub

        public DataFlavor[] getAvailableDataFlavors() {
                // TODO Auto-generated method stub
                return m_SystemClipboard.getAvailableDataFlavors();

        public synchronized Transferable getContents(Object requestor) {
                // TODO Auto-generated method stub
                return m_SystemClipboard.getContents(requestor);

public Object getData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
                return m_SystemClipboard.getData(flavor);

        public boolean isDataFlavorAvailable(DataFlavor flavor) {
                return m_SystemClipboard.isDataFlavorAvailable(flavor);

        public synchronized void removeFlavorListener(FlavorListener listener) {

        public synchronized FlavorListener[] getFlavorListeners() {
                return m_SystemClipboard.getFlavorListeners();

        public String getName() {
                return m_SystemClipboard.getName();

        public synchronized void setContents(Transferable contents, 
ClipboardOwner owner) {
                final ClipboardOwner realOwner = owner;
                m_SystemClipboard.setContents(contents, new ClipboardOwner() {
                        public void lostOwnership(Clipboard clipboard, 
Transferable contents) {
                                realOwner.lostOwnership(clipboard, contents);

        static/* packate */void fireFlavorsChanged() {
                for (FlavorListener listener : 
                m_Flavors = m_SystemClipboard.getAvailableDataFlavors();

        /* package */boolean isFlavorsChanged() {
                DataFlavor[] current = 
                boolean changed = false;
                if (m_Flavors != null && current.length == m_Flavors.length) {
                        for (int i = 0; i < m_Flavors.length; ++i)
                                if (!current[i].equals(m_Flavors[i]))
                                        return true;
                        m_Flavors = current;
                } else
                        changed = true;
                return true;

        /* package */void startClipboardDaemon(int period) {

                Timer daemon = new Timer(true);
                daemon.schedule(new TimerTask() {
                        public void run() {
Runnable() {
                                        public void run() {
                                                if (isFlavorsChanged())
                }, period, period);

Do not post admin requests to the list. They will be ignored.
Java-dev mailing list      ([EMAIL PROTECTED])
Help/Unsubscribe/Update your Subscription:

This email sent to [EMAIL PROTECTED]