-
Bug
-
Resolution: Fixed
-
P3: Somewhat important
-
None
-
4.5.3
-
None
Steps to reproduce / test case:
If you run 32 bit compiled QT application on any 64 bit windows which uses QSystemTrayIcon and want to find out actual tray icon geometry and position, you will end up with QRect (0,0,0,0).
That's because there is an error in:
QRect QSystemTrayIconSys::findIconGeometry(const int iconId) in file qsystemtrayicon_win.cpp
while doing operation :
if(!ReadProcessMemory(trayProcess, data, &buttonData, sizeof(TBBUTTON), &numBytes))
in 32 bit compiled application the structure of TBBUTTON differs from structure which is compiled in system's start menu bar (which is 64bit). so while reading 64 bit process memory ( start menu bar) from 32bit application (our application) you'll fill the structure with bad data because of different offsets.
that's because the structure definition is architecture dependent :
(copy of source from commctrl.h)
typedef struct _TBBUTTON {
int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
#ifdef _WIN64
BYTE bReserved[6]; // padding for alignment
#elif defined(_WIN32)
BYTE bReserved[2]; // padding for alignment
#endif
DWORD_PTR dwData;
INT_PTR iString;
} TBBUTTON, NEAR* PTBBUTTON, *LPTBBUTTON;
typedef const TBBUTTON *LPCTBBUTTON;
the solution for this one is detecting if we are running on 64 bit or 32 bit system and then use proper structure for process memory reading.
More information:
here is my version of this method - tested and working.
typedef struct _TBBUTTONX32 {
int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
BYTE bReserved[2]; // padding for alignment
DWORD_PTR dwData;
INT_PTR iString;
} TBBUTTONX32;
typedef struct _TBBUTTONX64 {
int iBitmap;
int idCommand;
BYTE fsState;
BYTE fsStyle;
BYTE bReserved[6]; // padding for alignment
DWORD_PTR dwData;
INT_PTR iString;
} TBBUTTONX64;
/*
* This function tries to determine the icon geometry from the tray
*
* If it fails an invalid rect is returned.
*/
QRect QSystemTrayIconSys::findIconGeometry(const int iconId)
{
#if !defined(Q_OS_WINCE)
SYSTEM_INFO si;
bool is64bit = false;
ZeroMemory(&si, sizeof(si));
GetNativeSystemInfo(&si);
// wProcessorArchitecture field is Windows processor architecture - for sure :)
if((si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) ||
(si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64))
{
is64bit = true;
}
QRect ret;
#endif
TBBUTTON
#if defined(Q_OS_WINCE)
TBBUTTON buttonData;
#else
TBBUTTONX32 buttonData32;
TBBUTTONX64 buttonData64;
#endif
DWORD processID = 0;
#if defined(Q_OS_WINCE)
HWND trayHandle = FindWindowW(L"Shell_TrayWnd", NULL);
#else
HWND trayHandle = FindWindowA("Shell_TrayWnd", NULL);
#endif
//find the toolbar used in the notification area
if (trayHandle) {
#if defined(Q_OS_WINCE)
trayHandle = FindWindowW(L"TrayNotifyWnd", NULL);
#else
trayHandle = FindWindowExA(trayHandle, NULL, "TrayNotifyWnd", NULL);
#endif
if (trayHandle) {
#if defined(Q_OS_WINCE)
HWND hwnd = FindWindowW(L"SysPager", NULL);
#else
HWND hwnd = FindWindowExW(trayHandle, NULL, L"SysPager", NULL);
#endif
if (hwnd) {
#if defined(Q_OS_WINCE)
hwnd = FindWindow(L"ToolbarWindow32", NULL);
#else
hwnd = FindWindowExW(hwnd, NULL, L"ToolbarWindow32", NULL);
#endif
if (hwnd)
trayHandle = hwnd;
}
}
}
if (!trayHandle)
return ret;
GetWindowThreadProcessId(trayHandle, &processID);
if (processID <= 0)
return ret;
HANDLE trayProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ, 0, processID);
if (!trayProcess)
return ret;
int buttonCount = SendMessage(trayHandle, TB_BUTTONCOUNT, 0, 0);
#if defined(Q_OS_WINCE)
LPVOID data = VirtualAlloc(NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
#else
LPVOID data;
if (is64bit) {
data = VirtualAllocEx(trayProcess, NULL, sizeof(TBBUTTONX32), MEM_COMMIT, PAGE_READWRITE);
} else {
data = VirtualAllocEx(trayProcess, NULL, sizeof(TBBUTTONX64), MEM_COMMIT, PAGE_READWRITE);
}
#endif
if ( buttonCount < 1 || !data ) {
CloseHandle(trayProcess);
return ret;
}
//search for our icon among all toolbar buttons
for (int toolbarButton = 0; toolbarButton < buttonCount; ++toolbarButton ) {
SIZE_T numBytes = 0;
DWORD appData[2] = { 0, 0 };
SendMessage(trayHandle, TB_GETBUTTON, toolbarButton , (LPARAM)data);
#if defined(Q_OS_WINCE)
if(!ReadProcessMemory(trayProcess, data, &buttonData, sizeof(TBBUTTON), &numBytes))
continue;
#else
if(is64bit) {
if(!ReadProcessMemory(trayProcess, data, &buttonData64, sizeof(buttonData64), &numBytes))
continue;
} else {
if(!ReadProcessMemory(trayProcess, data, &buttonData32, sizeof(buttonData32), &numBytes))
continue;
}
#endif
#if defined(Q_OS_WINCE)
if(!ReadProcessMemory(trayProcess, (LPVOID) buttonData.dwData, appData, sizeof(appData), &numBytes))
continue;
#else
if(is64bit) {
if(!ReadProcessMemory(trayProcess, (LPVOID) buttonData64.dwData, appData, sizeof(appData), &numBytes))
continue;
} else {
if(!ReadProcessMemory(trayProcess, (LPVOID) buttonData32.dwData, appData, sizeof(appData), &numBytes))
continue;
}
#endif
int currentIconId = appData[1];
HWND currentIconHandle = (HWND) appData[0];
#if defined(Q_OS_WINCE)
bool isHidden = buttonData.fsState & TBSTATE_HIDDEN;
#else
bool isHidden;
if(is64bit) {
isHidden = buttonData64.fsState & TBSTATE_HIDDEN;
} else {
isHidden = buttonData32.fsState & TBSTATE_HIDDEN;
}
#endif
if (currentIconHandle == winId() &&
currentIconId == iconId && !isHidden) {
SendMessage(trayHandle, TB_GETITEMRECT, toolbarButton , (LPARAM)data);
RECT iconRect = {0, 0};
if(ReadProcessMemory(trayProcess, data, &iconRect, sizeof(RECT), &numBytes)) {
MapWindowPoints(trayHandle, NULL, (LPPOINT)&iconRect, 2);
QRect geometry(iconRect.left + 1, iconRect.top + 1,
iconRect.right - iconRect.left - 2,
iconRect.bottom - iconRect.top - 2);
if (geometry.isValid())
ret = geometry;
break;
}
}
}
#if defined(Q_OS_WINCE)
VirtualFree(data, 0, MEM_RELEASE);
#else
VirtualFreeEx(trayProcess, data, 0, MEM_RELEASE);
#endif
CloseHandle(trayProcess);
return ret;
}