From b5082e2b2204a71672077b63a49a39544bd2f440 Mon Sep 17 00:00:00 2001 From: Edward Jakubowski Date: Sat, 19 Apr 2014 21:17:35 -0400 Subject: [PATCH] Added support for scripting menus, and showing counts for lists, comboboxes, treeviews, listviews Support for scripting menus and seeing more information about various list types, and partially working toolbars --- native/WpfBridge/WpfAutomation.cpp | 26 +++- native/WpfBridge/bin/wpfbridge32.dll | Bin 78848 -> 78848 bytes native/WpfBridge/bin/wpfbridge64.dll | Bin 93184 -> 93184 bytes src/org/synthuse/Api.java | 130 ++++++++++++++++-- src/org/synthuse/CommandPopupMenu.java | 3 + src/org/synthuse/CommandProcessor.java | 2 + src/org/synthuse/MenuInfo.java | 85 ++++++++++++ src/org/synthuse/WindowInfo.java | 103 +++++++++++++- src/org/synthuse/WindowsEnumeratedXml.java | 42 +++++- src/org/synthuse/XpathManager.java | 9 +- src/org/synthuse/commands/BaseCommand.java | 30 ++++ .../synthuse/commands/WindowsCommands.java | 16 +++ src/org/synthuse/test/UnitTestHelper.java | 79 +++++++++++ src/org/synthuse/test/WinApiTest.java | 32 ++++- src/org/synthuse/test/WpfBridgeTest.java | 71 +--------- 15 files changed, 541 insertions(+), 87 deletions(-) create mode 100644 src/org/synthuse/MenuInfo.java create mode 100644 src/org/synthuse/test/UnitTestHelper.java diff --git a/native/WpfBridge/WpfAutomation.cpp b/native/WpfBridge/WpfAutomation.cpp index ce97bc1..f8e4a9f 100644 --- a/native/WpfBridge/WpfAutomation.cpp +++ b/native/WpfBridge/WpfAutomation.cpp @@ -188,10 +188,15 @@ System::String ^ WpfAutomation::getRuntimeIdFromHandle(System::IntPtr windowHand array ^ WpfAutomation::enumDescendantWindowInfo(System::String ^runtimeIdValue, System::String ^properties) { AutomationElement ^parent = findAutomationElementById(runtimeIdValue, true); - if (parent == nullptr) - return nullptr; + if (parent == nullptr) + return nullptr; AutomationElementCollection ^aec = parent->FindAll(TreeScope::Descendants, getSearchConditions()); + //when wildcard is enabled it will pull all property names & values + System::Boolean wildcardEnabled = false; + if (properties->Equals(L"*")) + wildcardEnabled = true; + //create array for keeping order of properties System::String ^delim = L","; array ^propSpltArray = properties->Split(delim->ToCharArray()); @@ -203,6 +208,11 @@ array ^ WpfAutomation::enumDescendantWindowInfo(System::String { array ^aps = child->GetSupportedProperties(); array ^propValues = gcnew array(propSpltArray->Length);//keep order + System::String ^wildcardProperties = L""; + if (wildcardEnabled) { + wildcardProperties += "ParentRuntimeIdProperty:" + getRuntimeIdFromElement(tw->GetParent(child)) + ","; + //propValues = gcnew array(aps->Length +1 );//add one for parent property since it doesn't exist + } for(int i=0 ; i < propValues->Length ; i++) { propValues[i] = L""; @@ -218,7 +228,7 @@ array ^ WpfAutomation::enumDescendantWindowInfo(System::String System::String ^shortPropName = L" null "; if (ap->ProgrammaticName->Contains(L".")) shortPropName = ap->ProgrammaticName->Substring(ap->ProgrammaticName->IndexOf(L".") + 1); - if (properties->Contains(shortPropName) || properties->Contains(ap->ProgrammaticName) || ap->ProgrammaticName->Equals(properties)) + if (properties->Contains(shortPropName) || properties->Contains(ap->ProgrammaticName) || ap->ProgrammaticName->Equals(properties) || wildcardEnabled) { //System::Console::WriteLine("shortPropName: {0}", shortPropName); System::Object ^currentVal = child->GetCurrentPropertyValue(ap); @@ -242,17 +252,23 @@ array ^ WpfAutomation::enumDescendantWindowInfo(System::String } if (currentPropertyStr->Equals(L"")) //if there isn't a value skip continue; + if (wildcardEnabled) { + wildcardProperties += shortPropName + ":" +currentPropertyStr + ","; + continue; + } //System::Console::WriteLine("currentPropertyStr: {0}", currentPropertyStr); //find the correct order to return this property for(int i=0 ; i < propSpltArray->Length ; i++) { - if (propSpltArray[i]->Equals(shortPropName) || propSpltArray[i]->Equals(ap->ProgrammaticName)) - propValues[i] = currentPropertyStr; + if (propSpltArray[i]->Equals(shortPropName) || propSpltArray[i]->Equals(ap->ProgrammaticName)) + propValues[i] = currentPropertyStr; } } //output properties in the correct order for(int i=0 ; i < propSpltArray->Length ; i++) winInfoList[count] += propValues[i] + L","; + if (wildcardEnabled) + winInfoList[count] += wildcardProperties; ++count; } return winInfoList; diff --git a/native/WpfBridge/bin/wpfbridge32.dll b/native/WpfBridge/bin/wpfbridge32.dll index 4dd0ad1dbf3309c1b5ef5719779fa5bb53b41c17..b6dc92c758f197b62410554042c45517bad762b4 100644 GIT binary patch delta 11990 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 delta 12731 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 getFieldOrder() { + return Arrays.asList(new String[] { "cbSize", "fMask", "fType", "fState", "wID", "hSubMenu", "hbmpChecked", + "hbmpUnchecked", "dwItemData", "dwTypeData", "cch", "hbmpItem" }); + } + + public int cbSize; //The size of the structure, in bytes. The caller must set this member to sizeof(MENUITEMINFO). + public int fMask; //Indicates the members to be retrieved or set. MIIM_STRING or MIIM_SUBMENU or ... + public int fType; //The menu item type. fType is used only if fMask has a value of MIIM_FTYPE. + public int fState; //The menu item state. This member can be one or more of these values. Set fMask to MIIM_STATE to use fState. + public int wID; //An application-defined value that identifies the menu item. Set fMask to MIIM_ID to use wID. + public HMENU hSubMenu; //A handle to the drop-down menu or submenu associated with the menu item. Or NULL + public HBITMAP hbmpChecked; //A handle to the bitmap to display next to the item if it is selected. + public HBITMAP hbmpUnchecked; //A handle to the bitmap to display next to the item if it is not selected. + public ULONG_PTR dwItemData; //An application-defined value associated with the menu item. Set fMask to MIIM_DATA + //public byte[] dwTypeData = new byte[256]; + public String dwTypeData; //The contents of the menu item, depends on the value of fType and is used only if the MIIM_TYPE flag is set in the fMask member + public int cch; //The length of the menu item text, in characters, when information is received about a menu item of the MFT_STRING type. + public HBITMAP hbmpItem; //A handle to the bitmap to be displayed, or it can be one of the values in the following table. + } + } public interface User32 extends W32APIOptions { User32 instance = (User32) Native.loadLibrary("user32", User32.class, DEFAULT_OPTIONS); @@ -158,6 +232,20 @@ public class Api { boolean ScreenToClient(HWND hWnd, long[] lpPoint);//use macros POINT_X() and POINT_Y() on long lpPoint[0] //HWND WindowFromPoint(int xPoint, int yPoint); //HWND WindowFromPoint(POINT point); + + HMENU GetMenu(HWND hWnd); + boolean IsMenu(HMENU hMenu); + int GetMenuString(HMENU hMenu, int uIDItem, char[] buffer, int nMaxCount, int uFlag); + HMENU GetSubMenu(HMENU hMenu, int nPos); + int GetMenuItemCount(HMENU hMenu); + int GetMenuItemID(HMENU hMenu, int nPos); + //BOOL WINAPI GetMenuItemInfo(_In_ HMENU hMenu, _In_ UINT uItem, _In_ BOOL fByPosition, _Inout_ LPMENUITEMINFO lpmii); + boolean GetMenuItemInfoA(HMENU hMenu, int uItem, boolean fByPosition, WinDefExt.MENUITEMINFO mii); //MENUITEMINFO + boolean TrackPopupMenu(HMENU hMenu, int uFlags, int x, int y, int nReserved, HWND hWnd, long prcRect); + // + + int GetDlgCtrlID(HWND hwndCtl); + int GetDlgItemText(HWND hDlg, int nIDDlgItem, byte[] buffer, int nMaxCount); } public interface Gdi32 extends W32APIOptions { @@ -346,6 +434,32 @@ public class Api { user32.SendMessageA(handle, WM_KEYUP, keyCode, null); } + public String GetMenuItemText(HMENU hmenu, int position) { + if (user32.IsMenu(hmenu) == false) + return ""; + char[] buffer = new char[256]; + user32.GetMenuString(hmenu, position, buffer, 256, 0x0400); + return Native.toString(buffer); + /* + Api.WinDefExt.MENUITEMINFO mii = new Api.WinDefExt.MENUITEMINFO(); // = (MENUITEMINFO)Api.MENUITEMINFO.newInstance(Api.MENUITEMINFO.class); + mii.fMask = Api.WinDefExt.MENUITEMINFO.MIIM_TYPE; + mii.fType = Api.WinDefExt.MENUITEMINFO.MFT_STRING; + mii.cch = 0; + mii.dwTypeData = ""; + @SuppressWarnings("unused") + boolean result = Api.User32.instance.GetMenuItemInfoA(hmenu, position, true, mii); + //System.out.println(position + " GetMenuItemInfo (" + result + ") : " + mii.cch + " " + mii.dwTypeData); + mii.fMask = Api.WinDefExt.MENUITEMINFO.MIIM_TYPE; + mii.fType = Api.WinDefExt.MENUITEMINFO.MFT_STRING; + mii.cch += 1; + mii.dwTypeData = "";//new String(new char[mii.cch]).replace("\0", " "); //buffer string with spaces + result = Api.User32.instance.GetMenuItemInfoA(hmenu, position, true, mii); + //System.out.println(position + " GetMenuItemInfo2 (" + result + ") Text: " + mii.dwTypeData + " " + mii.cch + " " + mii.wID); + //System.out.println("last error: " + Api.Kernel32.instance.GetLastError()); + return mii.dwTypeData; + */ + } + public Point getWindowPosition(HWND handle) { Point windowPoint = new Point(); RECT rect = new RECT(); diff --git a/src/org/synthuse/CommandPopupMenu.java b/src/org/synthuse/CommandPopupMenu.java index ca5d3bb..bc535a8 100644 --- a/src/org/synthuse/CommandPopupMenu.java +++ b/src/org/synthuse/CommandPopupMenu.java @@ -106,6 +106,9 @@ public class CommandPopupMenu extends JPopupMenu { CommandMenuItem mntmWindowSwitchToThis = new CommandMenuItem("windowSwitchToThis", 2); mnWinMessages.add(mntmWindowSwitchToThis); + + CommandMenuItem mntmSelectMenu = new CommandMenuItem("selectMenu", 2); + mnWinMessages.add(mntmSelectMenu); CommandMenuItem mntmSetcursorposition = new CommandMenuItem("setCursorPosition", 3); mnWinMessages.add(mntmSetcursorposition); diff --git a/src/org/synthuse/CommandProcessor.java b/src/org/synthuse/CommandProcessor.java index d443ad0..5317da5 100644 --- a/src/org/synthuse/CommandProcessor.java +++ b/src/org/synthuse/CommandProcessor.java @@ -203,6 +203,8 @@ public class CommandProcessor implements Runnable{ //Windows Api Commands if (command.equals("windowFocus")) return win.cmdWindowFocus(args); + if (command.equals("selectMenu")) + return win.cmdSelectMenu(args); if (command.equals("windowMinimize")) return win.cmdWindowMinimize(args); if (command.equals("windowMaximize")) diff --git a/src/org/synthuse/MenuInfo.java b/src/org/synthuse/MenuInfo.java new file mode 100644 index 0000000..ed5aeb7 --- /dev/null +++ b/src/org/synthuse/MenuInfo.java @@ -0,0 +1,85 @@ +package org.synthuse; + +import java.math.BigInteger; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.WinDef.HMENU; + +public class MenuInfo { + public HMENU hmenu = null; + public String hmenuStr = ""; + public String hwndStr = ""; + public int menuCount = 0; + public String text = ""; + public String unaltered = ""; + public int id = 0; + public int position = 0; + public boolean hasSubMenu = false; + public HMENU submenu = null; + public String submenuStr = ""; + public int submenuCount = 0; + + public MenuInfo(HMENU hmenu) { + loadMenuBase(hmenu); + } + + public MenuInfo(HMENU hmenu, int position) { + loadMenuBase(hmenu); + if (this.menuCount > 0) + loadMenuDetails(hmenu, position); + } + + public void loadMenuBase(HMENU hmenu) { + Api api = new Api(); + this.hmenu = hmenu; + this.hmenuStr = GetHandleMenuAsString(hmenu); + this.menuCount = api.user32.GetMenuItemCount(hmenu); + } + + public void loadMenuDetails(HMENU hmenu, int position) { + Api api = new Api(); + this.position = position; + this.unaltered = api.GetMenuItemText(hmenu, position); + this.text = unaltered; + if (this.unaltered.contains("\t")) + this.text = this.text.substring(0, this.text.indexOf("\t")); + this.text = text.replaceAll("[^a-zA-Z0-9.,\\+ ]", ""); + this.id = api.user32.GetMenuItemID(hmenu, position); + HMENU submenu = api.user32.GetSubMenu(hmenu, position); + if (submenu != null) { + int subCount = api.user32.GetMenuItemCount(submenu); + if (subCount > 0) { + this.hasSubMenu = true; + this.submenu = submenu; + this.submenuStr = GetHandleMenuAsString(submenu); + this.submenuCount = subCount; + } + } + + } + + public static String GetHandleMenuAsString(HMENU hmenu) { + if (hmenu == null) + return "0"; + //String longHexStr = hWnd.toString().substring("native@".length()); + //String longHexStr = hmenu.getPointer() + String longHexStr = hmenu.getPointer().toString().substring("native@0x".length()); + long l = new BigInteger(longHexStr, 16).longValue(); + return l + ""; + } + + public static HMENU GetHandleMenuFromString(String hmenu) { + if (hmenu == null) + return null; + if (hmenu.isEmpty()) + return null; + String cleanNumericHandle = hmenu.replaceAll("[^\\d.]", ""); + try { + return (new HMENU(new Pointer(Long.parseLong(cleanNumericHandle)))); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/org/synthuse/WindowInfo.java b/src/org/synthuse/WindowInfo.java index 08169bf..3a3b635 100644 --- a/src/org/synthuse/WindowInfo.java +++ b/src/org/synthuse/WindowInfo.java @@ -7,12 +7,19 @@ package org.synthuse; +import java.util.LinkedHashMap; +import java.util.Map; + import org.synthuse.Api.*; import com.sun.jna.Native; import com.sun.jna.Pointer; +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.RECT; +import com.sun.jna.platform.win32.WinDef.WPARAM; import com.sun.jna.ptr.PointerByReference; public class WindowInfo { @@ -33,6 +40,10 @@ public class WindowInfo { public Object xmlObj = null; public String framework = "win32";//default as win32 public String runtimeId = ""; + public int menus = 0; + public HMENU menu = null; + + public Map extra = null; //Default Win32 support public WindowInfo(HWND hWnd, boolean isChild) { @@ -42,7 +53,68 @@ public class WindowInfo { text = Native.toString(buffer); if (text.isEmpty()) text = new Api().sendWmGetText(hWnd); + if (text.isEmpty()) { + //System.out.println("getting toolbar text"); + } + //Get item count depending on what type of control it is + LRESULT tbCount = Api.User32.instance.SendMessage(hWnd, Api.TB_BUTTONCOUNT, new WPARAM(0), new LPARAM(0)); + if (tbCount.intValue() > 0) { // toolbar button count + //System.out.println("TB_BUTTONCOUNT: " + tbCount.intValue()); + if (extra == null) + extra = new LinkedHashMap(); + extra.put("tbCount", tbCount.intValue() + ""); + //Api.User32.instance.SendMessageA(hWnd, Api.TB_GETTOOLTIPS, 0, buffer); + //text = Native.toString(buffer); + } + LRESULT lvCount = Api.User32.instance.SendMessage(hWnd, Api.LVM_GETITEMCOUNT, new WPARAM(0), new LPARAM(0)); + if (lvCount.intValue() > 0) { // listview item count + if (extra == null) + extra = new LinkedHashMap(); + extra.put("lvCount", lvCount.intValue() + ""); + } + LRESULT lbCount = Api.User32.instance.SendMessage(hWnd, Api.LB_GETCOUNT, new WPARAM(0), new LPARAM(0)); + if (lbCount.intValue() > 0) { // listbox item count + if (extra == null) + extra = new LinkedHashMap(); + extra.put("lbCount", lbCount.intValue() + ""); + } + LRESULT cbCount = Api.User32.instance.SendMessage(hWnd, Api.CB_GETCOUNT, new WPARAM(0), new LPARAM(0)); + if (cbCount.intValue() > 0) { // listbox item count + if (extra == null) + extra = new LinkedHashMap(); + extra.put("cbCount", cbCount.intValue() + ""); + } + LRESULT tvCount = Api.User32.instance.SendMessage(hWnd, Api.TVM_GETCOUNT, new WPARAM(0), new LPARAM(0)); + if (tvCount.intValue() > 0) { //treeview node count + if (extra == null) + extra = new LinkedHashMap(); + extra.put("tvCount", tvCount.intValue() + ""); + } + //check if window has a menu + HMENU hmenu = Api.User32.instance.GetMenu(hWnd); + if (hmenu != null) { //menu item count + int menuCount = Api.User32.instance.GetMenuItemCount(hmenu); + if (menuCount > 0) { + this.menus = menuCount; + this.menu = hmenu; + } + } + + if (isChild) { + int ctrlID = Api.User32.instance.GetDlgCtrlID(hWnd); + if (ctrlID > 0){ + //parent = User32.instance.GetParent(hWnd); + int dtresult = Api.User32.instance.GetDlgItemText(hWnd, ctrlID, buffer, 1024); + if (dtresult > 0) { + String dgText = Native.toString(buffer); + if (extra == null) + extra = new LinkedHashMap(); + extra.put("dgText", dgText + ""); + } + } + } + char[] buffer2 = new char[1026]; User32.instance.GetClassName(hWnd, buffer2, 1026); className = Native.toString(buffer2); @@ -85,12 +157,41 @@ public class WindowInfo { public WindowInfo(String enumProperties, boolean isChild) { //WPF_PROPERTY_LIST = "RuntimeIdProperty,ParentRuntimeIdProperty,ProcessIdProperty,FrameworkIdProperty,ClassNameProperty,NameProperty"; String[] spltProperties = enumProperties.split(","); + this.isChild = isChild; + if (SynthuseDlg.config.isFilterWpfDisabled()) { //use wildcard mode + extra = new LinkedHashMap(); + for(String prop: spltProperties) { + String[] propertyNameAndValue = prop.split(":", 2); + if (propertyNameAndValue.length < 2) + continue; + + if (propertyNameAndValue[0].equals("RuntimeIdProperty")) + this.runtimeId = propertyNameAndValue[1]; + else if (propertyNameAndValue[0].equals("ParentRuntimeIdProperty")) + this.parentStr = propertyNameAndValue[1]; + else if (propertyNameAndValue[0].equals("ProcessIdProperty")) + this.pid = Long.parseLong(propertyNameAndValue[1]); + else if (propertyNameAndValue[0].equals("FrameworkIdProperty")) + this.framework = propertyNameAndValue[1]; + else if (propertyNameAndValue[0].equals("ClassNameProperty")) + this.className = propertyNameAndValue[1]; + else if (propertyNameAndValue[0].equals("NameProperty")) + this.text = propertyNameAndValue[1]; + else if (propertyNameAndValue[0].equals("ValueProperty")) + this.value = propertyNameAndValue[1]; + else{ + extra.put(propertyNameAndValue[0], propertyNameAndValue[1]); + } + } + this.hwndStr = this.runtimeId; + return; + } + // non-wildcard mode if (spltProperties.length > 0) this.runtimeId = spltProperties[0]; this.hwndStr = this.runtimeId; if (spltProperties.length > 1 && isChild) this.parentStr = spltProperties[1]; - this.isChild = isChild; if (spltProperties.length > 2) this.pid = Long.parseLong(spltProperties[2]); if (spltProperties.length > 3) diff --git a/src/org/synthuse/WindowsEnumeratedXml.java b/src/org/synthuse/WindowsEnumeratedXml.java index e86b786..e7588d9 100644 --- a/src/org/synthuse/WindowsEnumeratedXml.java +++ b/src/org/synthuse/WindowsEnumeratedXml.java @@ -39,6 +39,7 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.WinDef.HMENU; import com.sun.jna.platform.win32.WinUser; import com.sun.jna.platform.win32.WinDef.HWND; @@ -77,6 +78,7 @@ public class WindowsEnumeratedXml implements Runnable{ final List silverlightParentList = new ArrayList();//MicrosoftSilverlight int wpfCount = 0; int silverlightCount = 0; + int menuCount = 0; class ChildWindowCallback implements WinUser.WNDENUMPROC { @Override @@ -142,6 +144,7 @@ public class WindowsEnumeratedXml implements Runnable{ win = doc.createElement("wpf"); else if (w.framework.equals("Silverlight")) win = doc.createElement("silver"); + win.setAttribute("hwnd", w.hwndStr); win.setAttribute("text", w.text); if (w.value != "" && w.value != null) @@ -165,6 +168,18 @@ public class WindowsEnumeratedXml implements Runnable{ win.setAttribute("pid", w.pid+""); //else //win.setAttribute("parent", w.parent + ""); // not really needed since child node is append to parent node + if (w.extra != null) { + for(String extraName: w.extra.keySet()) { + win.setAttribute(extraName, w.extra.get(extraName)+""); + } + } + + if (w.menus > 0) { + win.setAttribute("menus", w.menus+""); + //String menuStr = MenuInfo.GetHandleMenuAsString(w.menu); + buildMenuXmlElements(doc, win, w.menu, w.hwndStr); + ++menuCount; + } if (w.isChild && infoList.containsKey(w.parentStr)) { childCount++; @@ -188,6 +203,7 @@ public class WindowsEnumeratedXml implements Runnable{ totals.setAttribute("wpfWrapperCount", wpfParentList.size()+""); totals.setAttribute("wpfCount", wpfCount+""); totals.setAttribute("silverlightCount", silverlightCount+""); + totals.setAttribute("menuCount", menuCount+""); totals.setAttribute("processCount", processList.size()+""); totals.setAttribute("updatedLast", new Timestamp((new Date()).getTime()) + ""); rootElement.appendChild(totals); @@ -201,6 +217,26 @@ public class WindowsEnumeratedXml implements Runnable{ return ""; } + public static Element buildMenuXmlElements(Document xmlDoc, Element xmlElement, HMENU targetMenu, String targetWin) + { + MenuInfo firstMi = new MenuInfo(targetMenu); + for (int i = 0 ; i < firstMi.menuCount ; i++ ) { + MenuInfo menuInfo = new MenuInfo(targetMenu, i); + Element menuElement = xmlDoc.createElement("menu"); + menuElement.setAttribute("unaltered", menuInfo.unaltered + ""); + menuElement.setAttribute("text", menuInfo.text + ""); + menuElement.setAttribute("id", menuInfo.id + ""); + menuElement.setAttribute("position", menuInfo.position + ""); + menuElement.setAttribute("hmenu", menuInfo.hmenuStr + ""); + menuElement.setAttribute("hwnd", targetWin + ""); + if (menuInfo.hasSubMenu) { + buildMenuXmlElements(xmlDoc, menuElement, menuInfo.submenu, targetWin); + } + xmlElement.appendChild(menuElement); + } + return xmlElement; + } + public static Map EnumerateWindowsWithWpfBridge(String parentHwndStr, String frameworkType) { final Map infoList = new LinkedHashMap(); WpfBridge wb = new WpfBridge(); @@ -212,7 +248,11 @@ public class WindowsEnumeratedXml implements Runnable{ //System.out.println("getRuntimeIdFromHandle"); String parentRuntimeId = wb.getRuntimeIdFromHandle(hwnd); //System.out.println("runtimeId=" + runtimeId); - String[] allIds = wb.enumDescendantWindowInfo(parentRuntimeId, WindowInfo.WPF_PROPERTY_LIST); + String[] allIds = null; + if (SynthuseDlg.config.isFilterWpfDisabled()) + allIds = wb.enumDescendantWindowInfo(parentRuntimeId, "*"); + else + allIds = wb.enumDescendantWindowInfo(parentRuntimeId, WindowInfo.WPF_PROPERTY_LIST); if (allIds == null) return infoList; //empty list //System.out.println("enumDescendantWindowIds " + allIds.length); diff --git a/src/org/synthuse/XpathManager.java b/src/org/synthuse/XpathManager.java index 1b00835..17f632f 100644 --- a/src/org/synthuse/XpathManager.java +++ b/src/org/synthuse/XpathManager.java @@ -100,7 +100,7 @@ public class XpathManager implements Runnable{ //builtXpath = "//*[@hwnd='" + runtimeId + "']"; //System.out.println("evaluateXpathGetValues: " + builtXpath); List wpfResultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, builtXpath); - if (wpfResultList.size() > 0) + if (wpfResultList.size() == 1) return builtXpath; builtXpath = "//*[@hwnd='" + runtimeId + "']"; wpfResultList = WindowsEnumeratedXml.evaluateXpathGetValues(xml, builtXpath); @@ -181,7 +181,12 @@ public class XpathManager implements Runnable{ if (targetText instanceof JTextPane) { final JTextPane target = (JTextPane)targetText; target.requestFocus(); - int cPos = target.getCaretPosition(); + int cPos = 0; + try { + cPos = target.getCaretPosition(); + } catch(Exception ex) { + //return 0;//something is throwing nullpointer exception + } if (alwaysFromTop) cPos = 0; int len = target.getStyledDocument().getLength(); diff --git a/src/org/synthuse/commands/BaseCommand.java b/src/org/synthuse/commands/BaseCommand.java index bf25ab5..958b119 100644 --- a/src/org/synthuse/commands/BaseCommand.java +++ b/src/org/synthuse/commands/BaseCommand.java @@ -129,6 +129,36 @@ public class BaseCommand { return result; } + public int findMenuIdWithXpath(String xpath) { + int result = 0; + double secondsFromLastUpdate = ((double)(System.nanoTime() - LAST_UPDATED_XML) / 1000000000); + if (secondsFromLastUpdate > CommandProcessor.XML_UPDATE_THRESHOLD) { //default 5 second threshold + WIN_XML = WindowsEnumeratedXml.getXml(); + LAST_UPDATED_XML = System.nanoTime(); + } + WindowsEnumeratedXml.evaluateXpathGetValues(WIN_XML, xpath); + String resultStr = ""; + List resultList = WindowsEnumeratedXml.evaluateXpathGetValues(WIN_XML, xpath); + for(String item: resultList) { + if (item.contains("hmenu=")) { + List list = WindowsEnumeratedXml.evaluateXpathGetValues(item, "//@id"); + if (list.size() > 0) + resultStr = list.get(0); //get first id; + } + else + resultStr = item; + break; + } + resultStr = resultStr.replaceAll("[^\\d.]", ""); //remove all non-numeric values + //System.out.println("findMenuIdWithXpath: " + resultStr); + if (resultStr.isEmpty()) + appendError("Error: Failed to find window handle matching: " + xpath); + else + result = Integer.parseInt(resultStr); + return result; + } + + public Point getCenterWindowPosition(WinPtr handle) { Point p = null; if (handle.isWin32()) diff --git a/src/org/synthuse/commands/WindowsCommands.java b/src/org/synthuse/commands/WindowsCommands.java index 5be8327..0a8b5eb 100644 --- a/src/org/synthuse/commands/WindowsCommands.java +++ b/src/org/synthuse/commands/WindowsCommands.java @@ -2,6 +2,9 @@ package org.synthuse.commands; import org.synthuse.*; +import com.sun.jna.platform.win32.WinDef.LPARAM; +import com.sun.jna.platform.win32.WinDef.WPARAM; + public class WindowsCommands extends BaseCommand { public WindowsCommands(CommandProcessor cp) { @@ -108,4 +111,17 @@ public class WindowsCommands extends BaseCommand { return ""; return api.sendWmGetText(handle.hWnd); } + + public boolean cmdSelectMenu(String[] args) { + if (!checkArgumentLength(args, 1)) + return false; + WinPtr handle = findHandleWithXpath(args[0]); + if (handle.isEmpty()) + return false; + int id = findMenuIdWithXpath(args[0]); + //LRESULT result = + //System.out.println("PostMessage to " + handle.hWndStr + " for id " + id); + api.user32.PostMessage(handle.hWnd, Api.WM_COMMAND, new WPARAM(id), new LPARAM(0)); + return true; + } } diff --git a/src/org/synthuse/test/UnitTestHelper.java b/src/org/synthuse/test/UnitTestHelper.java new file mode 100644 index 0000000..ce82523 --- /dev/null +++ b/src/org/synthuse/test/UnitTestHelper.java @@ -0,0 +1,79 @@ +package org.synthuse.test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +// This class doesn't contain Test, only some methods that are used by the unit tests +public class UnitTestHelper { + + static Process runningApp = null; + public static void RunApp(String ResourceFilePath) { + String tempFilename = ExtractFileFromJar(ResourceFilePath); + Runtime runtime = Runtime.getRuntime(); + try { + runningApp = runtime.exec(tempFilename); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void DestroyApp() { + if (runningApp != null) + runningApp.destroy(); + } + + public static String ExtractFileFromJar(String path) { + // Obtain filename from path + String[] parts = path.split("/"); + String filename = (parts.length > 1) ? parts[parts.length - 1] : null; + // Split filename to prexif and suffix (extension) + String prefix = ""; + String suffix = null; + if (filename != null) { + parts = filename.split("\\.", 2); + prefix = parts[0]; + suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; + } + File temp = null; + try { + // Prepare temporary file + temp = File.createTempFile(prefix, suffix); + temp.deleteOnExit(); + } catch(Exception e) { + e.printStackTrace(); + } + if (!temp.exists()) { //some reason the temp file wasn't create so abort + System.out.println("File " + temp.getAbsolutePath() + " does not exist."); + return null; + } + + // Prepare buffer for data copying + byte[] buffer = new byte[1024]; + int readBytes; + // Open and check input stream + InputStream is = WpfBridgeTest.class.getResourceAsStream(path); + if (is == null) { //check if valid + System.out.println("File " + path + " was not found inside JAR."); + return null; + } + // Open output stream and copy data between source file in JAR and the temporary file + OutputStream os = null; + try { + os = new FileOutputStream(temp); + while ((readBytes = is.read(buffer)) != -1) { + os.write(buffer, 0, readBytes); + } + os.close(); + is.close(); + } catch(Exception e) { + e.printStackTrace(); + } + return temp.getAbsolutePath(); + // Finally, load the library + //System.load(temp.getAbsolutePath()); + } + +} diff --git a/src/org/synthuse/test/WinApiTest.java b/src/org/synthuse/test/WinApiTest.java index 6b0d7d2..9384b4c 100644 --- a/src/org/synthuse/test/WinApiTest.java +++ b/src/org/synthuse/test/WinApiTest.java @@ -20,11 +20,13 @@ import org.synthuse.Api.User32; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.WinUser; +import com.sun.jna.platform.win32.WinDef.HMENU; import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.platform.win32.WinDef.RECT; import com.sun.jna.ptr.PointerByReference; public class WinApiTest { + Api api = new Api(); @Before public void setUp() throws Exception { @@ -37,7 +39,7 @@ public class WinApiTest { } public static void output(String val) { - System.out.println(val); + //System.out.println(val); } //copied and modified slightly from WindowInfo class @@ -50,6 +52,8 @@ public class WinApiTest { String className = ""; String processName = ""; long pid = 0; + + Map extra = null; byte[] buffer = new byte[1024]; output("Calling GetWindowTextA"); @@ -68,6 +72,32 @@ public class WinApiTest { className = Native.toString(buffer2); output("GetClassName returned: " + className); + HMENU hmenu = Api.User32.instance.GetMenu(hWnd); + //hmenu = Api.User32.instance.GetSubMenu(hmenu, 0); + if (hmenu != null) { //menu item count + int menuCount = Api.User32.instance.GetMenuItemCount(hmenu); + if (menuCount > 0) { + if (extra == null) + extra = new LinkedHashMap(); + extra.put("menuCount", menuCount + ""); + System.out.println("className: " + className); + System.out.println("text: " + text); + System.out.println("menuCount: " + menuCount); + + for (int m = 0 ; m < menuCount ; m++) { + String menuTxt = api.GetMenuItemText(hmenu, m); + System.out.println("Menu Text: " + menuTxt); + } + /* + if (menuCount == 5) { + HMENU smenu = Api.User32.instance.GetSubMenu(hmenu, 0); + boolean result = Api.User32.instance.TrackPopupMenu(smenu, 0, 1, 1, 0, hWnd, 0); + System.out.println("TrackPopupMenu: " + result); + System.out.println("last error: " + Api.Kernel32.instance.GetLastError()); + }*/ + } + } + rect = new RECT(); output("Calling GetWindowRect"); User32.instance.GetWindowRect(hWnd, rect); diff --git a/src/org/synthuse/test/WpfBridgeTest.java b/src/org/synthuse/test/WpfBridgeTest.java index be15446..9749950 100644 --- a/src/org/synthuse/test/WpfBridgeTest.java +++ b/src/org/synthuse/test/WpfBridgeTest.java @@ -1,12 +1,6 @@ package org.synthuse.test; import static org.junit.Assert.*; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -16,28 +10,17 @@ import org.synthuse.*; public class WpfBridgeTest { - static Process mockApp = null; - public static void RunMockWpfApp() { - String tempFilename = ExtractFileFromJar("/org/synthuse/test/WpfMockTestApp.exe"); - Runtime runtime = Runtime.getRuntime(); - try { - mockApp = runtime.exec(tempFilename); - } catch (IOException e) { - e.printStackTrace(); - } - } @BeforeClass public static void setUpBeforeClass() { //this runs only once for this class - RunMockWpfApp(); + UnitTestHelper.RunApp("/org/synthuse/test/WpfMockTestApp.exe"); } @AfterClass public static void tearDownAfterClass() { //this runs only once for this class - if (mockApp != null) - mockApp.destroy(); + UnitTestHelper.DestroyApp(); } @Before @@ -139,54 +122,4 @@ public class WpfBridgeTest { System.out.println(w); } - public static String ExtractFileFromJar(String path) { - // Obtain filename from path - String[] parts = path.split("/"); - String filename = (parts.length > 1) ? parts[parts.length - 1] : null; - // Split filename to prexif and suffix (extension) - String prefix = ""; - String suffix = null; - if (filename != null) { - parts = filename.split("\\.", 2); - prefix = parts[0]; - suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; - } - File temp = null; - try { - // Prepare temporary file - temp = File.createTempFile(prefix, suffix); - temp.deleteOnExit(); - } catch(Exception e) { - e.printStackTrace(); - } - if (!temp.exists()) { //some reason the temp file wasn't create so abort - System.out.println("File " + temp.getAbsolutePath() + " does not exist."); - return null; - } - - // Prepare buffer for data copying - byte[] buffer = new byte[1024]; - int readBytes; - // Open and check input stream - InputStream is = WpfBridgeTest.class.getResourceAsStream(path); - if (is == null) { //check if valid - System.out.println("File " + path + " was not found inside JAR."); - return null; - } - // Open output stream and copy data between source file in JAR and the temporary file - OutputStream os = null; - try { - os = new FileOutputStream(temp); - while ((readBytes = is.read(buffer)) != -1) { - os.write(buffer, 0, readBytes); - } - os.close(); - is.close(); - } catch(Exception e) { - e.printStackTrace(); - } - return temp.getAbsolutePath(); - // Finally, load the library - //System.load(temp.getAbsolutePath()); - } }