You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1028 lines
32 KiB
1028 lines
32 KiB
/**************************** Module Header ********************************\
|
|
* Module Name: winable2.c
|
|
*
|
|
* This has the following Active Accesibility API
|
|
* GetGUIThreadInfo
|
|
* GetTitleBarInfo
|
|
* GetScrollBarInfo
|
|
* GetMenuBarInfo
|
|
* GetComboxBoxInfo
|
|
* GetListBoxInfo
|
|
*
|
|
* The Winevent hooks are handled in winable.c.
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* History:
|
|
* 08-30-96 IanJa Ported from Windows '95
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
/*****************************************************************************\
|
|
* _GetGUIThreadInfo
|
|
*
|
|
* This gets GUI information out of context. If you pass in a NULL thread ID,
|
|
* we will get the 'global' information, using the foreground thread. This
|
|
* is guaranteed to be the real active window, focus window, etc. Yes, you
|
|
* could do it yourself by calling GetForegroundWindow, getting the thread ID
|
|
* of that window via GetWindowThreadProcessId, then passing the ID into
|
|
* GetGUIThreadInfo(). However, that takes three calls and aside from being
|
|
* a pain, anything could happen in the middle. So passing in NULL gets
|
|
* you stuff in one call and hence also works right.
|
|
*
|
|
* This function returns FALSE if the thread doesn't have a queue or the
|
|
* thread ID is bogus.
|
|
\*****************************************************************************/
|
|
BOOL _GetGUIThreadInfo(
|
|
PTHREADINFO pti,
|
|
PGUITHREADINFO pgui)
|
|
{
|
|
PQ pq;
|
|
|
|
/*
|
|
* Validate threadinfo structure
|
|
*/
|
|
if (pgui->cbSize != sizeof(GUITHREADINFO)) {
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "GUITHREADINFO.cbSize %d is wrong", pgui->cbSize);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Is this a valid initialized GUI thread?
|
|
*/
|
|
if (pti != NULL) {
|
|
pq = pti->pq;
|
|
} else {
|
|
/*
|
|
* Use the foreground queue. To get menu state information we must also
|
|
* figure out the right pti. This matches _GetForegroundWindow() logic.
|
|
*/
|
|
if ((pq = gpqForeground) == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (pq->spwndActive && (GETPTI(pq->spwndActive)->pq == pq)) {
|
|
pti = GETPTI(pq->spwndActive);
|
|
if (PtiCurrentShared()->rpdesk != pti->rpdesk) {
|
|
RIPERR0(ERROR_ACCESS_DENIED, RIP_VERBOSE, "Foreground window on different desktop");
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
UserAssert(pq != NULL);
|
|
|
|
/*
|
|
* For C2 security, verify that pq and pti are on the current thread's desktop.
|
|
* We can't directly determine which desktop pq belongs to, but we can at
|
|
* least ensure that any caret info we return is not from another desktop
|
|
*/
|
|
if (pq->caret.spwnd &&
|
|
(GETPTI(pq->caret.spwnd)->rpdesk != PtiCurrentShared()->rpdesk)) {
|
|
RIPERR0(ERROR_ACCESS_DENIED, RIP_VERBOSE, "Foreground caret on different desktop");
|
|
return FALSE;
|
|
}
|
|
if (pti && (pti->rpdesk != PtiCurrentShared()->rpdesk)) {
|
|
RIPERR0(ERROR_ACCESS_DENIED, RIP_VERBOSE, "Foreground thread on different desktop");
|
|
return FALSE;
|
|
}
|
|
|
|
pgui->flags = 0;
|
|
pgui->hwndMoveSize = NULL;
|
|
pgui->hwndMenuOwner = NULL;
|
|
|
|
/*
|
|
* Get menu information from the THREADINFO.
|
|
*/
|
|
if (pti != NULL) {
|
|
if (pti->pmsd && !pti->pmsd->fTrackCancelled && pti->pmsd->spwnd) {
|
|
pgui->flags |= GUI_INMOVESIZE;
|
|
pgui->hwndMoveSize = HWq(pti->pmsd->spwnd);
|
|
}
|
|
|
|
if (pti->pMenuState && pti->pMenuState->pGlobalPopupMenu) {
|
|
pgui->flags |= GUI_INMENUMODE;
|
|
|
|
if (pti->pMenuState->pGlobalPopupMenu->fHasMenuBar) {
|
|
if (pti->pMenuState->pGlobalPopupMenu->fIsSysMenu) {
|
|
pgui->flags |= GUI_SYSTEMMENUMODE;
|
|
}
|
|
} else {
|
|
pgui->flags |= GUI_POPUPMENUMODE;
|
|
}
|
|
|
|
if (pti->pMenuState->pGlobalPopupMenu->spwndNotify) {
|
|
pgui->hwndMenuOwner = HWq(pti->pMenuState->pGlobalPopupMenu->spwndNotify);
|
|
}
|
|
}
|
|
|
|
if (pti->TIF_flags & TIF_16BIT) {
|
|
pgui->flags |= GUI_16BITTASK;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the rest of the information from the queue.
|
|
*/
|
|
pgui->hwndActive = HW(pq->spwndActive);
|
|
pgui->hwndFocus = HW(pq->spwndFocus);
|
|
pgui->hwndCapture = HW(pq->spwndCapture);
|
|
pgui->hwndCaret = NULL;
|
|
|
|
if (pq->caret.spwnd) {
|
|
pgui->hwndCaret = HWq(pq->caret.spwnd);
|
|
|
|
if ((GETPTI(pq->caret.spwnd) != PtiCurrentShared()) &&
|
|
(pq->caret.spwnd->pcls->style & CS_OWNDC)) {
|
|
/*
|
|
* This is the case where we are being called by a different
|
|
* thread than created the window, and the window has a
|
|
* private DC. We have to do extra work to be able to
|
|
* return the desired information.
|
|
* These coords are always relative to the client of hwndCaret.
|
|
*/
|
|
pgui->rcCaret.left = pq->caret.xOwnDc;
|
|
pgui->rcCaret.right = pgui->rcCaret.left + pq->caret.cxOwnDc;
|
|
pgui->rcCaret.top = pq->caret.yOwnDc;
|
|
pgui->rcCaret.bottom = pgui->rcCaret.top + pq->caret.cyOwnDc;
|
|
} else {
|
|
/*
|
|
* These coords are still in logical coordinates. Ie, these
|
|
* are the coordinates we draw at in UT_InvertCaret.
|
|
*/
|
|
pgui->rcCaret.left = pq->caret.x;
|
|
pgui->rcCaret.right = pgui->rcCaret.left + pq->caret.cx;
|
|
pgui->rcCaret.top = pq->caret.y;
|
|
pgui->rcCaret.bottom = pgui->rcCaret.top + pq->caret.cy;
|
|
}
|
|
|
|
if (pq->caret.iHideLevel == 0) {
|
|
pgui->flags |= GUI_CARETBLINKING;
|
|
}
|
|
} else if (pti && (pti->ppi->W32PF_Flags & W32PF_CONSOLEHASFOCUS)) {
|
|
/*
|
|
* The thread is running in the console window with focus. Pull
|
|
* out the info from the console pseudo caret.
|
|
*/
|
|
pgui->hwndCaret = pti->rpdesk->cciConsole.hwnd;
|
|
pgui->rcCaret = pti->rpdesk->cciConsole.rc;
|
|
} else {
|
|
SetRectEmpty(&pgui->rcCaret);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/****************************************************************************\
|
|
* xxxGetTitleBarInfo
|
|
*
|
|
* Gets info about a window's title bar. If the window is bogus or
|
|
* doesn't have a titlebar, this will fail.
|
|
\****************************************************************************/
|
|
BOOL xxxGetTitleBarInfo(
|
|
PWND pwnd,
|
|
PTITLEBARINFO ptbi)
|
|
{
|
|
int cxB;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Validate TITLEBARINFO structure.
|
|
*/
|
|
if (ptbi->cbSize != sizeof(TITLEBARINFO)) {
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "TITLEBARINFO.cbSize %d is wrong", ptbi->cbSize);
|
|
return FALSE;
|
|
}
|
|
|
|
RtlZeroMemory(&ptbi->rgstate, sizeof(ptbi->rgstate));
|
|
|
|
ptbi->rgstate[INDEX_TITLEBAR_SELF] |= STATE_SYSTEM_FOCUSABLE;
|
|
if (TestWF(pwnd, WFBORDERMASK) != LOBYTE(WFCAPTION)) {
|
|
// No titlebar.
|
|
ptbi->rgstate[INDEX_TITLEBAR_SELF] |= STATE_SYSTEM_INVISIBLE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (!TestWF(pwnd, WFMINIMIZED) && !TestWF(pwnd, WFCPRESENT)) {
|
|
// Off screen (didn't fit)
|
|
ptbi->rgstate[INDEX_TITLEBAR_SELF] |= STATE_SYSTEM_OFFSCREEN;
|
|
SetRectEmpty(&ptbi->rcTitleBar);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Get titlebar rect.
|
|
*/
|
|
ptbi->rcTitleBar = pwnd->rcWindow;
|
|
cxB = GetWindowBorders(pwnd->style, pwnd->ExStyle, TRUE, FALSE);
|
|
InflateRect(&ptbi->rcTitleBar, -cxB * SYSMET(CXBORDER), -cxB * SYSMET(CYBORDER));
|
|
if (TestWF(pwnd, WEFTOOLWINDOW)) {
|
|
ptbi->rcTitleBar.bottom = ptbi->rcTitleBar.top + SYSMET(CYSMCAPTION);
|
|
} else {
|
|
ptbi->rcTitleBar.bottom = ptbi->rcTitleBar.top + SYSMET(CYCAPTION);
|
|
}
|
|
|
|
/*
|
|
* Don't include the system menu area!
|
|
*/
|
|
if (TestWF(pwnd, WFSYSMENU) && _HasCaptionIcon(pwnd)) {
|
|
ptbi->rcTitleBar.left += (ptbi->rcTitleBar.bottom - ptbi->rcTitleBar.top - SYSMET(CYBORDER));
|
|
}
|
|
|
|
/*
|
|
* Close button.
|
|
*/
|
|
if (!TestWF(pwnd, WFSYSMENU) && TestWF(pwnd, WFWIN40COMPAT)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_CLOSEBUTTON] |= STATE_SYSTEM_INVISIBLE;
|
|
} else {
|
|
if (!xxxMNCanClose(pwnd)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_CLOSEBUTTON] |= STATE_SYSTEM_UNAVAILABLE;
|
|
}
|
|
|
|
if (TestWF(pwnd, WFCLOSEBUTTONDOWN)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_CLOSEBUTTON] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Max button.
|
|
*/
|
|
if (!TestWF(pwnd, WFSYSMENU) && TestWF(pwnd, WFWIN40COMPAT)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MAXBUTTON] |= STATE_SYSTEM_INVISIBLE;
|
|
} else {
|
|
if (!TestWF(pwnd, WFMAXBOX)) {
|
|
if (!TestWF(pwnd, WFMINBOX)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MAXBUTTON] |= STATE_SYSTEM_INVISIBLE;
|
|
} else {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MAXBUTTON] |= STATE_SYSTEM_UNAVAILABLE;
|
|
}
|
|
}
|
|
|
|
if (TestWF(pwnd, WFZOOMBUTTONDOWN)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MAXBUTTON] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Min button.
|
|
*/
|
|
if (!TestWF(pwnd, WFSYSMENU) && TestWF(pwnd, WFWIN40COMPAT)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MINBUTTON] |= STATE_SYSTEM_INVISIBLE;
|
|
} else {
|
|
if (!TestWF(pwnd, WFMINBOX)) {
|
|
if (!TestWF(pwnd, WFMAXBOX)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MINBUTTON] |= STATE_SYSTEM_INVISIBLE;
|
|
} else {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MINBUTTON] |= STATE_SYSTEM_UNAVAILABLE;
|
|
}
|
|
}
|
|
|
|
if (TestWF(pwnd, WFREDUCEBUTTONDOWN)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_MINBUTTON] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Help button.
|
|
*/
|
|
if (!TestWF(pwnd, WEFCONTEXTHELP) || TestWF(pwnd, WFMINBOX) ||
|
|
TestWF(pwnd, WFMAXBOX)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_HELPBUTTON] |= STATE_SYSTEM_INVISIBLE;
|
|
} else {
|
|
if (TestWF(pwnd, WFHELPBUTTONDOWN)) {
|
|
ptbi->rgstate[INDEX_TITLEBAR_HELPBUTTON] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
}
|
|
|
|
// IME button BOGUS!
|
|
ptbi->rgstate[INDEX_TITLEBAR_IMEBUTTON] = STATE_SYSTEM_INVISIBLE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************\
|
|
* xxxGetScrollBarInfo
|
|
*
|
|
* Gets state & location information about a scrollbar.
|
|
*
|
|
* Note we fill in the minimal amount of useful info. OLEACC is responsible
|
|
* for extrapolation. I.E., if both the line up and line down buttons are
|
|
* disabled, the whole scrollbar is, and the thumb is invisible.
|
|
\*****************************************************************************/
|
|
BOOL xxxGetScrollBarInfo(
|
|
PWND pwnd,
|
|
LONG idObject,
|
|
PSCROLLBARINFO psbi)
|
|
{
|
|
UINT wDisable;
|
|
BOOL fVertical;
|
|
SBCALC SBCalc;
|
|
PCLS pcls;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Validate SCROLLBARINFO structure.
|
|
*/
|
|
if (psbi->cbSize != sizeof(SCROLLBARINFO)) {
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING,
|
|
"SCROLLBARINFO.cbSize 0x%x is wrong", psbi->cbSize);
|
|
return FALSE;
|
|
}
|
|
|
|
pcls = pwnd->pcls;
|
|
|
|
if ((idObject == OBJID_CLIENT) &&
|
|
(GETFNID(pwnd) != FNID_SCROLLBAR) &&
|
|
(pcls->atomClassName != gpsi->atomSysClass[ICLS_SCROLLBAR])) {
|
|
|
|
return (BOOL)xxxSendMessage(pwnd, SBM_GETSCROLLBARINFO, 0, (LPARAM)psbi);
|
|
}
|
|
|
|
RtlZeroMemory(&psbi->rgstate, sizeof(psbi->rgstate));
|
|
|
|
/*
|
|
* Calculate where everything is.
|
|
*/
|
|
if (idObject == OBJID_CLIENT) {
|
|
RECT rc;
|
|
wDisable = ((PSBWND)pwnd)->wDisableFlags;
|
|
fVertical = ((PSBWND)pwnd)->fVert;
|
|
GetRect(pwnd, &rc, GRECT_CLIENT | GRECT_CLIENTCOORDS);
|
|
CalcSBStuff2(&SBCalc, &rc, (PSBDATA)&((PSBWND)pwnd)->SBCalc, ((PSBWND)pwnd)->fVert);
|
|
} else {
|
|
/*
|
|
* Is this window scrollbar here?
|
|
*/
|
|
if (idObject == OBJID_VSCROLL) {
|
|
fVertical = TRUE;
|
|
if (!TestWF(pwnd, WFVSCROLL)) {
|
|
// No scrollbar.
|
|
psbi->rgstate[INDEX_SCROLLBAR_SELF] |= STATE_SYSTEM_INVISIBLE;
|
|
} else if (!TestWF(pwnd, WFVPRESENT)) {
|
|
// Window too short to display it.
|
|
psbi->rgstate[INDEX_SCROLLBAR_SELF] |= STATE_SYSTEM_OFFSCREEN;
|
|
}
|
|
} else if (idObject == OBJID_HSCROLL) {
|
|
fVertical = FALSE;
|
|
if (! TestWF(pwnd, WFHSCROLL)) {
|
|
// No scrollbar.
|
|
psbi->rgstate[INDEX_SCROLLBAR_SELF] |= STATE_SYSTEM_INVISIBLE;
|
|
} else if (! TestWF(pwnd, WFHPRESENT)) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_SELF] |= STATE_SYSTEM_OFFSCREEN;
|
|
}
|
|
} else {
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "invalid idObject %d", idObject);
|
|
return FALSE;
|
|
}
|
|
|
|
if (psbi->rgstate[INDEX_SCROLLBAR_SELF] & STATE_SYSTEM_INVISIBLE) {
|
|
return TRUE;
|
|
}
|
|
|
|
wDisable = GetWndSBDisableFlags(pwnd, fVertical);
|
|
|
|
if (!(psbi->rgstate[INDEX_SCROLLBAR_SELF] & STATE_SYSTEM_OFFSCREEN)) {
|
|
CalcSBStuff(pwnd, &SBCalc, fVertical);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Setup button states.
|
|
*/
|
|
if (wDisable & LTUPFLAG) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_UP] |= STATE_SYSTEM_UNAVAILABLE;
|
|
psbi->rgstate[INDEX_SCROLLBAR_UPPAGE] |= STATE_SYSTEM_UNAVAILABLE;
|
|
}
|
|
|
|
if (wDisable & RTDNFLAG) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_DOWN] |= STATE_SYSTEM_UNAVAILABLE;
|
|
psbi->rgstate[INDEX_SCROLLBAR_DOWNPAGE] |= STATE_SYSTEM_UNAVAILABLE;
|
|
}
|
|
|
|
if ((wDisable & (LTUPFLAG | RTDNFLAG)) == (LTUPFLAG | RTDNFLAG)) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_SELF] |= STATE_SYSTEM_UNAVAILABLE;
|
|
}
|
|
|
|
/*
|
|
* Button pressed?
|
|
*/
|
|
if (TestWF(pwnd, WFSCROLLBUTTONDOWN) &&
|
|
((idObject != OBJID_VSCROLL) || TestWF(pwnd, WFVERTSCROLLTRACK))) {
|
|
if (TestWF(pwnd, WFLINEUPBUTTONDOWN)) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_UP] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
|
|
if (TestWF(pwnd, WFPAGEUPBUTTONDOWN)) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_UPPAGE] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
|
|
if (TestWF(pwnd, WFPAGEDNBUTTONDOWN)) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_DOWNPAGE] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
|
|
if (TestWF(pwnd, WFLINEDNBUTTONDOWN)) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_DOWN] |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Fill in area locations.
|
|
*/
|
|
if (!(psbi->rgstate[INDEX_SCROLLBAR_SELF] & STATE_SYSTEM_OFFSCREEN)) {
|
|
if (fVertical) {
|
|
psbi->rcScrollBar.left = SBCalc.pxLeft;
|
|
psbi->rcScrollBar.top = SBCalc.pxTop;
|
|
psbi->rcScrollBar.right = SBCalc.pxRight;
|
|
psbi->rcScrollBar.bottom = SBCalc.pxBottom;
|
|
} else {
|
|
psbi->rcScrollBar.left = SBCalc.pxTop;
|
|
psbi->rcScrollBar.top = SBCalc.pxLeft;
|
|
psbi->rcScrollBar.right = SBCalc.pxBottom;
|
|
psbi->rcScrollBar.bottom = SBCalc.pxRight;
|
|
}
|
|
|
|
if (idObject == OBJID_CLIENT) {
|
|
OffsetRect(&psbi->rcScrollBar, pwnd->rcClient.left, pwnd->rcClient.top);
|
|
} else {
|
|
OffsetRect(&psbi->rcScrollBar, pwnd->rcWindow.left, pwnd->rcWindow.top);
|
|
}
|
|
|
|
psbi->dxyLineButton = (SBCalc.pxUpArrow - SBCalc.pxTop);
|
|
psbi->xyThumbTop = (SBCalc.pxThumbTop - SBCalc.pxTop);
|
|
psbi->xyThumbBottom = (SBCalc.pxThumbBottom - SBCalc.pxTop);
|
|
|
|
/*
|
|
* Is the thumb all the way to the left/top? If so, page up is
|
|
* not visible.
|
|
*/
|
|
if (SBCalc.pxThumbTop == SBCalc.pxUpArrow) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_UPPAGE] |= STATE_SYSTEM_INVISIBLE;
|
|
}
|
|
|
|
/*
|
|
* Is the thumb all the way to the right/down? If so, page down
|
|
* is not visible.
|
|
*/
|
|
if (SBCalc.pxThumbBottom == SBCalc.pxDownArrow) {
|
|
psbi->rgstate[INDEX_SCROLLBAR_DOWNPAGE] |= STATE_SYSTEM_INVISIBLE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*****************************************************************************\
|
|
* _GetAncestor
|
|
*
|
|
* This gets one of:
|
|
* * The _real_ parent. This does NOT include the owner, unlike GetParent().
|
|
* Stops at a top level window unless we start with the desktop. In which
|
|
* case, we return the desktop.
|
|
* * The _real_ root, caused by walking up the chain getting the ancestor.
|
|
* * The _real_ owned root, caused by GetParent()ing up.
|
|
\*****************************************************************************/
|
|
PWND _GetAncestor(
|
|
PWND pwnd,
|
|
UINT gaFlags)
|
|
{
|
|
PWND pwndParent;
|
|
|
|
/*
|
|
* If we start with the desktop, the message window or the mother window,
|
|
* return NULL.
|
|
*/
|
|
if (pwnd == PWNDDESKTOP(pwnd) ||
|
|
pwnd == PWNDMESSAGE(pwnd) ||
|
|
pwnd->spwndParent == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
switch (gaFlags) {
|
|
case GA_PARENT:
|
|
pwnd = pwnd->spwndParent;
|
|
break;
|
|
|
|
case GA_ROOT:
|
|
while ((pwnd->spwndParent != PWNDDESKTOP(pwnd)) &&
|
|
(pwnd->spwndParent != PWNDMESSAGE(pwnd))) {
|
|
pwnd = pwnd->spwndParent;
|
|
}
|
|
break;
|
|
|
|
case GA_ROOTOWNER:
|
|
while (pwndParent = _GetParent(pwnd)) {
|
|
pwnd = pwndParent;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return pwnd;
|
|
}
|
|
|
|
|
|
/*****************************************************************************\
|
|
* _RealChildWindowFromPoint
|
|
*
|
|
* This returns the REAL child window at a point. The problem is that
|
|
* ChildWindowFromPoint() doesn't deal with HTTRANSPARENT areas of
|
|
* standard controls. We want to return a child behind a groupbox if it
|
|
* is in the "clear" area. But we want to return a static field always
|
|
* even though it too returns HTTRANSPARENT.
|
|
\*****************************************************************************/
|
|
PWND _RealChildWindowFromPoint(
|
|
PWND pwndParent,
|
|
POINT pt)
|
|
{
|
|
PWND pwndChild;
|
|
PWND pwndSave;
|
|
|
|
if (pwndParent != PWNDDESKTOP(pwndParent)) {
|
|
pt.x += pwndParent->rcClient.left;
|
|
pt.y += pwndParent->rcClient.top;
|
|
}
|
|
|
|
/*
|
|
* Is this point even in the parent?
|
|
*/
|
|
if (!PtInRect(&pwndParent->rcClient, pt) ||
|
|
(pwndParent->hrgnClip && !GrePtInRegion(pwndParent->hrgnClip, pt.x, pt.y))) {
|
|
// Nope
|
|
return NULL;
|
|
}
|
|
|
|
pwndSave = NULL;
|
|
|
|
/*
|
|
* Loop through the children.
|
|
*/
|
|
for (pwndChild = pwndParent->spwndChild; pwndChild; pwndChild = pwndChild->spwndNext) {
|
|
if (!TestWF(pwndChild, WFVISIBLE))
|
|
continue;
|
|
|
|
/*
|
|
* Is this point in the child's window?
|
|
*/
|
|
if (!PtInRect(&pwndChild->rcWindow, pt) ||
|
|
(pwndChild->hrgnClip && !GrePtInRegion(pwndChild->hrgnClip, pt.x, pt.y)))
|
|
continue;
|
|
|
|
/*
|
|
* OK, we are in somebody's window. Is this by chance a group box?
|
|
*/
|
|
if (IS_BUTTON(pwndChild)) {
|
|
if (TestWF(pwndChild, BFTYPEMASK) == LOBYTE(BS_GROUPBOX)) {
|
|
pwndSave = pwndChild;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return pwndChild;
|
|
}
|
|
|
|
/*
|
|
* Did we save a groupbox which turned out to have nothing behind it
|
|
* at that point?
|
|
*/
|
|
if (pwndSave) {
|
|
return pwndSave;
|
|
} else {
|
|
return pwndParent;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************\
|
|
* xxxGetMenuBarInfo
|
|
*
|
|
* This succeeds if the menu/menu item exists.
|
|
*
|
|
* Parameters:
|
|
* pwnd window
|
|
* idObject this can be OBJID_MENU, OBJID_SYSMENU, or OBJID_CLIENT
|
|
* idItem which thing do we need info on? 0..cItems. 0 indicates
|
|
* the menu itself, 1 is the first item on the menu...
|
|
* pmbi Pointer to a MENUBARINFO structure that gets filled in
|
|
*
|
|
\*****************************************************************************/
|
|
BOOL xxxGetMenuBarInfo(
|
|
PWND pwnd,
|
|
long idObject,
|
|
long idItem,
|
|
PMENUBARINFO pmbi)
|
|
{
|
|
PMENU pMenu;
|
|
int cBorders;
|
|
PITEM pItem;
|
|
PPOPUPMENU ppopup;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Validate MENUBARINFO structure.
|
|
*/
|
|
if (pmbi->cbSize != sizeof(MENUBARINFO)) {
|
|
RIPERR1(ERROR_INVALID_PARAMETER,
|
|
RIP_WARNING,
|
|
"MENUBARINFO.cbSize 0x%x is wrong",
|
|
pmbi->cbSize);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Initialize the fields.
|
|
*/
|
|
SetRectEmpty(&pmbi->rcBar);
|
|
pmbi->hMenu = NULL;
|
|
pmbi->hwndMenu = NULL;
|
|
pmbi->fBarFocused = FALSE;
|
|
pmbi->fFocused = FALSE;
|
|
|
|
/*
|
|
* Get the menu handle we will deal with.
|
|
*/
|
|
if (idObject == OBJID_MENU) {
|
|
int cBorders;
|
|
|
|
if (TestWF(pwnd, WFCHILD) || !pwnd->spmenu) {
|
|
return FALSE;
|
|
}
|
|
|
|
pMenu = pwnd->spmenu;
|
|
if (!pMenu) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* If we have an item, is it in the valid range?
|
|
*/
|
|
if ((idItem < 0) || ((DWORD)idItem > pMenu->cItems)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Menu handle.
|
|
*/
|
|
pmbi->hMenu = PtoHq(pMenu);
|
|
|
|
/*
|
|
* Menu rect.
|
|
*/
|
|
if (pMenu->cxMenu && pMenu->cyMenu) {
|
|
if (!idItem) {
|
|
cBorders = GetWindowBorders(pwnd->style, pwnd->ExStyle, TRUE, FALSE);
|
|
pmbi->rcBar.left = pwnd->rcWindow.left + cBorders * SYSMET(CXBORDER);
|
|
pmbi->rcBar.top = pwnd->rcWindow.top + cBorders * SYSMET(CYBORDER);
|
|
|
|
if (TestWF(pwnd, WFCPRESENT)) {
|
|
pmbi->rcBar.top += (TestWF(pwnd, WEFTOOLWINDOW) ? SYSMET(CYSMCAPTION) : SYSMET(CYCAPTION));
|
|
}
|
|
|
|
pmbi->rcBar.right = pmbi->rcBar.left + pMenu->cxMenu;
|
|
pmbi->rcBar.bottom = pmbi->rcBar.top + pMenu->cyMenu;
|
|
} else {
|
|
pItem = pMenu->rgItems + idItem - 1;
|
|
|
|
pmbi->rcBar.left = pwnd->rcWindow.left + pItem->xItem;
|
|
pmbi->rcBar.top = pwnd->rcWindow.top + pItem->yItem;
|
|
pmbi->rcBar.right = pmbi->rcBar.left + pItem->cxItem;
|
|
pmbi->rcBar.bottom = pmbi->rcBar.top + pItem->cyItem;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Are we currently in app menu bar mode?
|
|
*/
|
|
ppopup = GetpGlobalPopupMenu(pwnd);
|
|
if (ppopup && ppopup->fHasMenuBar && !ppopup->fIsSysMenu &&
|
|
(ppopup->spwndNotify == pwnd)) {
|
|
pmbi->fBarFocused = TRUE;
|
|
|
|
if (!idItem) {
|
|
pmbi->fFocused = TRUE;
|
|
} else if (ppopup->ppopupmenuRoot->posSelectedItem == (UINT)idItem-1) {
|
|
pmbi->fFocused = TRUE;
|
|
UserAssert(ppopup->ppopupmenuRoot);
|
|
pmbi->hwndMenu = HW(ppopup->ppopupmenuRoot->spwndNextPopup);
|
|
}
|
|
}
|
|
} else if (idObject == OBJID_SYSMENU) {
|
|
if (!TestWF(pwnd, WFSYSMENU)) {
|
|
return FALSE;
|
|
}
|
|
|
|
pMenu = xxxGetSysMenu(pwnd, FALSE);
|
|
if (!pMenu) {
|
|
return FALSE;
|
|
}
|
|
|
|
// If we have an item, is it in the valid range?
|
|
if ((idItem < 0) || ((DWORD)idItem > pMenu->cItems))
|
|
return FALSE;
|
|
|
|
pmbi->hMenu = PtoHq(pMenu);
|
|
|
|
/*
|
|
* Menu rect
|
|
*/
|
|
if (_HasCaptionIcon(pwnd)) {
|
|
// The menu and single item take up the same space
|
|
cBorders = GetWindowBorders(pwnd->style, pwnd->ExStyle, TRUE, FALSE);
|
|
pmbi->rcBar.left = pwnd->rcWindow.left + cBorders * SYSMET(CXBORDER);
|
|
pmbi->rcBar.top = pwnd->rcWindow.top + cBorders * SYSMET(CYBORDER);
|
|
|
|
pmbi->rcBar.right = pmbi->rcBar.left +
|
|
(TestWF(pwnd, WEFTOOLWINDOW) ? SYSMET(CXSMSIZE) : SYSMET(CXSIZE));
|
|
|
|
pmbi->rcBar.bottom = pmbi->rcBar.top +
|
|
(TestWF(pwnd, WEFTOOLWINDOW) ? SYSMET(CYSMSIZE) : SYSMET(CYSIZE));
|
|
}
|
|
|
|
/*
|
|
* Are we currently in system menu bar mode?
|
|
*/
|
|
ppopup = GetpGlobalPopupMenu(pwnd);
|
|
if (ppopup && ppopup->fHasMenuBar && ppopup->fIsSysMenu &&
|
|
(ppopup->spwndNotify == pwnd))
|
|
{
|
|
pmbi->fBarFocused = TRUE;
|
|
|
|
if (!idItem) {
|
|
pmbi->fFocused = TRUE;
|
|
} else if (ppopup->ppopupmenuRoot->posSelectedItem == (UINT)idItem - 1) {
|
|
pmbi->fFocused = TRUE;
|
|
UserAssert(ppopup->ppopupmenuRoot);
|
|
pmbi->hwndMenu = HW(ppopup->ppopupmenuRoot->spwndNextPopup);
|
|
}
|
|
}
|
|
} else if (idObject == OBJID_CLIENT) {
|
|
HMENU hMenu = (HMENU)xxxSendMessage(pwnd, MN_GETHMENU, 0, 0);
|
|
pMenu = ValidateHmenu(hMenu);
|
|
if (!pMenu) {
|
|
return FALSE;
|
|
}
|
|
|
|
// If we have an item, is it in the valid range?
|
|
if ((idItem < 0) || ((DWORD)idItem > pMenu->cItems)) {
|
|
return FALSE;
|
|
}
|
|
|
|
pmbi->hMenu = hMenu;
|
|
|
|
if (!idItem) {
|
|
pmbi->rcBar = pwnd->rcClient;
|
|
} else {
|
|
pItem = pMenu->rgItems + idItem - 1;
|
|
|
|
pmbi->rcBar.left = pwnd->rcClient.left + pItem->xItem;
|
|
pmbi->rcBar.top = pwnd->rcClient.top + pItem->yItem;
|
|
pmbi->rcBar.right = pmbi->rcBar.left + pItem->cxItem;
|
|
pmbi->rcBar.bottom = pmbi->rcBar.top + pItem->cyItem;
|
|
}
|
|
|
|
/*
|
|
* Are we currently in popup mode with us as one of the popups
|
|
* showing?
|
|
*
|
|
* Since malicious code could handle MN_GETHMENU and return a valid
|
|
* HMENU *w/o* pwnd being a real MENUWND, we need to explicitly
|
|
* check the fnid.
|
|
*/
|
|
if (GETFNID(pwnd) == FNID_MENU &&
|
|
(ppopup = ((PMENUWND)pwnd)->ppopupmenu) &&
|
|
(ppopup->ppopupmenuRoot == GetpGlobalPopupMenu(pwnd))) {
|
|
pmbi->fBarFocused = TRUE;
|
|
|
|
if (!idItem) {
|
|
pmbi->fFocused = TRUE;
|
|
} else if ((UINT)idItem == ppopup->posSelectedItem + 1) {
|
|
pmbi->fFocused = TRUE;
|
|
pmbi->hwndMenu = HW(ppopup->spwndNextPopup);
|
|
}
|
|
}
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxGetComboBoxInfo
|
|
*
|
|
* This returns combobox information for either a combo or its dropdown
|
|
* list.
|
|
\***************************************************************************/
|
|
BOOL xxxGetComboBoxInfo(
|
|
PWND pwnd,
|
|
PCOMBOBOXINFO pcbi)
|
|
{
|
|
PCLS pcls;
|
|
COMBOBOXINFO cbi = {
|
|
sizeof cbi,
|
|
};
|
|
BOOL fOtherProcess;
|
|
BOOL bRetval = FALSE;
|
|
WORD wWindowType = 0;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Make sure it is a combobox or a dropdown.
|
|
*/
|
|
pcls = pwnd->pcls;
|
|
if ((GETFNID(pwnd) == FNID_COMBOBOX) ||
|
|
(pcls->atomClassName == gpsi->atomSysClass[ICLS_COMBOBOX])) {
|
|
wWindowType = FNID_COMBOBOX;
|
|
} else if ((GETFNID(pwnd) == FNID_COMBOLISTBOX) ||
|
|
(pcls->atomClassName == gpsi->atomSysClass[ICLS_COMBOLISTBOX])) {
|
|
wWindowType = FNID_COMBOLISTBOX;
|
|
} else {
|
|
return (BOOL)xxxSendMessage(pwnd, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
|
|
}
|
|
|
|
/*
|
|
* Validate combo structure
|
|
*/
|
|
if (pcbi->cbSize != sizeof(COMBOBOXINFO)) {
|
|
RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "COMBOBOXINFO.cbSize %d is wrong", pcbi->cbSize);
|
|
return FALSE;
|
|
}
|
|
|
|
if (fOtherProcess = (GETPTI(pwnd)->ppi != PpiCurrent())) {
|
|
KeAttachProcess(PsGetProcessPcb(GETPTI(pwnd)->ppi->Process));
|
|
}
|
|
|
|
try {
|
|
PCBOX ccxPcboxSnap;
|
|
PWND ccxPwndSnap;
|
|
HWND ccxHwndSnap;
|
|
|
|
/*
|
|
* Snap and probe the CBOX structure, since it is client side.
|
|
*/
|
|
if (wWindowType == FNID_COMBOBOX) {
|
|
ccxPcboxSnap = ((PCOMBOWND)pwnd)->pcbox;
|
|
} else {
|
|
PLBIV ccxPlbSnap;
|
|
/*
|
|
* If this is a listbox, we must snap and probe the LBIV structure
|
|
* in order to get to the CBOX structure.
|
|
*/
|
|
ccxPlbSnap = ((PLBWND)pwnd)->pLBIV;
|
|
if (!ccxPlbSnap) {
|
|
goto errorexit;
|
|
}
|
|
ProbeForRead(ccxPlbSnap, sizeof(LBIV), DATAALIGN);
|
|
ccxPcboxSnap = ccxPlbSnap->pcbox;
|
|
}
|
|
if (!ccxPcboxSnap) {
|
|
goto errorexit;
|
|
}
|
|
ProbeForRead(ccxPcboxSnap, sizeof(CBOX), DATAALIGN);
|
|
|
|
/*
|
|
* Get the combo information now.
|
|
*/
|
|
|
|
/*
|
|
* Snap and probe the client side pointer to the Combo window.
|
|
*/
|
|
ccxPwndSnap = ccxPcboxSnap->spwnd;
|
|
ProbeForRead(ccxPwndSnap, sizeof(HEAD), DATAALIGN);
|
|
cbi.hwndCombo = HWCCX(ccxPwndSnap);
|
|
|
|
/*
|
|
* Snap & probe the client side pointer to the Edit window.
|
|
* To compare spwndEdit and pwnd, we should compare handles
|
|
* since spwndEdit is a client-side address and pwnd is a
|
|
* kernel-mode address,
|
|
*/
|
|
|
|
ccxPwndSnap = ccxPcboxSnap->spwndEdit;
|
|
/*
|
|
* If combobox is not fully initialized and spwndEdit is NULL,
|
|
* we should fail.
|
|
*/
|
|
ProbeForRead(ccxPwndSnap, sizeof(HEAD), DATAALIGN);
|
|
ccxHwndSnap = HWCCX(ccxPwndSnap);
|
|
if (ccxHwndSnap == HW(pwnd)) {
|
|
/*
|
|
* ComboBox doesn't have Edit control.
|
|
*/
|
|
cbi.hwndItem = NULL;
|
|
} else {
|
|
cbi.hwndItem = HWCCX(ccxPwndSnap);
|
|
}
|
|
|
|
/*
|
|
* Snap and probe the client side pointer to the List window
|
|
*/
|
|
ccxPwndSnap = ccxPcboxSnap->spwndList;
|
|
/*
|
|
* If combobox is not fully initialized and spwndList is NULL,
|
|
* we should fail.
|
|
*/
|
|
ProbeForRead(ccxPwndSnap, sizeof(HEAD), DATAALIGN);
|
|
cbi.hwndList = HWCCX(ccxPwndSnap);
|
|
|
|
/*
|
|
* Snap the rest of the combo information. We don't need to probe
|
|
* any of these, since there are no more indirections.
|
|
*/
|
|
cbi.rcItem = ccxPcboxSnap->editrc;
|
|
cbi.rcButton = ccxPcboxSnap->buttonrc;
|
|
|
|
/*
|
|
* Button state.
|
|
*/
|
|
cbi.stateButton = 0;
|
|
if (ccxPcboxSnap->CBoxStyle == CBS_SIMPLE) {
|
|
cbi.stateButton |= STATE_SYSTEM_INVISIBLE;
|
|
}
|
|
if (ccxPcboxSnap->fButtonPressed) {
|
|
cbi.stateButton |= STATE_SYSTEM_PRESSED;
|
|
}
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
goto errorexit;
|
|
}
|
|
|
|
*pcbi = cbi;
|
|
bRetval = TRUE;
|
|
|
|
errorexit:
|
|
if (fOtherProcess) {
|
|
KeDetachProcess();
|
|
}
|
|
|
|
return bRetval;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxGetListBoxInfo
|
|
*
|
|
* Currently returns back the # of items per column. There is no way to get
|
|
* or calculate this info any other way in a multicolumn list.
|
|
*
|
|
* For now, no structure is returned. If we ever need one more thing, make one.
|
|
\***************************************************************************/
|
|
DWORD xxxGetListBoxInfo(
|
|
PWND pwnd)
|
|
{
|
|
PCLS pcls;
|
|
DWORD dwRet = 0;
|
|
BOOL fOtherProcess;
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* Make sure it is a combobox or a dropdown.
|
|
*/
|
|
pcls = pwnd->pcls;
|
|
if ((pcls->atomClassName != gpsi->atomSysClass[ICLS_LISTBOX]) &&
|
|
(GETFNID(pwnd) != FNID_LISTBOX)) {
|
|
return (DWORD)xxxSendMessage(pwnd, LB_GETLISTBOXINFO, 0, 0);
|
|
}
|
|
|
|
if (fOtherProcess = (GETPTI(pwnd)->ppi != PpiCurrent())) {
|
|
KeAttachProcess(PsGetProcessPcb(GETPTI(pwnd)->ppi->Process));
|
|
}
|
|
|
|
try {
|
|
PLBIV ccxPlbSnap;
|
|
|
|
/*
|
|
* Snap and probe the pointer to the LBIV, since it is client-side.
|
|
*/
|
|
ccxPlbSnap = ((PLBWND)pwnd)->pLBIV;
|
|
if (!ccxPlbSnap) {
|
|
goto errorexit;
|
|
}
|
|
ProbeForRead(ccxPlbSnap, sizeof(LBIV), DATAALIGN);
|
|
|
|
if (ccxPlbSnap->fMultiColumn) {
|
|
dwRet = ccxPlbSnap->itemsPerColumn;
|
|
} else {
|
|
dwRet = ccxPlbSnap->cMac;
|
|
}
|
|
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
|
|
dwRet = 0;
|
|
}
|
|
|
|
errorexit:
|
|
if (fOtherProcess) {
|
|
KeDetachProcess();
|
|
}
|
|
|
|
return dwRet;
|
|
}
|