Use a native window to catch shutdown events.

This commit is contained in:
akwizgran
2011-11-19 15:45:23 +01:00
parent 046becd388
commit 3dc8a590c7
3 changed files with 116 additions and 60 deletions

View File

@@ -1,21 +1,27 @@
package net.sf.briar.lifecycle;
import java.awt.HeadlessException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import net.sf.briar.util.OsUtils;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef.HINSTANCE;
import com.sun.jna.platform.win32.WinDef.HMENU;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.LPARAM;
import com.sun.jna.platform.win32.WinDef.LRESULT;
import com.sun.jna.platform.win32.WinDef.WPARAM;
import com.sun.jna.platform.win32.WinUser.MSG;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.StdCallLibrary.StdCallCallback;
import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;
class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
@@ -25,9 +31,19 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
private static final int WM_QUERYENDSESSION = 17;
private static final int WM_ENDSESSION = 22;
private static final int GWL_WNDPROC = -4;
private static final int WS_MINIMIZE = 0x20000000;
private final Map<String, Object> options;
private boolean initialised = false; // Locking: this
WindowsShutdownManagerImpl() {
options = new TreeMap<String, Object>();
options.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
options.put(Library.OPTION_FUNCTION_MAPPER,
W32APIFunctionMapper.UNICODE);
}
@Override
public synchronized int addShutdownHook(Runnable runnable) {
if(!initialised) initialise();
@@ -37,22 +53,7 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
// Locking: this
private void initialise() {
if(OsUtils.isWindows()) {
try {
HWND hwnd = new HWND();
hwnd.setPointer(Native.getComponentPointer(new JFrame()));
User32 u = (User32) Native.loadLibrary("user32", User32.class);
try {
// Load the 64-bit functions
setCallback64Bit(u, hwnd);
} catch(UnsatisfiedLinkError e) {
// Load the 32-bit functions
setCallback32Bit(u, hwnd);
}
} catch(HeadlessException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
} catch(UnsatisfiedLinkError e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
new EventLoop().start();
} else {
if(LOG.isLoggable(Level.WARNING))
LOG.warning("Windows shutdown manager used on non-Windows OS");
@@ -60,38 +61,6 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
initialised = true;
}
private void setCallback64Bit(final User32 user32, HWND hwnd) {
final WindowProc oldProc = user32.GetWindowLongPtrW(hwnd, GWL_WNDPROC);
WindowProc newProc = new WindowProc() {
public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp) {
if(msg == WM_QUERYENDSESSION) {
// It's safe to delay returning from this message
runShutdownHooks();
} else if(msg == WM_ENDSESSION) {
// Return immediately or the JVM crashes on return
}
return user32.CallWindowProcPtrW(oldProc, hwnd, msg, wp, lp);
}
};
user32.SetWindowLongPtrW(hwnd, GWL_WNDPROC, newProc);
}
private void setCallback32Bit(final User32 user32, HWND hwnd) {
final WindowProc oldProc = user32.GetWindowLongW(hwnd, GWL_WNDPROC);
WindowProc newProc = new WindowProc() {
public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp) {
if(msg == WM_QUERYENDSESSION) {
// It's safe to delay returning from this message
runShutdownHooks();
} else if(msg == WM_ENDSESSION) {
// Return immediately or the JVM crashes on return
}
return user32.CallWindowProcW(oldProc, hwnd, msg, wp, lp);
}
};
user32.SetWindowLongW(hwnd, GWL_WNDPROC, newProc);
}
// Package access for testing
synchronized void runShutdownHooks() {
// Start each hook in its own thread
@@ -106,6 +75,49 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
}
}
private class EventLoop extends Thread {
@Override
public void run() {
try {
final User32 user32 = (User32) Native.loadLibrary("user32",
User32.class, options);
WindowProc proc = new WindowProc() {
public LRESULT callback(HWND hwnd, int msg, WPARAM wp,
LPARAM lp) {
if(msg == WM_QUERYENDSESSION) {
// It's safe to delay returning from this message
runShutdownHooks();
} else if(msg == WM_ENDSESSION) {
// Return immediately or the JVM crashes on return
}
return user32.DefWindowProc(hwnd, msg, wp, lp);
}
};
HWND hwnd = user32.CreateWindowEx(0, "STATIC", "", WS_MINIMIZE,
0, 0, 0, 0, null, null, null, null);
try {
user32.SetWindowLongPtr(hwnd, GWL_WNDPROC, proc);
if(LOG.isLoggable(Level.INFO))
LOG.info("Registered 64-bit callback");
} catch(UnsatisfiedLinkError e) {
user32.SetWindowLong(hwnd, GWL_WNDPROC, proc);
if(LOG.isLoggable(Level.INFO))
LOG.info("Registered 32-bit callback");
}
MSG msg = new MSG();
while(user32.GetMessage(msg, null, 0, 0) > 0) {
user32.TranslateMessage(msg);
user32.DispatchMessage(msg);
}
} catch(UnsatisfiedLinkError e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private static class RunOnce implements Runnable {
private final Runnable runnable;
@@ -123,16 +135,17 @@ class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
private static interface User32 extends StdCallLibrary {
LRESULT CallWindowProcW(WindowProc oldProc, HWND hwnd, int msg,
WPARAM wp, LPARAM lp);
LRESULT CallWindowProcPtrW(WindowProc oldProc, HWND hwnd, int msg,
WPARAM wp, LPARAM lp);
HWND CreateWindowEx(int styleEx, String className, String windowName,
int style, int x, int y, int width, int height, HWND parent,
HMENU menu, HINSTANCE instance, Pointer param);
WindowProc GetWindowLongW(HWND hwnd, int index);
WindowProc GetWindowLongPtrW(HWND hwnd, int index);
LRESULT DefWindowProc(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
LRESULT SetWindowLong(HWND hwnd, int index, WindowProc newProc);
LRESULT SetWindowLongPtr(HWND hwnd, int index, WindowProc newProc);
LRESULT SetWindowLongW(HWND hwnd, int index, WindowProc newProc);
LRESULT SetWindowLongPtrW(HWND hwnd, int index, WindowProc newProc);
int GetMessage(MSG msg, HWND hwnd, int filterMin, int filterMax);
boolean TranslateMessage(MSG msg);
LRESULT DispatchMessage(MSG msg);
}
private static interface WindowProc extends StdCallCallback {

View File

@@ -12,7 +12,7 @@ public class ShutdownManagerImplTest extends TestCase {
@Test
public void testAddAndRemove() {
ShutdownManager s = new ShutdownManagerImpl();
ShutdownManager s = createShutdownManager();
Set<Integer> handles = new HashSet<Integer>();
for(int i = 0; i < 100; i++) {
int handle = s.addShutdownHook(new Runnable() {
@@ -26,4 +26,8 @@ public class ShutdownManagerImplTest extends TestCase {
// The hooks should no longer be removable
for(int handle : handles) assertFalse(s.removeShutdownHook(handle));
}
protected ShutdownManager createShutdownManager() {
return new ShutdownManagerImpl();
}
}

View File

@@ -0,0 +1,39 @@
package net.sf.briar.lifecycle;
import net.sf.briar.api.lifecycle.ShutdownManager;
import org.junit.Test;
public class WindowsShutdownManagerImplTest extends ShutdownManagerImplTest {
@Override
protected ShutdownManager createShutdownManager() {
return new WindowsShutdownManagerImpl();
}
@Test
public void testManagerWaitsForHooksToRun() {
WindowsShutdownManagerImpl s = new WindowsShutdownManagerImpl();
SlowHook[] hooks = new SlowHook[10];
for(int i = 0; i < hooks.length; i++) {
hooks[i] = new SlowHook();
s.addShutdownHook(hooks[i]);
}
s.runShutdownHooks();
for(int i = 0; i < hooks.length; i++) assertTrue(hooks[i].finished);
}
private static class SlowHook implements Runnable {
private volatile boolean finished = false;
public void run() {
try {
Thread.sleep(100);
finished = true;
} catch(InterruptedException e) {
// Don't finish
}
}
}
}