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 4dd0ad1..b6dc92c 100644 Binary files a/native/WpfBridge/bin/wpfbridge32.dll and b/native/WpfBridge/bin/wpfbridge32.dll differ diff --git a/native/WpfBridge/bin/wpfbridge64.dll b/native/WpfBridge/bin/wpfbridge64.dll index 4570fa3..29affed 100644 Binary files a/native/WpfBridge/bin/wpfbridge64.dll and b/native/WpfBridge/bin/wpfbridge64.dll differ diff --git a/src/org/synthuse/Api.java b/src/org/synthuse/Api.java index 4ab1a96..f9d93ff 100644 --- a/src/org/synthuse/Api.java +++ b/src/org/synthuse/Api.java @@ -8,18 +8,17 @@ package org.synthuse; import java.awt.Point; +import java.util.Arrays; +import java.util.List; import com.sun.jna.Native; import com.sun.jna.Pointer; -import com.sun.jna.platform.win32.WinDef.HDC; -import com.sun.jna.platform.win32.WinDef.HPEN; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinDef.*; +import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR; +import com.sun.jna.platform.win32.WinDef; import com.sun.jna.platform.win32.WinNT.HANDLE; import com.sun.jna.platform.win32.WinUser; -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.platform.win32.WinNT.LARGE_INTEGER; import com.sun.jna.platform.win32.WinUser.WNDENUMPROC; import com.sun.jna.ptr.PointerByReference; @@ -27,6 +26,8 @@ import com.sun.jna.win32.W32APIOptions; public class Api { + // Constants + public static int WM_SETTEXT = 0x000c; public static int WM_GETTEXT = 0x000D; public static int WM_GETTEXTLENGTH = 0x000E; @@ -84,6 +85,24 @@ public class Api { public static int RDW_INVALIDATE = 0x0001; public static int RDW_UPDATENOW = 0x0100; public static int RDW_ALLCHILDREN = 0x0080; + + public static int TB_GETBUTTONTEXTA = (0x0400 + 45); + public static int TB_GETBUTTONTEXTW = (0x0400 + 75); + public static int TB_GETRECT = (0x0400 + 51); + public static int TB_GETTOOLTIPS = (0x0400 + 35); + public static int TB_BUTTONCOUNT = 0x0418; + + public static int LVM_FIRST = 0x1000; + public static int LVM_GETITEMCOUNT = LVM_FIRST + 4; + public static int LVM_GETITEM = LVM_FIRST + 75; + public static int LVIF_TEXT = 0x0001; + + public static int LB_GETCOUNT = 0x18B; + + public static int CB_GETCOUNT = 0x146; + + public static int TV_FIRST = 0x1100; + public static int TVM_GETCOUNT = TV_FIRST + 5; public static int VK_SHIFT = 16; public static int VK_LSHIFT = 0xA0; @@ -95,6 +114,8 @@ public class Api { public static int VK_LMENU = 0xA4; public static int VK_RMENU = 0xA5; + public static int WM_COMMAND = 0x111; + public static int CWP_ALL = 0x0000; // Does not skip any child windows public User32 user32; @@ -109,7 +130,60 @@ public class Api { public static final int POINT_X(long i) { return (int) (i & 0xFFFF); - } + } + + public interface WinDefExt extends WinDef { + //Structures + public class MENUITEMINFO extends Structure { + public static final int MFS_CHECKED = 0x00000008; + public static final int MFS_DEFAULT = 0x00001000; + public static final int MFS_DISABLED = 0x00000003; + public static final int MFS_ENABLED = 0x00000000; + public static final int MFS_GRAYED = 0x00000003; + public static final int MFS_HILITE = 0x00000080; + public static final int MFS_UNCHECKED = 0x00000000; + public static final int MFS_UNHILITE = 0x00000000; + public static final int MFT_STRING = 0x0000; + public static final int MIIM_DATA = 0x00000020; + public static final int MIIM_STRING = 0x0040; + public static final int MIIM_SUBMENU = 0x0004; + public static final int MIIM_TYPE = 0x0010; + + public static class ByValue extends MENUITEMINFO implements Structure.ByValue { + } + + public static class ByReference extends MENUITEMINFO implements Structure.ByReference { + } + + public MENUITEMINFO() { + cbSize = size(); + } + + public MENUITEMINFO(Pointer p) { + super(p); + } + + @Override + protected List 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()); - } }