com.openfin.desktop.win32.DesktopPortHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openfin-desktop-java-adapter Show documentation
Show all versions of openfin-desktop-java-adapter Show documentation
The Java API for OpenFin Runtime
package com.openfin.desktop.win32;
import com.openfin.desktop.ActionEvent;
import com.openfin.desktop.EventListener;
import com.openfin.desktop.PortDiscoveryHandler;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.*;
import com.sun.jna.win32.StdCallLibrary;
import org.json.JSONObject;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Create native Windows and listens to WM_COPYDATA from Runtime for Runtime port discovery.
*
* @author wche
* @since 12/11/14
*
*/
public class DesktopPortHandler implements WinUser.WindowProc, PortDiscoveryHandler {
private final static Logger logger = LoggerFactory.getLogger(DesktopPortHandler.class.getName());
private static final String windowClassName = "OPENFIN_ADAPTER_WINDOW";
private Map callbacks = Collections.synchronizedMap(new HashMap());
private WinDef.HMODULE hInst;
private static DesktopPortHandler instance; // singleton to process all messages of the Window class
private static int WM_COPYDATA = 74;
private static long MSGFLT_ALLOW = 1;
private DesktopPortHandler() {
this.registerWindowClass();
logger.debug("Created");
}
public static synchronized DesktopPortHandler getInstance() {
if (instance == null) {
instance = new DesktopPortHandler();
}
return instance;
}
public void registerEventListener(EventListener listener, int timeout) {
this.createMesssageThread(listener, timeout);
}
public void removeEventListener(EventListener listener) {
Iterator iterator = this.callbacks.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
WinMessageThread wthread = this.callbacks.get(key);
if (listener == wthread.getEventListener() && wthread.gethWnd() != null) {
logger.debug("destroy message window " + wthread.gethWnd().getPointer().toString());
User32.INSTANCE.PostMessage(wthread.gethWnd(), User32.WM_CLOSE, null, null);
this.getLastError();
wthread.timeoutThread.interrupt();
this.callbacks.remove(key);
break;
}
}
}
@Override
public String getEffectivePipeName() {
return null;
}
private void registerWindowClass() {
logger.debug("Registering window class " + windowClassName);
this.hInst = Kernel32.INSTANCE.GetModuleHandle("");
WinUser.WNDCLASSEX wClass = new WinUser.WNDCLASSEX();
wClass.hInstance = this.hInst;
wClass.lpfnWndProc = this;
wClass.lpszClassName = windowClassName;
// register window class
User32.INSTANCE.RegisterClassEx(wClass);
getLastError();
}
@Override
public WinDef.LRESULT callback(WinDef.HWND hwnd, int uMsg, WinDef.WPARAM wparam, WinDef.LPARAM lparam) {
logger.debug("uMsg " + uMsg);
switch (uMsg) {
case 74: // WM_COPYDATA
try {
COPYDATASTRUCT struct = new COPYDATASTRUCT(lparam.longValue());
String runtimeMsg = new String(struct.lpData.getByteArray(0, struct.cbData), "UTF-16LE");
logger.debug("COPYDATA: " + runtimeMsg + " HWND " + hwnd.getPointer().toString());
JSONObject jsonObject = new JSONObject(runtimeMsg);
ActionEvent actionEvent = new ActionEvent(windowClassName, jsonObject, this);
fireEvent(hwnd, actionEvent);
} catch (Exception e) {
logger.error("Error processing WM_COPYDATA", e);
}
return User32.INSTANCE.DefWindowProc(hwnd, uMsg, wparam, lparam);
default:
return User32.INSTANCE.DefWindowProc(hwnd, uMsg, wparam, lparam);
}
}
public int getLastError() {
int rc = Kernel32.INSTANCE.GetLastError();
if (rc != 0) {
logger.debug("GetLastError: " + rc);
}
return rc;
}
private void createMesssageThread(EventListener listener, int timeout) {
WinMessageThread winMessageThread = new WinMessageThread(listener, timeout);
winMessageThread.start();
}
private void addEvenListener(WinDef.HWND hWnd, WinMessageThread winMessageThread) {
logger.debug("addEvenListener HWND " + hWnd.getPointer().toString());
this.callbacks.put(hWnd.getPointer().toString(), winMessageThread);
}
public void fireEvent(WinDef.HWND hwnd, ActionEvent actionEvent) {
if (this.callbacks.get(hwnd.getPointer().toString()) != null) {
this.callbacks.get(hwnd.getPointer().toString()).getEventListener().eventReceived(actionEvent);
} else {
logger.debug("HWND missing for fireEvent " + hwnd.getPointer().toString());
}
}
protected class TimeoutThread extends Thread {
private EventListener eventListener;
private int timeout;
private volatile boolean interrupted = false;
public TimeoutThread(EventListener eventListener, int timeout) {
this.eventListener = eventListener;
this.timeout = timeout;
this.setName(DesktopPortHandler.class.getName() + ".TimeoutThread");
}
@Override
public void run() {
logger.debug("Starting timeout thread");
synchronized (eventListener) {
try {
eventListener.wait(timeout * 1000);
} catch (InterruptedException e) {
}
}
if (!interrupted) {
JSONObject jsonObject = new JSONObject();
ActionEvent actionEvent = new ActionEvent("TIMEOUT", jsonObject, this);
this.eventListener.eventReceived(actionEvent);
}
logger.debug("exiting");
}
public void interrupt() {
logger.debug("Interrupting timeout thread");
this.interrupted = true;
synchronized (this.eventListener) {
this.eventListener.notifyAll();
}
}
}
protected class WinMessageThread extends Thread {
private WinDef.HWND hWnd; // HWND for this thread
private EventListener eventListener;
private TimeoutThread timeoutThread;
public WinMessageThread(EventListener listener, int timeout) {
super();
this.setDaemon(true);
this.setName(DesktopPortHandler.class.getName() + ".WinMessageThread");
this.eventListener = listener;
this.timeoutThread = new TimeoutThread(listener, timeout);
this.timeoutThread.start();
}
@Override
public void run() {
// CreateWindowEx and GetMessage have to be called from the same thread
// create new window
this.hWnd = User32.INSTANCE.CreateWindowEx(0, windowClassName,
"OpenFin Java hidden window used to catch the windows events ",
0, 0, 0, 0, 0,
null, // WM_DEVICECHANGE contradicts parent=WinUser.HWND_MESSAGE
null, DesktopPortHandler.this.hInst, null);
if (getLastError() == 0) {
changeWindowFilter();
logger.debug(Thread.currentThread().getName() + " started User32.INSTANCE.GetMessage " + this.hWnd.getPointer().toString());
addEvenListener(this.hWnd, this);
WinUser.MSG msg = new WinUser.MSG();
int returnCode;
while ( (returnCode = User32.INSTANCE.GetMessage(msg, this.hWnd, 0, 0)) != 0) {
if (returnCode > 0) {
User32.INSTANCE.TranslateMessage(msg);
User32.INSTANCE.DispatchMessage(msg);
} else {
logger.debug(Thread.currentThread().getName() + " GetMessage return " + returnCode + ", stop GetMessage loop " + getLastError());
break;
}
}
} else {
logger.warn("Error creating message window");
}
logger.debug(Thread.currentThread().getName() + " exiting ");
}
public WinDef.HWND gethWnd() {
return this.hWnd;
}
public EventListener getEventListener() {
return this.eventListener;
}
private void changeWindowFilter() {
try {
// allow processes with lower integrity to send messages
boolean filterResult = WinMessageHelper.customUser32.ChangeWindowMessageFilterEx(this.hWnd, WM_COPYDATA, new WinDef.DWORD(MSGFLT_ALLOW), null);
if (filterResult) {
logger.debug("ChangeWindowMessageFilterEx returns " + filterResult);
} else {
logger.warn("ChangeWindowMessageFilterEx returns " + getLastError());
}
} catch (UnsatisfiedLinkError linkError) {
// ChangeWindowMessageFilterEx is only supported in W7+
logger.warn("Error calling ChangeWindowMessageFilterEx", linkError);
} catch (Exception ex) {
logger.warn("Error calling ChangeWindowMessageFilterEx", ex);
}
}
}
public static class COPYDATASTRUCT extends Structure {
/**
* The by-reference version of this structure.
*/
public static class ByReference
extends COPYDATASTRUCT
implements Structure.ByReference { }
/**
* Instantiates a new COPYDATASTRUCT.
*/
public COPYDATASTRUCT() { }
/**
* Instantiates a new COPYDATASTRUCT with existing data given
* the address of that data.
*
* @param pointer Address of the existing structure.
*/
public COPYDATASTRUCT(final long pointer) {
this(new Pointer(pointer));
}
/**
* Instantiates a new COPYDATASTRUCT with existing data given
* a pointer to that data.
*
* @param memory Pointer to the existing structure.
*/
public COPYDATASTRUCT(final Pointer memory) {
super(memory);
read();
}
/** The data to be passed to the receiving application. */
public BaseTSD.ULONG_PTR dwData;
/** The size, in bytes, of the data pointed to by the lpData
* member. */
public int cbData;
/** The data to be passed to the receiving application. This
* member can be null. */
public Pointer lpData;
/**
* Returns the serialized order of this structure's fields.
*
* @return The serialized order of this structure's fields.
* @see com.sun.jna.Structure#getFieldOrder()
*/
@Override
protected final List getFieldOrder() {
return Arrays.asList(new String[]{"dwData", "cbData", "lpData"});
}
}
}