PlanetSquires Forums

Support Forums => José Roca Software => Topic started by: José Roca on June 07, 2017, 12:10:48 AM

Title: FBWinSpy - Preliminary work
Post by: José Roca on June 07, 2017, 12:10:48 AM
Inspired by the tools PBWinSpy by Börje Hagsten and WinSpy, I have done some preliminary work for FBWinSpy.

It extracts information about a GUI and its child controls. This information could be used to attempt to reconstruct the GUI generating code for FreeBasic. As an example, if the GUI has a menu, it generates code to reconstruct it.
Title: Re: FBWinSpy - Preliminary work
Post by: Johan Klassen on June 07, 2017, 04:59:02 AM
thank you  :)
Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 07, 2017, 02:12:30 PM
:-)
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 08, 2017, 02:12:06 AM
There is an undocumented function to retrieve the DPI awareness of an application, but requires windows 8.1+


' ========================================================================================
' Get process DPI awareness (undocumented)
' Untested. Requires Windows 8.1+
' ========================================================================================
FUNCTION GetProcessDpiAwarenessInternal (BYVAL hProcess AS HANDLE, BYVAL pValue AS ULONG PTR) AS BOOLEAN
   DIM pLib AS ANY PTR = DyLibLoad("user32.dll")
   IF pLib = NULL THEN EXIT FUNCTION
   DIM pGetProcessDpiAwarenessInternal AS FUNCTION (BYVAL hProcess AS HANDLE, BYVAL pValue AS ULONG PTR) AS LONG
   pGetProcessDpiAwarenessInternal = DyLibSymbol(pLib, "GetProcessDpiAwarenessInternal")
   IF pGetProcessDpiAwarenessInternal THEN FUNCTION = pGetProcessDpiAwarenessInternal(hProcess, pValue)
   DyLibFree(pLib)
END FUNCTION
' ========================================================================================

Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 08, 2017, 02:18:22 AM
I have added more information. Unfortunately, although I can retrieve the number of parts of a status bar of another application, I can't retrieve the size of the parts. Also limitations with other controls such Toolbars and Rebars.


