1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Win32k subsystem 4 * PURPOSE: Window scrolling function 5 * FILE: win32ss/user/ntuser/scrollex.c 6 * PROGRAMER: Filip Navara (xnavara@volny.cz) 7 */ 8 9 #include <win32k.h> 10 11 DBG_DEFAULT_CHANNEL(UserPainting); 12 13 static 14 HWND FASTCALL 15 co_IntFixCaret(PWND Window, RECTL *lprc, UINT flags) 16 { 17 PTHRDCARETINFO CaretInfo; 18 PTHREADINFO pti; 19 PUSER_MESSAGE_QUEUE ThreadQueue; 20 HWND hWndCaret; 21 PWND WndCaret; 22 23 ASSERT_REFS_CO(Window); 24 25 pti = PsGetCurrentThreadWin32Thread(); 26 ThreadQueue = pti->MessageQueue; 27 CaretInfo = &ThreadQueue->CaretInfo; 28 hWndCaret = CaretInfo->hWnd; 29 30 WndCaret = ValidateHwndNoErr(hWndCaret); 31 32 // FIXME: Check for WndCaret can be NULL 33 if (WndCaret == Window || 34 ((flags & SW_SCROLLCHILDREN) && IntIsChildWindow(Window, WndCaret))) 35 { 36 POINT pt, FromOffset, ToOffset; 37 RECTL rcCaret; 38 39 pt.x = CaretInfo->Pos.x; 40 pt.y = CaretInfo->Pos.y; 41 IntGetClientOrigin(WndCaret, &FromOffset); 42 IntGetClientOrigin(Window, &ToOffset); 43 rcCaret.left = pt.x; 44 rcCaret.top = pt.y; 45 rcCaret.right = pt.x + CaretInfo->Size.cx; 46 rcCaret.bottom = pt.y + CaretInfo->Size.cy; 47 if (RECTL_bIntersectRect(lprc, lprc, &rcCaret)) 48 { 49 co_UserHideCaret(0); 50 lprc->left = pt.x; 51 lprc->top = pt.y; 52 return hWndCaret; 53 } 54 } 55 56 return 0; 57 } 58 59 /* 60 Old GetUpdateRgn, for scrolls, see above note. 61 */ 62 INT FASTCALL 63 co_IntGetUpdateRgn(PWND Window, PREGION Rgn, BOOL bErase) 64 { 65 int RegionType; 66 RECTL Rect; 67 PREGION UpdateRgn; 68 69 ASSERT_REFS_CO(Window); 70 71 if (bErase) 72 { 73 co_IntPaintWindows(Window, RDW_NOCHILDREN, FALSE); 74 } 75 76 Window->state &= ~WNDS_UPDATEDIRTY; 77 78 if (Window->hrgnUpdate == NULL) 79 { 80 REGION_SetRectRgn(Rgn, 0, 0, 0, 0); 81 return NULLREGION; 82 } 83 84 UpdateRgn = REGION_LockRgn(Window->hrgnUpdate); 85 if (!UpdateRgn) 86 return ERROR; 87 88 Rect = Window->rcClient; 89 IntIntersectWithParents(Window, &Rect); 90 REGION_SetRectRgn(Rgn, Rect.left, Rect.top, Rect.right, Rect.bottom); 91 RegionType = IntGdiCombineRgn(Rgn, Rgn, UpdateRgn, RGN_AND); 92 REGION_bOffsetRgn(Rgn, -Window->rcClient.left, -Window->rcClient.top); 93 REGION_UnlockRgn(UpdateRgn); 94 95 return RegionType; 96 } 97 98 static 99 INT FASTCALL 100 UserScrollDC( 101 HDC hDC, 102 INT dx, 103 INT dy, 104 const RECTL *prcScroll, 105 const RECTL *prcClip, 106 HRGN hrgnUpdate, 107 PREGION RgnUpdate, 108 RECTL *prcUpdate) 109 { 110 PDC pDC; 111 RECTL rcScroll, rcClip, rcSrc, rcDst; 112 INT Result; 113 114 if (GdiGetClipBox(hDC, &rcClip) == ERROR) 115 { 116 ERR("GdiGetClipBox failed for HDC %p\n", hDC); 117 return ERROR; 118 } 119 120 rcScroll = rcClip; 121 if (prcClip) 122 { 123 RECTL_bIntersectRect(&rcClip, &rcClip, prcClip); 124 } 125 126 if (prcScroll) 127 { 128 rcScroll = *prcScroll; 129 RECTL_bIntersectRect(&rcSrc, &rcClip, prcScroll); 130 } 131 else 132 { 133 rcSrc = rcClip; 134 } 135 136 rcDst = rcSrc; 137 RECTL_vOffsetRect(&rcDst, dx, dy); 138 RECTL_bIntersectRect(&rcDst, &rcDst, &rcClip); 139 140 if (!NtGdiBitBlt( hDC, 141 rcDst.left, 142 rcDst.top, 143 rcDst.right - rcDst.left, 144 rcDst.bottom - rcDst.top, 145 hDC, 146 rcDst.left - dx, 147 rcDst.top - dy, 148 SRCCOPY, 149 0, 150 0)) 151 { 152 return ERROR; 153 } 154 155 /* Calculate the region that was invalidated by moving or 156 could not be copied, because it was not visible */ 157 if (RgnUpdate || hrgnUpdate || prcUpdate) 158 { 159 PREGION RgnOwn, RgnTmp; 160 161 pDC = DC_LockDc(hDC); 162 if (!pDC) 163 { 164 return ERROR; 165 } 166 167 if (hrgnUpdate) 168 { 169 NT_ASSERT(RgnUpdate == NULL); 170 RgnUpdate = REGION_LockRgn(hrgnUpdate); 171 if (!RgnUpdate) 172 { 173 DC_UnlockDc(pDC); 174 return ERROR; 175 } 176 } 177 178 /* Begin with the shifted and then clipped scroll rect */ 179 rcDst = rcScroll; 180 RECTL_vOffsetRect(&rcDst, dx, dy); 181 RECTL_bIntersectRect(&rcDst, &rcDst, &rcClip); 182 if (RgnUpdate) 183 { 184 RgnOwn = RgnUpdate; 185 REGION_SetRectRgn(RgnOwn, rcDst.left, rcDst.top, rcDst.right, rcDst.bottom); 186 } 187 else 188 { 189 RgnOwn = IntSysCreateRectpRgnIndirect(&rcDst); 190 } 191 192 /* Add the source rect */ 193 RgnTmp = IntSysCreateRectpRgnIndirect(&rcSrc); 194 IntGdiCombineRgn(RgnOwn, RgnOwn, RgnTmp, RGN_OR); 195 196 /* Substract the part of the dest that was visible in source */ 197 IntGdiCombineRgn(RgnTmp, RgnTmp, pDC->prgnVis, RGN_AND); 198 REGION_bOffsetRgn(RgnTmp, dx, dy); 199 Result = IntGdiCombineRgn(RgnOwn, RgnOwn, RgnTmp, RGN_DIFF); 200 201 /* DO NOT Unlock DC while messing with prgnVis! */ 202 DC_UnlockDc(pDC); 203 204 REGION_Delete(RgnTmp); 205 206 if (prcUpdate) 207 { 208 REGION_GetRgnBox(RgnOwn, prcUpdate); 209 } 210 211 if (hrgnUpdate) 212 { 213 REGION_UnlockRgn(RgnUpdate); 214 } 215 else if (!RgnUpdate) 216 { 217 REGION_Delete(RgnOwn); 218 } 219 } 220 else 221 Result = NULLREGION; 222 223 return Result; 224 } 225 226 DWORD 227 FASTCALL 228 IntScrollWindowEx( 229 PWND Window, 230 INT dx, 231 INT dy, 232 const RECT *prcScroll, 233 const RECT *prcClip, 234 HRGN hrgnUpdate, 235 LPRECT prcUpdate, 236 UINT flags) 237 { 238 INT Result; 239 RECTL rcScroll, rcClip, rcCaret; 240 PWND CaretWnd; 241 HDC hDC; 242 PREGION RgnUpdate = NULL, RgnTemp, RgnWinupd = NULL; 243 HWND hwndCaret; 244 DWORD dcxflags = 0; 245 int rdw_flags; 246 USER_REFERENCE_ENTRY CaretRef; 247 248 if (!Window || !IntIsWindowDrawable(Window)) 249 { 250 return ERROR; 251 } 252 253 IntGetClientRect(Window, &rcClip); 254 255 if (prcScroll) 256 RECTL_bIntersectRect(&rcScroll, &rcClip, prcScroll); 257 else 258 rcScroll = rcClip; 259 260 if (prcClip) 261 RECTL_bIntersectRect(&rcClip, &rcClip, prcClip); 262 263 if (rcClip.right <= rcClip.left || rcClip.bottom <= rcClip.top || 264 (dx == 0 && dy == 0)) 265 { 266 return NULLREGION; 267 } 268 269 /* We must use a copy of the region, as we can't hold an exclusive lock 270 * on it while doing callouts to user-mode */ 271 RgnUpdate = IntSysCreateRectpRgn(0, 0, 0, 0); 272 if(!RgnUpdate) 273 { 274 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY); 275 return ERROR; 276 } 277 278 if (hrgnUpdate) 279 { 280 RgnTemp = REGION_LockRgn(hrgnUpdate); 281 if (!RgnTemp) 282 { 283 EngSetLastError(ERROR_INVALID_HANDLE); 284 Result = ERROR; 285 goto Cleanup; 286 } 287 IntGdiCombineRgn(RgnUpdate, RgnTemp, NULL, RGN_COPY); 288 REGION_UnlockRgn(RgnTemp); 289 } 290 291 /* ScrollWindow uses the window DC, ScrollWindowEx doesn't */ 292 if (flags & SW_SCROLLWNDDCE) 293 { 294 dcxflags = DCX_USESTYLE; 295 296 if (!(Window->pcls->style & (CS_OWNDC|CS_CLASSDC))) 297 dcxflags |= DCX_CACHE; // AH??? wine~ If not Powned or with Class go Cheap! 298 299 if (flags & SW_SCROLLCHILDREN && Window->style & WS_CLIPCHILDREN) 300 dcxflags |= DCX_CACHE|DCX_NOCLIPCHILDREN; 301 } 302 else 303 { 304 /* So in this case ScrollWindowEx uses Cache DC. */ 305 dcxflags = DCX_CACHE|DCX_USESTYLE; 306 if (flags & SW_SCROLLCHILDREN) dcxflags |= DCX_NOCLIPCHILDREN; 307 } 308 309 hDC = UserGetDCEx(Window, 0, dcxflags); 310 if (!hDC) 311 { 312 /* FIXME: SetLastError? */ 313 Result = ERROR; 314 goto Cleanup; 315 } 316 317 rdw_flags = (flags & SW_ERASE) && (flags & SW_INVALIDATE) ? RDW_INVALIDATE | RDW_ERASE : RDW_INVALIDATE ; 318 319 rcCaret = rcScroll; 320 hwndCaret = co_IntFixCaret(Window, &rcCaret, flags); 321 322 Result = UserScrollDC( hDC, 323 dx, 324 dy, 325 &rcScroll, 326 &rcClip, 327 NULL, 328 RgnUpdate, 329 prcUpdate); 330 331 UserReleaseDC(Window, hDC, FALSE); 332 333 /* 334 * Take into account the fact that some damage may have occurred during 335 * the scroll. Keep a copy in hrgnWinupd to be added to hrngUpdate at the end. 336 */ 337 338 RgnTemp = IntSysCreateRectpRgn(0, 0, 0, 0); 339 if (!RgnTemp) 340 { 341 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY); 342 Result = ERROR; 343 goto Cleanup; 344 } 345 346 if (co_IntGetUpdateRgn(Window, RgnTemp, FALSE) != NULLREGION) 347 { 348 PREGION RgnClip = IntSysCreateRectpRgnIndirect(&rcClip); 349 if (RgnClip) 350 { 351 if (hrgnUpdate) 352 { 353 RgnWinupd = IntSysCreateRectpRgn(0, 0, 0, 0); 354 // FIXME: What to do if RgnWinupd == NULL?? 355 IntGdiCombineRgn( RgnWinupd, RgnTemp, 0, RGN_COPY); 356 } 357 358 REGION_bOffsetRgn(RgnTemp, dx, dy); 359 360 IntGdiCombineRgn(RgnTemp, RgnTemp, RgnClip, RGN_AND); 361 362 if (hrgnUpdate) 363 IntGdiCombineRgn( RgnWinupd, RgnWinupd, RgnTemp, RGN_OR ); 364 365 co_UserRedrawWindow(Window, NULL, RgnTemp, rdw_flags ); 366 367 REGION_Delete(RgnClip); 368 } 369 } 370 REGION_Delete(RgnTemp); 371 372 if (flags & SW_SCROLLCHILDREN) 373 { 374 PWND Child; 375 RECTL rcChild; 376 POINT ClientOrigin; 377 USER_REFERENCE_ENTRY WndRef; 378 RECTL rcDummy; 379 LPARAM lParam; 380 381 IntGetClientOrigin(Window, &ClientOrigin); 382 383 for (Child = Window->spwndChild; Child; Child = Child->spwndNext) 384 { 385 rcChild = Child->rcWindow; 386 RECTL_vOffsetRect(&rcChild, -ClientOrigin.x, -ClientOrigin.y); 387 388 /* Adjust window positions */ 389 RECTL_vOffsetRect(&Child->rcWindow, dx, dy); 390 RECTL_vOffsetRect(&Child->rcClient, dx, dy); 391 392 if (!prcScroll || RECTL_bIntersectRect(&rcDummy, &rcChild, &rcScroll)) 393 { 394 UserRefObjectCo(Child, &WndRef); 395 396 if (UserIsDesktopWindow(Window->spwndParent)) 397 lParam = MAKELONG(Child->rcClient.left, Child->rcClient.top); 398 else 399 lParam = MAKELONG(rcChild.left + dx, rcChild.top + dy); 400 401 /* wine sends WM_POSCHANGING, WM_POSCHANGED messages */ 402 /* windows sometimes a WM_MOVE */ 403 co_IntSendMessage(UserHMGetHandle(Child), WM_MOVE, 0, lParam); 404 405 UserDerefObjectCo(Child); 406 } 407 } 408 } 409 410 if (flags & (SW_INVALIDATE | SW_ERASE)) 411 { 412 co_UserRedrawWindow( Window, 413 NULL, 414 RgnUpdate, 415 rdw_flags | /* HACK */ 416 ((flags & SW_SCROLLCHILDREN) ? RDW_ALLCHILDREN : RDW_NOCHILDREN) ); 417 } 418 419 if (hwndCaret && (CaretWnd = UserGetWindowObject(hwndCaret))) 420 { 421 UserRefObjectCo(CaretWnd, &CaretRef); 422 423 co_IntSetCaretPos(rcCaret.left + dx, rcCaret.top + dy); 424 co_UserShowCaret(CaretWnd); 425 426 UserDerefObjectCo(CaretWnd); 427 } 428 429 if (hrgnUpdate && (Result != ERROR)) 430 { 431 /* Give everything back to the caller */ 432 RgnTemp = REGION_LockRgn(hrgnUpdate); 433 /* The handle should still be valid */ 434 ASSERT(RgnTemp); 435 if (RgnWinupd) 436 IntGdiCombineRgn(RgnTemp, RgnUpdate, RgnWinupd, RGN_OR); 437 else 438 IntGdiCombineRgn(RgnTemp, RgnUpdate, NULL, RGN_COPY); 439 REGION_UnlockRgn(RgnTemp); 440 } 441 442 Cleanup: 443 if (RgnWinupd) 444 { 445 REGION_Delete(RgnWinupd); 446 } 447 448 if (RgnUpdate) 449 { 450 REGION_Delete(RgnUpdate); 451 } 452 453 return Result; 454 } 455 456 457 BOOL FASTCALL 458 IntScrollWindow(PWND pWnd, 459 int dx, 460 int dy, 461 CONST RECT *lpRect, 462 CONST RECT *prcClip) 463 { 464 return IntScrollWindowEx( pWnd, dx, dy, lpRect, prcClip, 0, NULL, 465 (lpRect ? 0 : SW_SCROLLCHILDREN) | (SW_ERASE|SW_INVALIDATE|SW_SCROLLWNDDCE)) != ERROR; 466 } 467 468 /* 469 * NtUserScrollDC 470 * 471 * Status 472 * @implemented 473 */ 474 BOOL APIENTRY 475 NtUserScrollDC( 476 HDC hDC, 477 INT dx, 478 INT dy, 479 const RECT *prcUnsafeScroll, 480 const RECT *prcUnsafeClip, 481 HRGN hrgnUpdate, 482 LPRECT prcUnsafeUpdate) 483 { 484 DECLARE_RETURN(DWORD); 485 DWORD Result; 486 NTSTATUS Status = STATUS_SUCCESS; 487 RECTL rcScroll, rcClip, rcUpdate; 488 489 TRACE("Enter NtUserScrollDC\n"); 490 UserEnterExclusive(); 491 492 _SEH2_TRY 493 { 494 if (prcUnsafeScroll) 495 { 496 ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1); 497 rcScroll = *prcUnsafeScroll; 498 } 499 if (prcUnsafeClip) 500 { 501 ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1); 502 rcClip = *prcUnsafeClip; 503 } 504 if (prcUnsafeUpdate) 505 { 506 ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1); 507 } 508 } 509 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 510 { 511 Status = _SEH2_GetExceptionCode(); 512 } 513 _SEH2_END; 514 515 if (!NT_SUCCESS(Status)) 516 { 517 SetLastNtError(Status); 518 RETURN(FALSE); 519 } 520 521 Result = UserScrollDC( hDC, 522 dx, 523 dy, 524 prcUnsafeScroll ? &rcScroll : NULL, 525 prcUnsafeClip ? &rcClip : NULL, 526 hrgnUpdate, 527 NULL, 528 prcUnsafeUpdate ? &rcUpdate : NULL); 529 if(Result == ERROR) 530 { 531 /* FIXME: Only if hRgnUpdate is invalid we should SetLastError(ERROR_INVALID_HANDLE) */ 532 RETURN(FALSE); 533 } 534 535 if (prcUnsafeUpdate) 536 { 537 _SEH2_TRY 538 { 539 *prcUnsafeUpdate = rcUpdate; 540 } 541 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 542 { 543 Status = _SEH2_GetExceptionCode(); 544 } 545 _SEH2_END; 546 547 if (!NT_SUCCESS(Status)) 548 { 549 /* FIXME: SetLastError? */ 550 /* FIXME: correct? We have already scrolled! */ 551 RETURN(FALSE); 552 } 553 } 554 555 RETURN(TRUE); 556 557 CLEANUP: 558 TRACE("Leave NtUserScrollDC, ret=%lu\n",_ret_); 559 UserLeave(); 560 END_CLEANUP; 561 } 562 563 /* 564 * NtUserScrollWindowEx 565 * 566 * Status 567 * @implemented 568 */ 569 570 DWORD APIENTRY 571 NtUserScrollWindowEx( 572 HWND hWnd, 573 INT dx, 574 INT dy, 575 const RECT *prcUnsafeScroll, 576 const RECT *prcUnsafeClip, 577 HRGN hrgnUpdate, 578 LPRECT prcUnsafeUpdate, 579 UINT flags) 580 { 581 DECLARE_RETURN(DWORD); 582 INT Result; 583 NTSTATUS Status = STATUS_SUCCESS; 584 PWND Window = NULL; 585 RECTL rcScroll, rcClip, rcUpdate; 586 USER_REFERENCE_ENTRY Ref; 587 588 TRACE("Enter NtUserScrollWindowEx\n"); 589 UserEnterExclusive(); 590 591 Window = UserGetWindowObject(hWnd); 592 if (!Window || !IntIsWindowDrawable(Window)) 593 { 594 Window = NULL; /* prevent deref at cleanup */ 595 RETURN(ERROR); 596 } 597 UserRefObjectCo(Window, &Ref); 598 599 _SEH2_TRY 600 { 601 if (prcUnsafeScroll) 602 { 603 ProbeForRead(prcUnsafeScroll, sizeof(*prcUnsafeScroll), 1); 604 rcScroll = *prcUnsafeScroll; 605 } 606 607 if (prcUnsafeClip) 608 { 609 ProbeForRead(prcUnsafeClip, sizeof(*prcUnsafeClip), 1); 610 rcClip = *prcUnsafeClip; 611 } 612 } 613 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 614 { 615 Status = _SEH2_GetExceptionCode(); 616 } 617 _SEH2_END; 618 619 if (!NT_SUCCESS(Status)) 620 { 621 SetLastNtError(Status); 622 RETURN(ERROR); 623 } 624 625 Result = IntScrollWindowEx(Window, 626 dx, dy, 627 prcUnsafeScroll ? &rcScroll : NULL, 628 prcUnsafeClip ? &rcClip : NULL, 629 hrgnUpdate, 630 prcUnsafeUpdate ? &rcUpdate : NULL, 631 flags); 632 633 if (prcUnsafeUpdate) 634 { 635 _SEH2_TRY 636 { 637 /* Probe here, to not fail on invalid pointer before scrolling */ 638 ProbeForWrite(prcUnsafeUpdate, sizeof(*prcUnsafeUpdate), 1); 639 *prcUnsafeUpdate = rcUpdate; 640 } 641 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 642 { 643 Status = _SEH2_GetExceptionCode(); 644 } 645 _SEH2_END; 646 647 if (!NT_SUCCESS(Status)) 648 { 649 SetLastNtError(Status); 650 RETURN(ERROR); 651 } 652 } 653 654 RETURN(Result); 655 656 CLEANUP: 657 if (Window) 658 UserDerefObjectCo(Window); 659 660 TRACE("Leave NtUserScrollWindowEx, ret=%lu\n",_ret_); 661 UserLeave(); 662 END_CLEANUP; 663 } 664 665 /* EOF */ 666