package com.python.pydev.debug;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IDebugEventSetListener;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TaskBar;
import org.eclipse.swt.widgets.TaskItem;
import org.eclipse.ui.IStartup;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.model.PyThread;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.utils.PlatformUtils;
import org.python.pydev.shared_ui.utils.RunInUiThread;
import org.python.pydev.shared_ui.utils.UIUtils;
import com.python.pydev.debug.remote.IRemoteDebuggerListener;
import com.python.pydev.debug.remote.RemoteDebuggerServer;
import com.python.pydev.debug.remote.client_api.PydevRemoteDebuggerServer;
public class DebugEarlyStartup implements IStartup {
private final Job checkAlwaysOnJob = new Job("Check debug server always on") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
checkAlwaysOn(PydevPlugin.getDefault().getPreferenceStore());
} catch (NullPointerException e) {
// Ignore: it can happen during interpreter shutdown.
// java.lang.NullPointerException
// at org.eclipse.ui.preferences.ScopedPreferenceStore.internalGet(ScopedPreferenceStore.java:489)
// at org.eclipse.ui.preferences.ScopedPreferenceStore.getInt(ScopedPreferenceStore.java:518)
// at com.python.pydev.debug.DebugEarlyStartup.checkAlwaysOn(DebugEarlyStartup.java:241)
// at com.python.pydev.debug.DebugEarlyStartup$1.run(DebugEarlyStartup.java:37)
// at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54)
}
return Status.OK_STATUS;
}
};
@Override
public void earlyStartup() {
//Note: preferences are in the PydevPlugin, not in the debug plugin.
IPreferenceStore preferenceStore = PydevPlugin.getDefault().getPreferenceStore();
preferenceStore.addPropertyChangeListener(new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (DebugPluginPrefsInitializer.DEBUG_SERVER_STARTUP.equals(event.getProperty())) {
//On a change in the preferences, re-check if it should be always on...
checkAlwaysOnJob.schedule(200);
}
}
});
RemoteDebuggerServer.getInstance().addListener(new IRemoteDebuggerListener() {
@Override
public void stopped(RemoteDebuggerServer remoteDebuggerServer) {
//When it stops, re-check if it should be always on.
checkAlwaysOnJob.schedule(200);
}
});
checkAlwaysOnJob.schedule(500); //wait a little bit more to enable on startup.
DebugPlugin.getDefault().addDebugEventListener(new IDebugEventSetListener() {
@Override
public void handleDebugEvents(DebugEvent[] events) {
if (events != null) {
for (DebugEvent debugEvent : events) {
if (debugEvent.getKind() == DebugEvent.SUSPEND) {
if (debugEvent.getDetail() == DebugEvent.BREAKPOINT) {
if (debugEvent.getSource() instanceof PyThread) {
IPreferenceStore preferenceStore2 = PydevPlugin.getDefault().getPreferenceStore();
final int forceOption = preferenceStore2
.getInt(DebugPluginPrefsInitializer.FORCE_SHOW_SHELL_ON_BREAKPOINT);
if (forceOption != DebugPluginPrefsInitializer.FORCE_SHOW_SHELL_ON_BREAKPOINT_MAKE_NOTHING) {
Runnable r = new Runnable() {
@Override
public void run() {
Shell activeShell = UIUtils.getActiveShell();
if (activeShell != null) {
forceActive(activeShell, forceOption);
}
}
};
boolean runNowIfInUiThread = true;
RunInUiThread.async(r, runNowIfInUiThread);
}
}
}
}
}
}
}
});
}
/**
* There are some issues with just forceActive as it doesn't actually bring it to the front on windows on some situations.
*
* - https://bugs.eclipse.org/bugs/show_bug.cgi?id=192036: outlines the win32 solution implemented in here (using reflection to avoid issues compiling on other platforms).
*
* Some possible alternatives:
* - we could change the text/icon in the taskbar (http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet336.java)
* - Creating our own windows-dependent dll (but this is probably too much for the build process too) http://stackoverflow.com/questions/2773364/make-jface-window-blink-in-taskbar-or-get-users-attention
* - https://github.com/jnr/jnr-ffi using the approach commented on http://stackoverflow.com/questions/2315560/how-do-you-force-a-java-swt-program-to-move-itself-to-the-foreground seems a possible acceptable workaround
*/
public void forceActive(final Shell shell, int forceOption) {
//First, make sure it's not minimized
shell.setMinimized(false);
if (forceOption == DebugPluginPrefsInitializer.FORCE_SHOW_SHELL_ON_BREAKPOINT_MAKE_ACTIVE) {
if (PlatformUtils.isWindowsPlatform()) {
try {
Class<?> OSClass = Class.forName("org.eclipse.swt.internal.win32.OS");
Method hFromMethod = OSClass.getMethod("GetForegroundWindow");
Method SetForegroundWindowMethod = OSClass.getMethod("SetForegroundWindow", int.class);
Method GetWindowThreadProcessIdMethod = OSClass.getMethod("GetWindowThreadProcessId", int.class,
int[].class);
int hFrom = (int) hFromMethod.invoke(OSClass);
//int hFrom = OS.GetForegroundWindow();
// on Mac it's not available, so, use reflection to compile on it.
Field handleField = shell.getClass().getDeclaredField("handle");
int shellHandle = (Integer) handleField.get(shell);
if (hFrom <= 0) {
//OS.SetForegroundWindow(shell.handle);
SetForegroundWindowMethod.invoke(OSClass, shellHandle);
return;
}
if (shellHandle == hFrom) {
return;
}
//int pid = OS.GetWindowThreadProcessId(hFrom, null);
int pid = (int) GetWindowThreadProcessIdMethod.invoke(OSClass, hFrom, null);
//int _threadid = OS.GetWindowThreadProcessId(shell.handle, null);
int _threadid = (int) GetWindowThreadProcessIdMethod.invoke(OSClass, shellHandle, null);
if (_threadid == pid) {
//OS.SetForegroundWindow(shell.handle);
SetForegroundWindowMethod.invoke(OSClass, shellHandle);
return;
}
if (pid > 0) {
Method AttachThreadInputMethod = OSClass.getMethod("AttachThreadInput", int.class, int.class,
boolean.class);
//if (!OS.AttachThreadInput(_threadid, pid, true)) {
if (!((boolean) AttachThreadInputMethod.invoke(OSClass, _threadid, pid, true))) {
return;
}
//OS.SetForegroundWindow(shell.handle);
SetForegroundWindowMethod.invoke(OSClass, shellHandle);
//OS.AttachThreadInput(_threadid, pid, false);
AttachThreadInputMethod.invoke(OSClass, _threadid, pid, false);
}
//OS.BringWindowToTop(shell.handle);
//OS.UpdateWindow(shell.handle);
//OS.SetActiveWindow(shell.handle);
for (String s : new String[] { "BringWindowToTop", "UpdateWindow", "SetActiveWindow" }) {
Method method = OSClass.getMethod(s, int.class);
method.invoke(OSClass, shellHandle);
}
return; //ok, workaround on win32 worked.
} catch (Throwable e) {
// Log and go the usual platform-independent route...
Log.log(e);
}
//As specified from http://www.eclipsezone.com/eclipse/forums/t28413.html:
shell.forceActive();
shell.setActive();
}
}
if (forceOption == DebugPluginPrefsInitializer.FORCE_SHOW_SHELL_ON_BREAKPOINT_SHOW_INDETERMINATE_PROGRESS) {
final TaskBar taskBar = shell.getDisplay().getSystemTaskBar();
if (taskBar != null) {
TaskItem item = taskBar.getItem(shell);
if (item == null) {
item = taskBar.getItem(null);
}
RunInUiThread.async(new ShowIndeterminateProgressRunnable(shell, item,
System.currentTimeMillis() + 5000));
}
}
}
private static class ShowIndeterminateProgressRunnable implements Runnable {
private long blinkUntil;
private TaskItem item;
public ShowIndeterminateProgressRunnable(Shell shell, TaskItem item, long blinkUntil) {
this.blinkUntil = blinkUntil;
this.item = item;
}
@Override
public void run() {
if (System.currentTimeMillis() < this.blinkUntil) {
item.setProgressState(SWT.INDETERMINATE);
new Thread() {
@Override
public void run() {
synchronized (this) {
try {
this.wait(300);
} catch (InterruptedException e) {
Log.log(e);
}
}
RunInUiThread.async(ShowIndeterminateProgressRunnable.this);
}
}.start();
} else {
//Last one should always restore!
item.setProgressState(SWT.DEFAULT);
}
}
}
private static volatile boolean checkedOnOnce = false;
public void checkAlwaysOn(final IPreferenceStore preferenceStore) {
int debugServerStartup = preferenceStore.getInt(DebugPluginPrefsInitializer.DEBUG_SERVER_STARTUP);
if (debugServerStartup != DebugPluginPrefsInitializer.DEBUG_SERVER_MANUAL) {
boolean runNowIfInUiThread = true;
Runnable r = new Runnable() {
@Override
public void run() {
//Check if it didn't change in the meanwhile...
int debugServerStartup = preferenceStore.getInt(DebugPluginPrefsInitializer.DEBUG_SERVER_STARTUP);
if (debugServerStartup == DebugPluginPrefsInitializer.DEBUG_SERVER_KEEY_ALWAYS_ON
&& !PydevRemoteDebuggerServer.isRunning()) {
PydevRemoteDebuggerServer.startServer();
} else if (debugServerStartup == DebugPluginPrefsInitializer.DEBUG_SERVER_ON_WHEN_PLUGIN_STARTED) {
if (!checkedOnOnce && !PydevRemoteDebuggerServer.isRunning()) {
//Note: if the preference was manual and the user just changed to this setting, this
//will turn it on as that'll be the first time it's checked.
//Is this a bug or feature? -- I think it's a feature :)
PydevRemoteDebuggerServer.startServer();
}
}
checkedOnOnce = true;
}
};
RunInUiThread.async(r, runNowIfInUiThread);
}
}
}