Keyboard hook fixes, added targetRefresh command

Keyboard hook class now only listens for Hot Keys and not a all keys in
a global keyboard hook.  This should fix the reliability of the keyboard
hook.
Added a new command targetRefresh which will allow you to refresh the
XML on a specified window and not the entire desktop.  This will help if
you want to speed up the synthuse script by not continuously doing full
refreshes of the xml.
This commit is contained in:
Edward Jakubowski
2014-05-16 08:00:24 -04:00
parent 33522694e4
commit c36aa19753
7 changed files with 193 additions and 35 deletions

View File

@@ -167,6 +167,9 @@ public class CommandPopupMenu extends JPopupMenu {
CommandMenuItem mntmSetUpdateThreshold = new CommandMenuItem("setUpdateThreshold", 2, false); CommandMenuItem mntmSetUpdateThreshold = new CommandMenuItem("setUpdateThreshold", 2, false);
add(mntmSetUpdateThreshold); add(mntmSetUpdateThreshold);
CommandMenuItem mntmTargetRefresh = new CommandMenuItem("targetRefresh", 2);
add(mntmTargetRefresh);
CommandMenuItem mntmVerifyElementNotPresent = new CommandMenuItem("verifyElementNotPresent", 2); CommandMenuItem mntmVerifyElementNotPresent = new CommandMenuItem("verifyElementNotPresent", 2);
add(mntmVerifyElementNotPresent); add(mntmVerifyElementNotPresent);

View File

@@ -263,6 +263,8 @@ public class CommandProcessor implements Runnable{
return main.cmdVerifyElementNotPresent(args); return main.cmdVerifyElementNotPresent(args);
if (command.equals("verifyElementPresent")) if (command.equals("verifyElementPresent"))
return main.cmdVerifyElementPresent(args); return main.cmdVerifyElementPresent(args);
if (command.equals("targetRefresh"))
return main.cmdTargetRefresh(args);
if (command.equals("waitForTitle")) if (command.equals("waitForTitle"))
return main.cmdWaitForTitle(args); return main.cmdWaitForTitle(args);
if (command.equals("waitForText")) if (command.equals("waitForText"))

View File

@@ -15,6 +15,8 @@ import java.util.List;
import com.sun.jna.*; import com.sun.jna.*;
import com.sun.jna.platform.win32.User32; import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser.*; import com.sun.jna.platform.win32.WinUser.*;
import com.sun.jna.platform.win32.WinDef.*; import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.win32.W32APIOptions; import com.sun.jna.win32.W32APIOptions;
@@ -24,6 +26,7 @@ public class KeyboardHook implements Runnable{
// Keyboard event class, interface, and array list // Keyboard event class, interface, and array list
public static class TargetKeyPress { public static class TargetKeyPress {
int idNumber;
int targetKeyCode; int targetKeyCode;
boolean withShift, withCtrl, withAlt; boolean withShift, withCtrl, withAlt;
public TargetKeyPress (int targetKeyCode) { public TargetKeyPress (int targetKeyCode) {
@@ -32,6 +35,13 @@ public class KeyboardHook implements Runnable{
this.withCtrl = false; this.withCtrl = false;
this.withAlt = false; this.withAlt = false;
} }
public TargetKeyPress (int idNumber, int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) {
this.idNumber = idNumber;
this.targetKeyCode = targetKeyCode;
this.withShift = withShift;
this.withCtrl = withCtrl;
this.withAlt = withAlt;
}
public TargetKeyPress (int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) { public TargetKeyPress (int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) {
this.targetKeyCode = targetKeyCode; this.targetKeyCode = targetKeyCode;
this.withShift = withShift; this.withShift = withShift;
@@ -59,6 +69,15 @@ public class KeyboardHook implements Runnable{
public static final int VK_MENU = 0x12; public static final int VK_MENU = 0x12;
public static final int VK_CAPITAL = 0x14; public static final int VK_CAPITAL = 0x14;
public static final int MOD_ALT = 0x0001;
public static final int MOD_CONTROL = 0x0002;
public static final int MOD_NOREPEAT = 0x4000;
public static final int MOD_SHIFT = 0x0004;
public static final int MOD_WIN = 0x0008;
public static final int QS_HOTKEY = 0x0080;
public static final int INFINITE = 0xFFFFFFFF;
public static HHOOK hHook = null; public static HHOOK hHook = null;
public static LowLevelKeyboardProc lpfn; public static LowLevelKeyboardProc lpfn;
public static volatile boolean quit = false; public static volatile boolean quit = false;
@@ -76,6 +95,17 @@ public class KeyboardHook implements Runnable{
short GetKeyState(int nVirtKey); short GetKeyState(int nVirtKey);
short GetAsyncKeyState(int nVirtKey); short GetAsyncKeyState(int nVirtKey);
/*
DWORD WINAPI MsgWaitForMultipleObjects(
__in DWORD nCount, //The number of object handles in the array pointed to by pHandles.
__in const HANDLE *pHandles, //An array of object handles.
__in BOOL bWaitAll, //If this parameter is TRUE, the function returns when the states of all objects in the pHandles array have been set to signaled and an input event has been received.
__in DWORD dwMilliseconds, //if dwMilliseconds is INFINITE, the function will return only when the specified objects are signaled.
__in DWORD dwWakeMask //The input types for which an input event object handle will be added to the array of object handles.
);*/
int MsgWaitForMultipleObjects(int nCount, Pointer pHandles, boolean bWaitAll, int dwMilliSeconds, int dwWakeMask);
boolean RegisterHotKey(Pointer hWnd, int id, int fsModifiers, int vk);
//public static interface HOOKPROC extends StdCallCallback { //public static interface HOOKPROC extends StdCallCallback {
// LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT lParam); // LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT lParam);
//} //}
@@ -89,8 +119,10 @@ public class KeyboardHook implements Runnable{
// Create Global Windows Keyboard hook and wait until quit == true // Create Global Windows Keyboard hook and wait until quit == true
public void createGlobalKeyboardHook() { public void createGlobalKeyboardHook() {
if (hHook != null) if (hHook != null)
return; //hook already running don't add anymore return; //hook already running don't add anymore
System.out.println("starting global keyboard hook");
HMODULE hMod = Kernel32.instance.GetModuleHandle(null); HMODULE hMod = Kernel32.instance.GetModuleHandle(null);
HOOKPROC lpfn = new HOOKPROC() { HOOKPROC lpfn = new HOOKPROC() {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -108,33 +140,49 @@ public class KeyboardHook implements Runnable{
hHook = User32.INSTANCE.SetWindowsHookEx(WH_KEYBOARD_LL, lpfn, hMod, 0); hHook = User32.INSTANCE.SetWindowsHookEx(WH_KEYBOARD_LL, lpfn, hMod, 0);
if (hHook == null) if (hHook == null)
return; return;
//System.out.println("starting message loop");
MSG msg = new MSG(); MSG msg = new MSG();
int cnt = 0;
try { try {
while (!quit) { while (!quit) {
User32.INSTANCE.PeekMessage(msg, null, 0, 0, 1); User32.INSTANCE.PeekMessage(msg, null, 0, 0, 1);
Thread.sleep(10); if (msg.message == User32.WM_HOTKEY){ // && msg.wParam.intValue() == 1
++cnt; //System.out.println("Hot key pressed!");
if (cnt > 500) msg = new MSG(); //must clear msg so it doesn't repeat
{
cnt = 0;
//System.out.println("heartbeat test");
} }
Thread.sleep(10);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
/* //System.out.println("message loop stopped");
while (!quit) {
// hex arguments: WM_KEYFIRST, WM_KEYLAST
int result = User32.INSTANCE.GetMessage(msg, null, 0x100, 0x109);
if (result == -1) {
break;
} else {
User32.INSTANCE.TranslateMessage(msg);
User32.INSTANCE.DispatchMessage(msg);
} }
}*/
// Create HotKeys Windows hook and wait until quit == true
public void createHotKeysHook() {
registerAllHotKeys();
//User32Ex.instance.MsgWaitForMultipleObjects(0, Pointer.NULL, true, INFINITE, QS_HOTKEY);
//System.out.println("starting message loop");
MSG msg = new MSG();
try {
while (!quit) {
User32.INSTANCE.PeekMessage(msg, null, 0, 0, 1);
if (msg.message == User32.WM_HOTKEY){ // && msg.wParam.intValue() == 1
//System.out.println("Hot key pressed " + msg.wParam);
TargetKeyPress target = findTargetKeyPressById(msg.wParam.intValue());
if (target != null)
events.keyPressed(target);
msg = new MSG(); //must clear msg so it doesn't repeat
}
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
unregisterAllHotKeys();
//System.out.println("message loop stopped"); //System.out.println("message loop stopped");
} }
@@ -149,11 +197,12 @@ public class KeyboardHook implements Runnable{
} }
//stops Keyboard hook and causes the unhook command to be called //stops Keyboard hook and causes the unhook command to be called
public static void stopGlobalKeyboardHook() { public static void stopKeyboardHook() {
quit = true; quit = true;
} }
// search target keyboard event list for a match and return it otherwise return null if no match // search target keyboard event list for a match and return it otherwise return null if no match
private TargetKeyPress getTargetKeyPressed(int keyCode) { private TargetKeyPress getTargetKeyPressed(int keyCode) {
TargetKeyPress target = null; TargetKeyPress target = null;
for (TargetKeyPress tkp : KeyboardHook.targetList) { for (TargetKeyPress tkp : KeyboardHook.targetList) {
@@ -170,10 +219,52 @@ public class KeyboardHook implements Runnable{
return target; return target;
} }
private TargetKeyPress findTargetKeyPressById(int idNumber)
{
TargetKeyPress target = null;
for (TargetKeyPress tkp : KeyboardHook.targetList) {
if (tkp.idNumber == idNumber)
return tkp;
}
return target;
}
// add more target keys to watch for // add more target keys to watch for
public static void addKeyEvent(int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) { public static void addKeyEvent(int targetKeyCode, boolean withShift, boolean withCtrl, boolean withAlt) {
KeyboardHook.targetList.add(new TargetKeyPress(targetKeyCode, withShift, withCtrl, withAlt)); KeyboardHook.targetList.add(new TargetKeyPress(KeyboardHook.targetList.size() + 1 , targetKeyCode, withShift, withCtrl, withAlt));
} }
private void registerAllHotKeys() // must register hot keys in the same thread that is watching for hotkey messages
{
for (TargetKeyPress tkp : KeyboardHook.targetList) {
//BOOL WINAPI RegisterHotKey(HWND hWnd, int id, UINT fsModifiers, UINT vk);
int modifiers = User32.MOD_NOREPEAT;
if (tkp.withShift)
modifiers = modifiers | User32.MOD_SHIFT;
if (tkp.withCtrl)
modifiers = modifiers | User32.MOD_CONTROL;
if (tkp.withAlt)
modifiers = modifiers | User32.MOD_ALT;
//System.out.println("RegisterHotKey " + tkp.idNumber + "," + modifiers + ", " + tkp.targetKeyCode);
if (!User32.INSTANCE.RegisterHotKey(new WinDef.HWND(Pointer.NULL), tkp.idNumber, modifiers, tkp.targetKeyCode))
{
System.out.println("Couldn't register hotkey " + tkp.targetKeyCode);
}
}
}
private void unregisterAllHotKeys() // must register hot keys in the same thread that is watching for hotkey messages
{
for (TargetKeyPress tkp : KeyboardHook.targetList) {
if (!User32.INSTANCE.UnregisterHotKey(Pointer.NULL, tkp.idNumber))
{
System.out.println("Couldn't unregister hotkey " + tkp.targetKeyCode);
}
}
}
// add more target keys to watch for // add more target keys to watch for
public static void addKeyEvent(int targetKeyCode) { public static void addKeyEvent(int targetKeyCode) {
KeyboardHook.targetList.add(new TargetKeyPress(targetKeyCode)); KeyboardHook.targetList.add(new TargetKeyPress(targetKeyCode));
@@ -181,7 +272,8 @@ public class KeyboardHook implements Runnable{
@Override @Override
public void run() { public void run() {
createGlobalKeyboardHook(); //createGlobalKeyboardHook();
createHotKeysHook();
//System.out.println("Unhooking Global Keyboard Hook"); //System.out.println("Unhooking Global Keyboard Hook");
unhook();//wait for quit == true then unhook unhook();//wait for quit == true then unhook
} }
@@ -193,7 +285,7 @@ public class KeyboardHook implements Runnable{
this.events = events; this.events = events;
} }
public static void StartGlobalKeyboardHookThreaded(KeyboardEvents events) { public static void StartKeyboardHookThreaded(KeyboardEvents events) {
Thread t = new Thread(new KeyboardHook(events)); Thread t = new Thread(new KeyboardHook(events));
t.start(); t.start();
} }

View File

@@ -393,7 +393,7 @@ public class SynthuseDlg extends JFrame {
this.addWindowListener(new WindowAdapter() { this.addWindowListener(new WindowAdapter() {
@Override @Override
public void windowClosing(WindowEvent arg0) { public void windowClosing(WindowEvent arg0) {
KeyboardHook.stopGlobalKeyboardHook(); //stop keyboard hook KeyboardHook.stopKeyboardHook(); //stop keyboard hook
config.save(); config.save();
SynthuseDlg.this.dispose(); // force app to close SynthuseDlg.this.dispose(); // force app to close
} }
@@ -402,7 +402,7 @@ public class SynthuseDlg extends JFrame {
KeyboardHook.addKeyEvent(config.getRefreshKeyCode(), true, true, false);// refresh xml when CTRL+SHIFT+3 is pressed KeyboardHook.addKeyEvent(config.getRefreshKeyCode(), true, true, false);// refresh xml when CTRL+SHIFT+3 is pressed
KeyboardHook.addKeyEvent(config.getTargetKeyCode(), true, true, false);// target window when CTRL+SHIFT+~ is pressed KeyboardHook.addKeyEvent(config.getTargetKeyCode(), true, true, false);// target window when CTRL+SHIFT+~ is pressed
//add global hook and event //add global hook and event
KeyboardHook.StartGlobalKeyboardHookThreaded(new KeyboardHook.KeyboardEvents() { KeyboardHook.StartKeyboardHookThreaded(new KeyboardHook.KeyboardEvents() {
@Override @Override
public void keyPressed(KeyboardHook.TargetKeyPress target) { public void keyPressed(KeyboardHook.TargetKeyPress target) {
//System.out.println("target key pressed " + target.targetKeyCode); //System.out.println("target key pressed " + target.targetKeyCode);

View File

@@ -79,14 +79,9 @@ public class WindowsEnumeratedXml implements Runnable{
public static String getXml() { public static String getXml() {
final Map<String, WindowInfo> infoList = new LinkedHashMap<String, WindowInfo>(); final Map<String, WindowInfo> infoList = new LinkedHashMap<String, WindowInfo>();
final Map<String, String> processList = new LinkedHashMap<String, String>();
final List<String> wpfParentList = new ArrayList<String>();//HwndWrapper final List<String> wpfParentList = new ArrayList<String>();//HwndWrapper
final List<String> silverlightParentList = new ArrayList<String>();//MicrosoftSilverlight final List<String> silverlightParentList = new ArrayList<String>();//MicrosoftSilverlight
final List<String> winFormParentList = new ArrayList<String>();//class="WindowsForms*" final List<String> winFormParentList = new ArrayList<String>();//class="WindowsForms*"
int wpfCount = 0;
int winFormCount = 0;
int silverlightCount = 0;
int menuCount = 0;
//wpf.setTouchableOnly(false); //wpf.setTouchableOnly(false);
//wpf.countChildrenWindows();//fix for missing cached elements //wpf.countChildrenWindows();//fix for missing cached elements
@@ -126,22 +121,29 @@ public class WindowsEnumeratedXml implements Runnable{
UiaBridge uiabridge = new UiaBridge(); UiaBridge uiabridge = new UiaBridge();
for (String handle : wpfParentList) { for (String handle : wpfParentList) {
Map<String, WindowInfo> wpfInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "*"); Map<String, WindowInfo> wpfInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "*");
wpfCount += wpfInfoList.size();
infoList.putAll(wpfInfoList); infoList.putAll(wpfInfoList);
} }
for (String handle : winFormParentList) { for (String handle : winFormParentList) {
//System.out.println("winform parent " + handle); //System.out.println("winform parent " + handle);
Map<String, WindowInfo> winFormInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "*"); Map<String, WindowInfo> winFormInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "*");
winFormCount += winFormInfoList.size();
infoList.putAll(winFormInfoList); infoList.putAll(winFormInfoList);
} }
for (String handle : silverlightParentList) { for (String handle : silverlightParentList) {
Map<String, WindowInfo> slInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "Silverlight"); Map<String, WindowInfo> slInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "Silverlight");
silverlightCount += slInfoList.size();
infoList.putAll(slInfoList); infoList.putAll(slInfoList);
} }
}
return generateWindowsXml(infoList, "EnumeratedWindows");
} }
public static String generateWindowsXml(Map<String, WindowInfo> infoList, String rootElementName)
{
final Map<String, String> processList = new LinkedHashMap<String, String>();
int wpfCount = 0;
int winFormCount = 0;
int silverlightCount = 0;
int menuCount = 0;
// convert window info list to xml dom // convert window info list to xml dom
try { try {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
@@ -149,7 +151,7 @@ public class WindowsEnumeratedXml implements Runnable{
// root elements // root elements
Document doc = docBuilder.newDocument(); Document doc = docBuilder.newDocument();
Element rootElement = doc.createElement("EnumeratedWindows"); Element rootElement = doc.createElement(rootElementName);
doc.appendChild(rootElement); doc.appendChild(rootElement);
long parentCount = 0; long parentCount = 0;
@@ -162,11 +164,20 @@ public class WindowsEnumeratedXml implements Runnable{
if (w.framework.equals("win32")) if (w.framework.equals("win32"))
win = doc.createElement("win"); win = doc.createElement("win");
else if (w.framework.equals("WPF")) else if (w.framework.equals("WPF"))
{
win = doc.createElement("wpf"); win = doc.createElement("wpf");
++wpfCount;
}
else if (w.framework.equals("WinForm")) else if (w.framework.equals("WinForm"))
{
win = doc.createElement("winfrm"); win = doc.createElement("winfrm");
++winFormCount;
}
else if (w.framework.equals("Silverlight")) else if (w.framework.equals("Silverlight"))
{
win = doc.createElement("silver"); win = doc.createElement("silver");
++silverlightCount;
}
else else
win = doc.createElement("win"); win = doc.createElement("win");
//System.out.println(w.toString()); //System.out.println(w.toString());
@@ -229,7 +240,7 @@ public class WindowsEnumeratedXml implements Runnable{
totals.setAttribute("parentCount", parentCount+""); totals.setAttribute("parentCount", parentCount+"");
totals.setAttribute("childCount", childCount+""); totals.setAttribute("childCount", childCount+"");
totals.setAttribute("windowCount", infoList.size()+""); totals.setAttribute("windowCount", infoList.size()+"");
totals.setAttribute("wpfWrapperCount", wpfParentList.size()+""); //totals.setAttribute("wpfWrapperCount", wpfParentList.size()+"");
totals.setAttribute("wpfCount", wpfCount+""); totals.setAttribute("wpfCount", wpfCount+"");
totals.setAttribute("winFormCount", winFormCount+""); totals.setAttribute("winFormCount", winFormCount+"");
totals.setAttribute("silverlightCount", silverlightCount+""); totals.setAttribute("silverlightCount", silverlightCount+"");

View File

@@ -7,6 +7,7 @@ import java.io.StringWriter;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import org.synthuse.*; import org.synthuse.*;
@@ -97,6 +98,48 @@ public class BaseCommand {
LAST_UPDATED_XML = System.nanoTime(); LAST_UPDATED_XML = System.nanoTime();
} }
public void targetXmlRefresh(String xpath) {
if (WIN_XML.isEmpty()) //can't target refresh unless there is XML to start with
{
forceXmlRefresh();
return;
}
//WIN_XML = WindowsEnumeratedXml.getXml();
LAST_UPDATED_XML = System.nanoTime();
String resultStr = "";
String resultHwndStr = "";
List<String> resultList = WindowsEnumeratedXml.evaluateXpathGetValues(WIN_XML, xpath);
for(String item: resultList) {
//System.out.println("xpath result item: " + item);
resultStr = item;
if (item.contains("hwnd=")) {
List<String> hwndList = WindowsEnumeratedXml.evaluateXpathGetValues(item, "//@hwnd");
if (hwndList.size() > 0)
resultHwndStr = hwndList.get(0).replaceAll("[^\\d-.]", ""); //get first hwnd;
}
else
resultStr = item;
break;
}
String newXml = "";
Map<String, WindowInfo> infoList;
if (resultHwndStr.contains("-")) { //uiabridge target refresh
resultHwndStr = resultHwndStr.split("-")[1];
infoList = WindowsEnumeratedXml.EnumerateWindowsWithUiaBridge(uiabridge, resultHwndStr, "*");
newXml = WindowsEnumeratedXml.generateWindowsXml(infoList, "updates");
//System.out.println("newXml: " + newXml);
}
else
{ // native target refresh
}
int pos = WIN_XML.indexOf(resultStr);
WIN_XML = WIN_XML.substring(0, pos) + newXml + WIN_XML.substring(pos + resultStr.length());
}
public String getWindowTypeWithXpath(String xpath) { public String getWindowTypeWithXpath(String xpath) {
String result = ""; String result = "";
double secondsFromLastUpdate = ((double)(System.nanoTime() - LAST_UPDATED_XML) / 1000000000); double secondsFromLastUpdate = ((double)(System.nanoTime() - LAST_UPDATED_XML) / 1000000000);

View File

@@ -64,6 +64,13 @@ public class MainCommands extends BaseCommand {
return true; return true;
} }
public boolean cmdTargetRefresh(String[] args) {
if (!checkArgumentLength(args, 1))
return false;
targetXmlRefresh(args[0]);
return true;
}
public boolean cmdWaitForTitle(String[] args) { public boolean cmdWaitForTitle(String[] args) {
if (!checkArgumentLength(args, 1)) if (!checkArgumentLength(args, 1))
return false; return false;