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);
add(mntmSetUpdateThreshold);
CommandMenuItem mntmTargetRefresh = new CommandMenuItem("targetRefresh", 2);
add(mntmTargetRefresh);
CommandMenuItem mntmVerifyElementNotPresent = new CommandMenuItem("verifyElementNotPresent", 2);
add(mntmVerifyElementNotPresent);

View File

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

View File

@@ -15,6 +15,8 @@ import java.util.List;
import com.sun.jna.*;
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.WinDef.*;
import com.sun.jna.win32.W32APIOptions;
@@ -24,6 +26,7 @@ public class KeyboardHook implements Runnable{
// Keyboard event class, interface, and array list
public static class TargetKeyPress {
int idNumber;
int targetKeyCode;
boolean withShift, withCtrl, withAlt;
public TargetKeyPress (int targetKeyCode) {
@@ -32,6 +35,13 @@ public class KeyboardHook implements Runnable{
this.withCtrl = 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) {
this.targetKeyCode = targetKeyCode;
this.withShift = withShift;
@@ -59,6 +69,15 @@ public class KeyboardHook implements Runnable{
public static final int VK_MENU = 0x12;
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 LowLevelKeyboardProc lpfn;
public static volatile boolean quit = false;
@@ -76,6 +95,17 @@ public class KeyboardHook implements Runnable{
short GetKeyState(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 {
// 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
public void createGlobalKeyboardHook() {
if (hHook != null)
return; //hook already running don't add anymore
System.out.println("starting global keyboard hook");
HMODULE hMod = Kernel32.instance.GetModuleHandle(null);
HOOKPROC lpfn = new HOOKPROC() {
@SuppressWarnings("unused")
@@ -108,33 +140,49 @@ public class KeyboardHook implements Runnable{
hHook = User32.INSTANCE.SetWindowsHookEx(WH_KEYBOARD_LL, lpfn, hMod, 0);
if (hHook == null)
return;
//System.out.println("starting message loop");
MSG msg = new MSG();
int cnt = 0;
try {
while (!quit) {
User32.INSTANCE.PeekMessage(msg, null, 0, 0, 1);
Thread.sleep(10);
++cnt;
if (cnt > 500)
{
cnt = 0;
//System.out.println("heartbeat test");
if (msg.message == User32.WM_HOTKEY){ // && msg.wParam.intValue() == 1
//System.out.println("Hot key pressed!");
msg = new MSG(); //must clear msg so it doesn't repeat
}
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
/*
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);
//System.out.println("message loop stopped");
}
// 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");
}
@@ -149,11 +197,12 @@ public class KeyboardHook implements Runnable{
}
//stops Keyboard hook and causes the unhook command to be called
public static void stopGlobalKeyboardHook() {
public static void stopKeyboardHook() {
quit = true;
}
// search target keyboard event list for a match and return it otherwise return null if no match
private TargetKeyPress getTargetKeyPressed(int keyCode) {
TargetKeyPress target = null;
for (TargetKeyPress tkp : KeyboardHook.targetList) {
@@ -170,10 +219,52 @@ public class KeyboardHook implements Runnable{
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
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
public static void addKeyEvent(int targetKeyCode) {
KeyboardHook.targetList.add(new TargetKeyPress(targetKeyCode));
@@ -181,7 +272,8 @@ public class KeyboardHook implements Runnable{
@Override
public void run() {
createGlobalKeyboardHook();
//createGlobalKeyboardHook();
createHotKeysHook();
//System.out.println("Unhooking Global Keyboard Hook");
unhook();//wait for quit == true then unhook
}
@@ -193,7 +285,7 @@ public class KeyboardHook implements Runnable{
this.events = events;
}
public static void StartGlobalKeyboardHookThreaded(KeyboardEvents events) {
public static void StartKeyboardHookThreaded(KeyboardEvents events) {
Thread t = new Thread(new KeyboardHook(events));
t.start();
}

View File

@@ -393,7 +393,7 @@ public class SynthuseDlg extends JFrame {
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent arg0) {
KeyboardHook.stopGlobalKeyboardHook(); //stop keyboard hook
KeyboardHook.stopKeyboardHook(); //stop keyboard hook
config.save();
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.getTargetKeyCode(), true, true, false);// target window when CTRL+SHIFT+~ is pressed
//add global hook and event
KeyboardHook.StartGlobalKeyboardHookThreaded(new KeyboardHook.KeyboardEvents() {
KeyboardHook.StartKeyboardHookThreaded(new KeyboardHook.KeyboardEvents() {
@Override
public void keyPressed(KeyboardHook.TargetKeyPress target) {
//System.out.println("target key pressed " + target.targetKeyCode);

View File

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

View File

@@ -7,6 +7,7 @@ import java.io.StringWriter;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.synthuse.*;
@@ -97,6 +98,48 @@ public class BaseCommand {
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) {
String result = "";
double secondsFromLastUpdate = ((double)(System.nanoTime() - LAST_UPDATED_XML) / 1000000000);

View File

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