' ========================================================================================
' Retrieve child windows
' ========================================================================================
FUNCTION EnumChildProc (BYVAL hwnd AS HWND, BYVAL lParam AS LPARAM) AS LONG

   DIM wszClassName AS WSTRING * 260 = AfxGetWindowClassName(hwnd)
   DIM cwsText AS CWSTR = wszClassName
   cwsText += "; cID = " & WSTR(GetDlgCtrlID(hwnd))
   cwsText += "; Width = " & WSTR(AfxGetWindowWidth(hwnd))
   cwsText += "; Height = " & WSTR(AfxGetWindowHeight(hwnd))
   cwsText += "; Style = &h" & HEX(AfxGetWindowStyle(hwnd))
   cwsText += "; StyleEx = &h" & HEX(AfxGetWindowExStyle(hwnd))
   cwsText += CHR(13, 10)

   SELECT CASE UCASE(wszClassName)
      CASE "STATIC"
         cwsText += "   - Styles: " & GetStaticStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "BUTTON"
         cwsText += "   - Styles: " & GetButtonStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "TOOLBARWINDOW32"
         cwsText += "   - Styles: " & GetToolbarStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - ExStyles: " & GetToolbarExStyles(Toolbar_GetExtendedStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Buttons: " & WSTR(Toolbar_ButtonCount(hwnd))
         cwsText += "; Width: " & WSTR(Toolbar_GetButtonWidth(hwnd))
         cwsText += "; Height: " & WSTR(Toolbar_GetButtonHeight(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "COMBOBOX"
         cwsText += "   - Styles: " & GetComboBoxStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "LISTBOX"
         cwsText += "   - Styles: " & GetListBoxStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "REBARWINDOW32"
         cwsText += "   - Styles: " & GetRebarStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "EDIT"
         cwsText += "   - Styles: " & GetEditStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "MSCTLS_PROGRESS32"
         cwsText += "   - Styles: " & GetProgressBarStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "MSCTLS_STATUSBAR32"
         cwsText += "   - Styles: " & GetStatusBarStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         DIM nParts AS LONG = StatusBar_GetPartsCount(hwnd)
         cwsText += "   - Parts: " & WSTR(nParts) & "; Text part 1: " & **AfxGetWindowText(hwnd) & CHR(13, 10)
      CASE "SYSTABCONTROL32"
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - ExStyles: " & GetSysTabExStyles(TabCtrl_GetExtendedStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "SYSHEADER32"
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "SYSTREEVIEW32"
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "SYSLISTVIEW32"
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Styles: " & GetListViewStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - ExStyles: " & GetSysTabExStyles(ListView_GetExtendedListViewStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE "RICHED20", "RICHEDIT20A", "RICHEDIT20W", "RICHEDIT", "RICHEDIT50A", "RICHEDIT50W"
         cwsText += "   - Styles: " & GetRichEditStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
      CASE ELSE
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
   END SELECT

   DIM pcws AS CWSTR PTR = cast(CWSTR PTR, lParam)
   IF pcws THEN pcws->Add(cwsText)

   FUNCTION = CTRUE

END FUNCTION
' ========================================================================================

Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 08, 2017, 01:24:40 PM
Hola Jose,

About the StatusBar, what if you try with VirtualAllocEx(), ReadProcessMemory(), and friends...

Pierre


Dim ByteCount          AS SIZE_T_ Ptr
Dim pStatusBarRect     AS LPCVOID
Dim StatusBarRect      AS RECT
Dim hStatusBar         AS HANDLE
Dim hProcess           AS HANDLE
Dim hThread            AS DWORD
Dim pid                AS DWORD
Dim StatusBarPartId    AS LONG
Dim StatusBarPartCount AS LONG

hStatusBar         = The status bar handle
StatusBarPartCount = SendMessage(hStatusBar, SB_GETPARTS, 0, 0)
StatusBarPartId    = StatusBarPartCount - 1 'Zero based
hThread            = GetWindowThreadProcessId(hStatusBar, @pid)
hProcess           = OpenProcess(PROCESS_VM_OPERATION OR PROCESS_VM_READ OR PROCESS_VM_WRITE OR _
                                  PROCESS_QUERY_INFORMATION, FALSE, pid)
pStatusBarRect     = VirtualAllocEx(BYVAL hProcess, BYVAL 0, SIZEOF(StatusBarRect), _
                                     BYVAL MEM_COMMIT, BYVAL PAGE_READWRITE)
SendMessage(hStatusBar, SB_GETRECT, StatusBarPartId, Cast(lParam, pStatusBarRect))
ReadProcessMemory(hProcess, pStatusBarRect, BYVAL VARPTR(StatusBarRect), BYVAL SIZEOF(StatusBarRect), ByteCount)
VirtualFreeEx(hProcess, @pStatusBarRect, 0, MEM_RELEASE)
CloseHandle(hProcess)
MessageBox(HWND_DESKTOP, _
            !"StatusBarPartCount \t"    & STR(StatusBarPartCount) & _
            !"\nStatusBarPartId  \t"    & STR(StatusBarPartId)    & _
            !"\nStatusBarRect.Left \t"  & STR(StatusBarRect.Left) & _
            !"\nStatusBarRect.Right \t" & STR(StatusBarRect.Right), _
            !"StatusBarRect", MB_OK OR MB_TOPMOST)

Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 08, 2017, 05:35:47 PM
Thanks very much, Pierre. I was looking at OpenProcess, but I was missing the VirtualAllocEx, ReadProcessMemory friends. I remember now to have read code that used them many years ago, but could not remember it. To fix knowledge about rarely used techniques is one of the reasons of what I write so many wrappers. Like this one, that I never have needed to use until now:


' ========================================================================================
' Retrieves the path of the executable file that created the specified window.
' ========================================================================================
PRIVATE FUNCTION AfxGetPathFromWindowHandle (BYVAL hwnd AS HWND) AS CWSTR
   DIM idProc AS DWORD, hProcess AS HANDLE, wszPath AS WSTRING * MAX_PATH
   GetWindowThreadProcessId(hwnd, @idProc)
   IF idProc THEN
      hProcess = OpenProcess(PROCESS_QUERY_INFORMATION OR PROCESS_VM_READ, FALSE, idProc)
      IF hProcess THEN
         GetModuleFileNameExW(hProcess, NULL, wszPath, SIZEOF(wszPath))
         CloseHandle(hProcess)
         RETURN wszPath
      END IF
   END IF
END FUNCTION
' ========================================================================================


I'm using it in FBWinSpy to detect if the application is 32 or 64 bit:


         ' // ------ Get the binary type ------------
         wszExePath = AfxGetPathFromWindowHandle(_hwnd)
         cwsText += wszExePath & CHR(13, 10)
         DIM nType AS DWORD
         GetBinaryTypeW(@wszExePath, @nType)
         DIM wszType AS WSTRING * 260
         IF nType = 0 THEN
            wszType = " (32 bit)"
         ELSEIF nType = 6 THEN
            wszType = " (64 bit)"
         END IF
         cwsText += "Binary type: " & WSTR(nType) & wszType & CHR(13, 10)


Coming back to the status bar, this function does the job:


' ========================================================================================
' Retrieves the bounding rectangle of a part in a status window.
' Parameters:
' - hStatusBar = Handle of the status bar control. It can belong to another application.
' - nPart = Zero-based index of the part whose bounding rectangle is to be retrieved.
' Return value: A RECT structure with the bounding rectangle.
' ========================================================================================
PRIVATE FUNCTION AfxGetStatusBarRect (BYVAL hStatusBar AS HWND, BYVAL nPart AS LONG) AS RECT
   DIM idProc AS DWORD, hProcess AS HANDLE, pSbRect AS ANY PTR, rc AS RECT, cbBytesRead AS SIZE_T
   GetWindowThreadProcessId(hStatusBar, @idProc)
   IF idProc = 0 THEN EXIT FUNCTION
   hProcess = OpenProcess(PROCESS_VM_OPERATION OR PROCESS_VM_READ OR PROCESS_VM_WRITE OR _
              PROCESS_QUERY_INFORMATION, FALSE, idProc)
   IF hProcess = NULL THEN EXIT FUNCTION
   pSbRect = VirtualAllocEx(hProcess, NULL, SIZEOF(RECT), MEM_COMMIT, PAGE_READWRITE)
   IF pSbRect THEN
      SendMessageW(hStatusBar, SB_GETRECT, nPart, cast(LPARAM, pSbRect))
      ReadProcessMemory(hProcess, pSbRect, @rc, SIZEOF(rc), @cbBytesRead)
      VirtualFreeEx(hProcess, pSbRect, 0, MEM_RELEASE)
   END IF
   CloseHandle(hProcess)
   FUNCTION = rc
END FUNCTION
' ========================================================================================


Being used as:


      CASE "MSCTLS_STATUSBAR32"
         cwsText += "   - Styles: " & GetStatusBarStyles(AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         cwsText += "   - Window styles: " & GetWindowStyles(hwnd, AfxGetWindowStyle(hwnd)) & CHR(13, 10)
         DIM rc AS RECT = AfxGetWindowRect(hwnd)
         cwsText += "   - Window Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         rc = AfxGetWindowClientRect(hwnd)
         cwsText += "   - Client Rect: Left = " & WSTR(rc.Left) & "; Right = " & WSTR(rc.Right) & "; Top = " & WSTR(rc.Top) & "; Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         DIM nParts AS LONG = StatusBar_GetPartsCount(hwnd)
         cwsText += "   - Parts: " & WSTR(nParts) & "; Text part 1: " & **AfxGetWindowText(hwnd) & CHR(13, 10)
         FOR i AS LONG = 0 to nParts - 1
            rc = AfxGetStatusBarRect(hwnd, i)
            cwsText += "   - Part " & WSTR(i) &": Left = " & WSTR(rc.Left) & ", Right = " & WSTR(rc.Right) & ", Top = " & WSTR(rc.Top) & ", Bottom = " & WSTR(rc.Bottom) & CHR(13, 10)
         NEXT

Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 08, 2017, 09:08:37 PM
Looks like fbWinSpy is coming great.  :-)

I don't think it is usefull in the current scope, but if somebody ever need an extended GetBinaryType() that do more than exe,
then "Checking for 32/64 bit exe, dll, cpl, scr... files" (https://forum.powerbasic.com/forum/user-to-user-discussions/powerbasic-for-windows/55621-files-or-dlls-are-they-16bit-32bit-64bit) might help,
or easier yet for non critial code,
the unDocumented Ole32.dll's CoGetModuleType(wFileName, dwModuleType) (http://undoc.airesoft.co.uk/ole32.dll/CoGetModuleType.php) who also does check the FileHeader.

Pierre
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 08, 2017, 10:17:38 PM
In fact, using the C aliases for data types, we don't even need to know if the application is 32 or 64 bit, because the goal is to generate code that compiles to 32 or 64 bit without changes.

For example, in the CWindows class, instead of assigning 4 or 8 bytes to the cbWndExtra member, I'm using SIZEOF(HANDLE). If it is 32 bit, SIZEOF(HANDLE) will return 4, and if it is 64 bit, it will return 8.


   ' // Fill the WNDCLASSEXW structure
   WITH wcexw
      .cbSize        = SIZEOF(wcexw)
      .style         = CS_DBLCLKS OR CS_HREDRAW OR CS_VREDRAW
      .lpfnWndProc   = lpfnWndProc
      .cbClsExtra    = 0
      .cbWndExtra    = SIZEOF(HANDLE)
      .hInstance     = hInstance
      .hCursor       = ..LoadCursorW(NULL, CAST(LPCWSTR, IDC_ARROW))
      .hbrBackground = CAST(HBRUSH, COLOR_3DFACE + 1)
      .lpszMenuName  = NULL
      .lpszClassName = @m_wszClassName
      .hIcon         = 0
      .hIconSm       = 0
   END WITH


I have noticed your use of BYVAL when passing some parameters in calls to Windows API functions. The FB headers always use BYVAL (there is not a single BYREF in any of the declares), so you won't ever need to use the BYVAL override when calling them (a different matter are my wrappers, in which I often use BYREF). What you will need a lot is to use VARPTR or @, and also CAST.

BTW here is the WINE code for GetBinary Type:
https://github.com/wine-mirror/wine/blob/master/dlls/kernel32/module.c

I often check the WINE source code to see what the Windows API functions do under the hood.
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 09, 2017, 02:13:32 AM
Files reuploaded in the first post.

Added code to retrieve information about the toolbar buttons.

Added a check to ascertain if the form handle is the same that the main window before getting the menu information. The form handle could be a popup dialog or a tab page.
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 09, 2017, 02:14:55 PM
I'm thinking that the best approach could be to use a TreeView to classify all the information retrieved, as I do in my TypeLib Browser. Then, this information can be parsed to generate code.
Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 09, 2017, 02:31:50 PM
Yep, about the ByVal you are right, code was a ported from PB
and I'm often lazy and sloppy so I posted without good cleaning.

I never did pay much attention to Wine, a second look made it more interesting, thank.

Now time to look at the new fbWinSpy, ...it will be much easier to port code.

Pierre
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 09, 2017, 03:44:27 PM
> I never did pay much attention to Wine, a second look made it more interesting, thank.

I don't use Wine, but sometimes I look at the source code to know what and how the Windows API functions do.
Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 09, 2017, 07:34:32 PM
If you don't have already a function to retrieve controls handle that are inside a frame then, J. Brown wrote WindowFromPointEx.c (http://read.pudn.com/downloads14/sourcecode/windows/system/55084/winspy+SCR/scr/WindowFromPointEx.c__.htm).
Even if it's not really needed in the present case,  I find it useful in many occasions. It is working pretty well.
Here is a fb adaptation, and some pb code with a couple of frames and controls to show what I mean...
For the fun of it, I did replace WindowFromPoint(pt) with WindowFromPointEx(pt) in fbWinSpy.

Pierre


' ========================================================================================
' WindowFromPointEx procedure
' ========================================================================================
Function WindowFromPointEx(CursorPosDesktop AS POINT) AS HANDLE
'Thank to J. Brown @ http://read.pudn.com/downloads14/sourcecode/windows/system/55084/winspy+SCR/scr/WindowFromPointEx.c__.htm

'J. Brown says...
'The problem:
' WindowFromPoint API is not very good. It cannot cope
' with odd window arrangements, i.e. a group-box in a dialog
' may contain a few check-boxes. These check-boxes are not
' children of the groupbox, but are at the same "level" in the
' window-hierachy. WindowFromPoint will just return the
' first available window it finds which encompasses the mouse
' (i.e. the group-box), but will NOT be able to detect the contents.

'The solution:
' We use WindowFromPoint to start us off, and Then step back one
' level (i.e. from the parent of what WindowFromPoint returned).
' Once we have this window, we enumerate ALL children of this window
' ourselves, and find the one that best fits under the mouse -
' the smallest window that fits, in fact.
' I've tested this on alot of dIfferent apps, and it seems
' to work flawlessly - in fact, I havn't found a situation yet
' that this method doesn't work on.....we'll see!

'J. Brown -

Dim hFromPoint  AS HANDLE
Dim hParent     AS HANDLE
Dim hControlTry AS HANDLE
Dim CtrlRect    AS RECT
Dim Area        AS LONG
Dim AreaPrev    AS LONG

hFromPoint = WindowFromPoint(CursorPosDesktop)
Function   = hFromPoint
hParent    = GetParent(hFromPoint)
If hParent Then
   hControlTry = GetWindow(hParent, GW_Child)      'Get first child
   Do While hControlTry                            'Enumerate all child including ourselve
     GetWindowRect(hControlTry, @CtrlRect)         'Get child's rect
     If PtInRect(@CtrlRect, CursorPosDesktop) Then 'Is mouse on control
       Area = (CtrlRect.Right - CtrlRect.Left) * (CtrlRect.Bottom - CtrlRect.Top) 'Calculate area
       If AreaPrev Then 'This is another control under same mouse position, like FRAME or BUTTON
         If Area < AreaPrev Then 'If it's smaller Then we want it
           If isWindowVisible(hControlTry) Then
             Function = hControlTry
           End If
         End If
       End If
       AreaPrev = Area 'Memorise for next loop, If any
     End If
     hControlTry = GetWindow(hControlTry, GW_HWNDNEXT)
   Loop
End If

END Function
' ========================================================================================


Dialog with frames and other controls

#COMPILE EXE '#Win 9.07#
#DIM ALL
#INCLUDE "Win32Api.inc"
'#INCLUDE "Commctrl.inc"

%FrameA = 101
%FrameB = 102
%Static = 201
%Edit   = 202
%Button = 203
'_____________________________________________________________________________

CALLBACK FUNCTION MainProc()

SELECT CASE CBMSG
   CASE %WM_COMMAND
     SELECT CASE CBCTL
       CASE %Button
         IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
           WinBeep(1500, 100)
         END IF
     END SELECT
END SELECT

END FUNCTION
'_____________________________________________________________________________

FUNCTION PBMAIN()
LOCAL RetVal AS LONG
LOCAL hDlg   AS DWORD

DIALOG NEW %HWND_DESKTOP, "FrameInFrame", , , 200, 200, _
%WS_CAPTION OR %WS_MINIMIZEBOX OR %WS_SYSMENU, 0 TO hDlg

CONTROL ADD FRAME, hDlg, %FrameA, "Frame 101", 5, 5, 190, 190
CONTROL ADD LABEL, hDlg, %Static, "Label 201", 25, 25, 150, 15, %SS_CENTER
CONTROL ADD TEXTBOX, hDlg, %Edit, "TextBox 202", 35, 60, 130, 15
CONTROL ADD FRAME, hDlg, %FrameB, "Frame 102", 25, 100, 150, 80
CONTROL ADD BUTTON, hDlg, %Button, "Button 203", 70, 135, 60, 15

DIALOG SHOW MODAL hDlg, CALL MainProc TO RetVal

END FUNCTION
'_____________________________________________________________________________
'


Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 10, 2017, 05:49:42 AM
Thanks very much, Pierre, it has been providential. I was pulling my hair because in my CSED editor I have popup windows with group boxes in which the controls are not children of the group box, but of the popup window, and it was not returning the selected control.

I have made a GUI. You can select the main window, a popup window with controls or an individual contros (with or without child controls) and it displays the retrieved information in a Treeview. Next step will be to retrieve and display information about the child controls.

New files reuploaded in the first post.
Title: Re: FBWinSpy - Preliminary work
Post by: James Fuller on June 10, 2017, 10:05:59 AM
Jose,
  I have investigated this type of app previously and found not knowing the font name, size of the Window/Dialog/Control places them quite low on my must have utilities list.
Have you found (or created) an api that will give you this info?

James

Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 10, 2017, 10:17:50 AM
As the title of the thread says, it is a preliminary work. The code to retrieve and display information about the controls has not been yet implemented. I have posted it because I knew that Pierre would be interested. The others will have to wait.
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 10, 2017, 08:04:17 PM
Quote from: James Fuller on June 10, 2017, 10:05:59 AM
Jose,
  I have investigated this type of app previously and found not knowing the font name, size of the Window/Dialog/Control places them quite low on my must have utilities list.
Have you found (or created) an api that will give you this info?

James

To get the size of a window is a piece of cake.

Regarding the font, there is not guarantee that this info can be obtainable, except for Windows standard controls. I will display it if the info is retrievable.

This function retrieves the font. See Remaks.


' ========================================================================================
' Retrieves the font used by the specified window.
' Parameters:
' - hwnd = Handle to the window.
' - lplfw = Pointer to a LOGFONTW stucture.
' Return value:
'   If the function succeeds, and lplfw is a valid pointer, the return value is the number
'   of bytes stored into the buffer.
'   If the function succeeds, and lplfw is NULL, the return value is the number of bytes
'   required to hold the information the function would store into the buffer.
'   If the function fails, the return value is zero.
' Remarks:
'    WM_GETFONT works fine cross-process, provided that the window you're sending it to
'    responds to this message. This is only guaranteed for standard Windows controls. If the
'    standard Windows control is using the system font, it returns NULL.
'    The WM_GETFONT message does not return a font handle if the message is sent to a dialog
'    box created by the DialogBoxParam, DialogBoxIndirectParam, CreateDialogParam, or
'    CreateDialogIndirectParam function.
'    A window can use an arbitrary font for rendering, or even multiple fonts.
' ========================================================================================
FUNCTION FBWS_GetFont (BYVAL hwnd AS HWND, BYVAL lplfw AS LOGFONTW PTR) AS LONG
   IF IsWindow(hwnd) = FALSE THEN EXIT FUNCTION
   DIM hFont AS HFONT = CAST(HFONT, SendMessageW(hwnd, WM_GETFONT, 0, 0))
   IF hFont = 0 THEN EXIT FUNCTION
   FUNCTION = GetObjectW(hFont, SIZEOF(LOGFONTW), lplfw)
END FUNCTION
' ========================================================================================


Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 11, 2017, 12:29:03 AM
Added information of the controls, including font name and point size if available.

Code reuploaded in the first post.
Title: Re: FBWinSpy - Preliminary work
Post by: James Fuller on June 11, 2017, 08:13:31 AM
Jose,
  Thank you for the confirmation.
Now to be any use for DDT -> SDK (CWindow) you will need to add functionality to query the user for the font name and pont size for the Dialog and controls.
I had visions of doing this but lost interest.

James
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 11, 2017, 09:50:47 AM
It already retrieves the font of the controls.

To retrieve the font used by the dialog, it's simply a matter of adding


   ' // Get the font, if available
   DIM lfw AS LOGFONTW
   IF FBWS_GetFont(hwndMain, @lfw) THEN
      TreeView_AddItem(hTreeView, __hMainWindowNode, NULL, "Font mame: " & lfw.lfFaceName)
      TreeView_AddItem(hTreeView, __hMainWindowNode, NULL, "Font point size: " & AfxGetFontPointSize(lfw.lfHeight))
   END IF


I have checked it and DDT dialogs respond to the WM_GETFONT message.
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 11, 2017, 09:56:50 AM
BTW I don't have any interest in translating DDT code to SDK. I'm allergic to dialogs in general and to DDT in particular.

At most, we could translate the GUI, but what is going to do with this the typical DDTer, without  much knowledge of SDK code (Pierre is an exception), with the rest of DDT code?
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 11, 2017, 03:22:04 PM
Added menu information and other changes.

Code reuploaded in the first post.
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 12, 2017, 07:44:04 AM
Added information about rebars and other changes.

Code reuploaded in the first post.
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 12, 2017, 11:03:00 AM
Now it searches recursively if a child window or control (e.g. a tab control) has also children and displays full information.

Code reuploaded in the first post.
Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 15, 2017, 11:54:02 PM
Hey Jose,

On my machine, using Control-A in the "Code TAB" edit control will beep.
Also Control-E is assigned to "è" on my keyboard making the character to be inserted.
All seem's fine if I use the modified code, except that, of course, the code for "è" is not a valid solution

Is Control-E is standard for selection in Spanish? 

Pierre

' ========================================================================================
' Code text box window procedure
' ========================================================================================
FUNCTION FBWS_Edit_CodeView_WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

   SELECT CASE uMsg

#If 0 'Genuine Jose code
      CASE WM_KEYDOWN
         ' // If Ctrl+A or Ctrl+E pressed, select all the text
         IF wParam = VK_A OR wParam = VK_E THEN
            IF GetAsyncKeyState(VK_CONTROL) THEN
               PostMessage hwnd, EM_SETSEL, 0, -1
            END IF
         END IF

         ' // Eat the Escape key to avoid the page being destroyed
         IF wParam = VK_ESCAPE THEN EXIT FUNCTION

#Else 'Modified code
      CASE WM_CHAR
        SELECT CASE wParam
          CASE 1, 232 '1 = Control-A, 232 = è 
            'Using WM_CHAR/1 instead of WM_KEYDOWN/VK_A will not beep
            'On my keyboard Control-e give "è". So it add "è" to the text before selection
            SendMessage(hWnd, EM_SETSEL, 0, - 1) 'Select everything
            EXIT FUNCTION
        END SELECT

      CASE WM_KEYDOWN
         IF wParam = VK_ESCAPE THEN EXIT FUNCTION     
#EndIf

      CASE WM_DESTROY
         ' // REQUIRED: Remove control subclassing
         SetWindowLongPtrW hwnd, GWLP_WNDPROC, CAST(LONG_PTR, RemovePropW(hwnd, "OLDWNDPROC"))

   END SELECT

   ' // Default processing of Windows messages
   FUNCTION = CallWindowProcW(GetPropW(hwnd, "OLDWNDPROC"), hwnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================

Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 16, 2017, 02:28:25 AM
As I normally have my speaker off because of these annoying web pages with sound, I didn't have noticed the beep :)

Maybe better to use only


      CASE WM_CHAR
         SELECT CASE wParam
            CASE 1   ' // Ctrl+A
               SendMessage(hWnd, EM_SETSEL, 0, - 1) 'Select everything
               EXIT FUNCTION
         END SELECT


Otherwise, we can't use è.

> Is Control-E is standard for selection in Spanish?

Explorer and Notepad use it.
Title: Re: FBWinSpy - Preliminary work
Post by: Pierre Bellisle on June 16, 2017, 03:55:35 PM
Hey,

The "è" is certainly no big deal for me, I might be the only one having it cause I got my own layout using MS-Keyboard-Layout-Creator.
It's not a french keyboard standard asignment, still other may have the letter "e" inserted with a beep.

This is interesting stuff, one more thing to take care about... :-) Comparison of shortcuts in English and Spanish (http://stelio.net/stiki/IT:Comparison_of_shortcuts_in_English_and_Spanish)

Pierre
Title: Re: FBWinSpy - Preliminary work
Post by: José Roca on June 16, 2017, 04:12:10 PM
These differences came because they try to choose a shortcut that is easier to remember according the language of the user.

For example:

Ctrl+F (for English "Find"), Ctrl+B (for Spanish "Buscar")
Ctrl+O (for English "Open"), Ctrl+A (for Spanish "Abrir")
Ctrl+E (for English "All"), Ctrl+E (could have been Ctrl+T, for Spanish "Todo", but Ctrl+T is already used for "Tamaño", i.e. "Size")
Ctrl+U (for English "Underline"), Ctrl+S (for Spanish "Subrayado")
etc.

In Notepad, using the Spanish Windows version, to do a "Find" you have to use Ctrl+B (not Ctrl+F) and to select all Ctrl+E (not Ctrl-A).