diff --git a/src/org/synthuse/CommandPopupMenu.java b/src/org/synthuse/CommandPopupMenu.java index 46440d4..e49f294 100644 --- a/src/org/synthuse/CommandPopupMenu.java +++ b/src/org/synthuse/CommandPopupMenu.java @@ -148,7 +148,7 @@ public class CommandPopupMenu extends JPopupMenu { CommandMenuItem mntmForceRefresh = new CommandMenuItem("forceRefresh", 1, false); add(mntmForceRefresh); - + CommandMenuItem mntmOpen = new CommandMenuItem("open", 2, false); add(mntmOpen); @@ -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); diff --git a/src/org/synthuse/CommandProcessor.java b/src/org/synthuse/CommandProcessor.java index abef8e4..f568651 100644 --- a/src/org/synthuse/CommandProcessor.java +++ b/src/org/synthuse/CommandProcessor.java @@ -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")) diff --git a/src/org/synthuse/KeyboardHook.java b/src/org/synthuse/KeyboardHook.java index 034d311..08b5542 100644 --- a/src/org/synthuse/KeyboardHook.java +++ b/src/org/synthuse/KeyboardHook.java @@ -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; @@ -75,7 +94,18 @@ public class KeyboardHook implements Runnable{ boolean UnhookWindowsHookEx(HHOOK idHook); 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(); } diff --git a/src/org/synthuse/SynthuseDlg.java b/src/org/synthuse/SynthuseDlg.java index dc23951..7a6adc1 100644 --- a/src/org/synthuse/SynthuseDlg.java +++ b/src/org/synthuse/SynthuseDlg.java @@ -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); diff --git a/src/org/synthuse/WindowsEnumeratedXml.java b/src/org/synthuse/WindowsEnumeratedXml.java index 7c2b1cf..858f6d3 100644 --- a/src/org/synthuse/WindowsEnumeratedXml.java +++ b/src/org/synthuse/WindowsEnumeratedXml.java @@ -79,14 +79,9 @@ public class WindowsEnumeratedXml implements Runnable{ public static String getXml() { final Map infoList = new LinkedHashMap(); - final Map processList = new LinkedHashMap(); final List wpfParentList = new ArrayList();//HwndWrapper final List silverlightParentList = new ArrayList();//MicrosoftSilverlight final List winFormParentList = new ArrayList();//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 wpfInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "*"); - wpfCount += wpfInfoList.size(); infoList.putAll(wpfInfoList); } for (String handle : winFormParentList) { //System.out.println("winform parent " + handle); Map winFormInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "*"); - winFormCount += winFormInfoList.size(); infoList.putAll(winFormInfoList); } for (String handle : silverlightParentList) { Map slInfoList = EnumerateWindowsWithUiaBridge(uiabridge, handle, "Silverlight"); - silverlightCount += slInfoList.size(); infoList.putAll(slInfoList); } + } + return generateWindowsXml(infoList, "EnumeratedWindows"); + } + public static String generateWindowsXml(Map infoList, String rootElementName) + { + final Map processList = new LinkedHashMap(); + 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+""); diff --git a/src/org/synthuse/commands/BaseCommand.java b/src/org/synthuse/commands/BaseCommand.java index 69fc9e7..d885ff3 100644 --- a/src/org/synthuse/commands/BaseCommand.java +++ b/src/org/synthuse/commands/BaseCommand.java @@ -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.*; @@ -96,6 +97,48 @@ public class BaseCommand { WIN_XML = WindowsEnumeratedXml.getXml(); 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 resultList = WindowsEnumeratedXml.evaluateXpathGetValues(WIN_XML, xpath); + for(String item: resultList) { + //System.out.println("xpath result item: " + item); + resultStr = item; + if (item.contains("hwnd=")) { + List 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 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 = ""; diff --git a/src/org/synthuse/commands/MainCommands.java b/src/org/synthuse/commands/MainCommands.java index 11d5d47..a12cd61 100644 --- a/src/org/synthuse/commands/MainCommands.java +++ b/src/org/synthuse/commands/MainCommands.java @@ -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;