package org.eclipse.jdt.internal.debug.core; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchListener; import org.eclipse.jdt.core.dom.Message; import org.eclipse.jdt.debug.core.IJavaBreakpoint; import org.eclipse.jdt.debug.core.IJavaBreakpointListener; import org.eclipse.jdt.debug.core.IJavaDebugTarget; import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.core.IJavaType; import org.eclipse.jdt.debug.core.JDIDebugModel; import org.eclipse.jdt.internal.debug.core.model.JDIClassType; import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget; import org.eclipse.jdt.internal.debug.core.model.JDIThread; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.ClassType; import com.sun.jdi.Field; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.InvalidTypeException; import com.sun.jdi.InvocationException; import com.sun.jdi.Method; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.Event; /** * On SWT/GTK applications, we need to drop any mouse grabs when a * breakpoint is hit or the mouse is not usable. This class watches launches * and when a SWT/GTK application is being debugged "ungrabs" the mouse * pointer when any breakpoints are hit. This occurs because in X-Windows * the mouse pointer is a global resource that can only be locked by * a single application. */ public class BreakMouseGrab implements IJavaBreakpointListener, ILaunchListener, IJDIEventListener { //Maps JDIDebugTarget(s) to an appropriate OS class (if one exists) private WeakHashMap debugTargetToOS; public BreakMouseGrab () { // If the current system is a Windows system this stuff is totally irrelevant. // Drop out quickly so Windows users don't see any sort of performance hit. if (System.getProperty("os.name").regionMatches(true, 0, "win32", 0, 3)) return; debugTargetToOS = new WeakHashMap (); JDIDebugModel.addJavaBreakpointListener(this); DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); } protected void finalize() throws Throwable { JDIDebugModel.removeJavaBreakpointListener(this); super.finalize(); } /** * Registered to be called whenever a breakpoint is hit in Java code. * If the running DebugTarget has been detected as containing * org.eclipse.swt.internal.gtk.OS, then run gdk_pointer_ungrab inside * the target VM to free the mouse cursor */ public boolean breakpointHit(IJavaThread ijThread, IJavaBreakpoint breakpoint) { JDIThread thread = (JDIThread)ijThread; JDIDebugTarget target = (JDIDebugTarget) thread.getDebugTarget(); ClassType gtkOS = null; if (debugTargetToOS != null) { gtkOS = (ClassType)debugTargetToOS.get(target); } if (gtkOS == null) return true; List methods, arguments; Method gdk_pointer_ungrab = null; Field GDK_CURRENT_TIME = null; methods = gtkOS.methodsByName("gdk_pointer_ungrab"); if (methods != null && methods.size() > 0) { gdk_pointer_ungrab = (Method) methods.get(0); } GDK_CURRENT_TIME = gtkOS.fieldByName("GDK_CURRENT_TIME"); if (GDK_CURRENT_TIME == null || gdk_pointer_ungrab == null) { return true; } // Add GDK_CURRENT_TIME to the arguments list arguments = new ArrayList(1); arguments.add(gtkOS.getValue (GDK_CURRENT_TIME)); try { // Actually run the method in the target VM gtkOS.invokeMethod(thread.getUnderlyingThread(), gdk_pointer_ungrab, arguments, 0); } catch (InvalidTypeException e) { } catch (ClassNotLoadedException e) { } catch (IncompatibleThreadStateException e) { } catch (InvocationException e) { } return true; } /** * Try to retrieve org.eclipse.swt.internal.gtk.OS from @target * * @return mirror of the class in @target if it existed, or null */ protected ClassType getOSClass (JDIDebugTarget target) { IJavaType[] classes; ClassType OS = null; try { classes = target.getJavaTypes("org.eclipse.swt.internal.gtk.OS"); } catch (DebugException e) { return null; } if (classes != null && classes.length > 0) { OS = (ClassType) ((JDIClassType) classes[0]).getUnderlyingType(); } return OS; } /** * Registered to receive notification if/when org.eclipse.swt.internal.gtk.OS * is loaded. If we are notified, add a mapping between the debug target * and the mirror of this class so we don't have to look for it again */ public boolean handleEvent(Event event, JDIDebugTarget target) { if (!(event instanceof ClassPrepareEvent)) return true; ClassPrepareEvent classEvent = (ClassPrepareEvent) event; debugTargetToOS.put(target, classEvent.referenceType()); return true; } /** * Registered to receive notification when a launch "changes", usually meaning * that a new DebugTarget was added. If its a Java debug target, we want to * have handleEvent() called if the SWT/GTK class is ever loaded */ public void launchChanged(ILaunch launch) { if (!(launch.getDebugTarget() instanceof JDIDebugTarget)) { return; } JDIDebugTarget target = (JDIDebugTarget)launch.getDebugTarget(); if (debugTargetToOS.get(target) != null) return; ClassType OS = getOSClass (target); if (OS != null) { // We found a loaded SWT/GTK class, add it to the mapping debugTargetToOS.put(target, OS); } else { // Watch this class to see if the SWT/GTK class ever gets loaded target.addJDIEventListener(this, target.createClassPrepareRequest("org.eclipse.swt.internal.gtk.OS")); } } /** * If the launch being removed was an SWT/GTK application, remove it * from the DebugTarget->OS Class mapping */ public void launchRemoved(ILaunch launch) { if (!(launch.getDebugTarget() instanceof JDIDebugTarget)) { return; } JDIDebugTarget target = (JDIDebugTarget)launch.getDebugTarget(); if (target == null) { return; } // If we allocated resources for this DebugTarget, remove them if (debugTargetToOS.get(target) != null) { debugTargetToOS.remove(target); } } /* stubs for ILaunchListener */ public void launchAdded(ILaunch launch) { } /* stubs for IBreakpointListener */ public void addingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {} public boolean installingBreakpoint(IJavaDebugTarget target, IJavaBreakpoint breakpoint, IJavaType type) { return true;} public void breakpointInstalled(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {} public void breakpointRemoved(IJavaDebugTarget target, IJavaBreakpoint breakpoint) {} public void breakpointHasRuntimeException(IJavaLineBreakpoint breakpoint, DebugException exception) {} public void breakpointHasCompilationErrors(IJavaLineBreakpoint breakpoint, Message[] errors) {} }