reactos/win32ss/gdi/eng/mouse.c
Hervé Poussineau 162de4a0d8 [WIN32SS:ENG] Call display mouse functions only when using hardware pointer
Otherwise, use software pointer functions.

This fixes graphical glitches on cursor change, when the display driver
provides accelerated pointer functions (DrvSetPointerShape/DrvMovePointer),
but refuses to handle a certain cursor.

These graphical glitches may be reproduced:
- by using Voodoo driver SFFT 1.9
- by using framebuf_new.dll instead of framebuf.dll
- by using the panning driver (setting DefaultSettings.XPanning and
  DefaultSettings.YPanning in registry)
2022-10-11 19:04:59 +02:00

848 lines
22 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS win32 subsystem
* PURPOSE: Mouse pointer functions
* FILE: win32ss/gdi/eng/mouse.c
* PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
* Timo Kreuzer (timo.kreuzer@reactos.org)
*/
/* INCLUDES ******************************************************************/
#include <win32k.h>
#define NDEBUG
#include <debug.h>
/* FUNCTIONS *****************************************************************/
__drv_preferredFunction("(see documentation)", "Obsolete, always returns false. ")
BOOL
APIENTRY
EngSetPointerTag(
_In_ HDEV hdev,
_In_opt_ SURFOBJ *psoMask,
_In_opt_ SURFOBJ *psoColor,
_Reserved_ XLATEOBJ *pxlo,
_In_ FLONG fl)
{
// This function is obsolete for Windows 2000 and later.
// This function is still supported, but always returns FALSE.
// www.osr.com/ddk/graphics/gdifncs_4yav.htm
return FALSE;
}
/*
* FUNCTION: Notify the mouse driver that drawing is about to begin in
* a rectangle on a particular surface.
*/
_Requires_lock_held_(*ppdev->hsemDevLock)
BOOL
NTAPI
MouseSafetyOnDrawStart(
_Inout_ PPDEVOBJ ppdev,
_In_ LONG HazardX1,
_In_ LONG HazardY1,
_In_ LONG HazardX2,
_In_ LONG HazardY2)
{
LONG tmp;
GDIPOINTER *pgp;
ASSERT(ppdev != NULL);
ASSERT(ppdev->pSurface != NULL);
pgp = &ppdev->Pointer;
if (pgp->Exclude.right == -1)
{
return FALSE;
}
ppdev->SafetyRemoveCount++;
if (ppdev->SafetyRemoveLevel != 0)
{
return FALSE;
}
if (HazardX1 > HazardX2)
{
tmp = HazardX2;
HazardX2 = HazardX1;
HazardX1 = tmp;
}
if (HazardY1 > HazardY2)
{
tmp = HazardY2;
HazardY2 = HazardY1;
HazardY1 = tmp;
}
if (pgp->Exclude.right >= HazardX1
&& pgp->Exclude.left <= HazardX2
&& pgp->Exclude.bottom >= HazardY1
&& pgp->Exclude.top <= HazardY2)
{
ppdev->SafetyRemoveLevel = ppdev->SafetyRemoveCount;
if (ppdev->flFlags & PDEV_HARDWARE_POINTER)
ppdev->pfnMovePointer(&ppdev->pSurface->SurfObj, -1, -1, NULL);
else if (ppdev->flFlags & PDEV_SOFTWARE_POINTER)
EngMovePointer(&ppdev->pSurface->SurfObj, -1, -1, NULL);
}
return TRUE;
}
/*
* FUNCTION: Notify the mouse driver that drawing has finished on a surface.
*/
_Requires_lock_held_(*ppdev->hsemDevLock)
BOOL
NTAPI
MouseSafetyOnDrawEnd(
_Inout_ PPDEVOBJ ppdev)
{
GDIPOINTER *pgp;
ASSERT(ppdev != NULL);
ASSERT(ppdev->pSurface != NULL);
pgp = &ppdev->Pointer;
if (pgp->Exclude.right == -1)
{
return FALSE;
}
if (--ppdev->SafetyRemoveCount >= ppdev->SafetyRemoveLevel)
{
return FALSE;
}
if (ppdev->flFlags & PDEV_HARDWARE_POINTER)
ppdev->pfnMovePointer(&ppdev->pSurface->SurfObj,
gpsi->ptCursor.x,
gpsi->ptCursor.y,
&pgp->Exclude);
else if (ppdev->flFlags & PDEV_SOFTWARE_POINTER)
EngMovePointer(&ppdev->pSurface->SurfObj,
gpsi->ptCursor.x,
gpsi->ptCursor.y,
&pgp->Exclude);
ppdev->SafetyRemoveLevel = 0;
return TRUE;
}
/* SOFTWARE MOUSE POINTER IMPLEMENTATION **************************************/
VOID
NTAPI
IntHideMousePointer(
_Inout_ PDEVOBJ *ppdev,
_Inout_ SURFOBJ *psoDest)
{
GDIPOINTER *pgp;
POINTL pt;
RECTL rclDest;
POINTL ptlSave;
ASSERT(ppdev);
ASSERT(psoDest);
pgp = &ppdev->Pointer;
if (!pgp->Enabled)
{
return;
}
pgp->Enabled = FALSE;
if (!pgp->psurfSave)
{
DPRINT("No SaveSurface!\n");
return;
}
/* Calculate cursor coordinates */
pt.x = ppdev->ptlPointer.x - pgp->HotSpot.x;
pt.y = ppdev->ptlPointer.y - pgp->HotSpot.y;
rclDest.left = max(pt.x, 0);
rclDest.top = max(pt.y, 0);
rclDest.right = min(pt.x + pgp->Size.cx, psoDest->sizlBitmap.cx);
rclDest.bottom = min(pt.y + pgp->Size.cy, psoDest->sizlBitmap.cy);
ptlSave.x = rclDest.left - pt.x;
ptlSave.y = rclDest.top - pt.y;
IntEngBitBlt(psoDest,
&pgp->psurfSave->SurfObj,
NULL,
NULL,
NULL,
&rclDest,
&ptlSave,
&ptlSave,
NULL,
NULL,
ROP4_FROM_INDEX(R3_OPINDEX_SRCCOPY));
}
VOID
NTAPI
IntShowMousePointer(
_Inout_ PDEVOBJ *ppdev,
_Inout_ SURFOBJ *psoDest)
{
GDIPOINTER *pgp;
POINTL pt;
RECTL rclSurf, rclPointer;
ASSERT(ppdev);
ASSERT(psoDest);
pgp = &ppdev->Pointer;
if (pgp->Enabled)
{
return;
}
pgp->Enabled = TRUE;
/* Check if we have any mouse pointer */
if (!pgp->psurfSave) return;
/* Calculate pointer coordinates */
pt.x = ppdev->ptlPointer.x - pgp->HotSpot.x;
pt.y = ppdev->ptlPointer.y - pgp->HotSpot.y;
/* Calculate the rect on the surface */
rclSurf.left = max(pt.x, 0);
rclSurf.top = max(pt.y, 0);
rclSurf.right = min(pt.x + pgp->Size.cx, psoDest->sizlBitmap.cx);
rclSurf.bottom = min(pt.y + pgp->Size.cy, psoDest->sizlBitmap.cy);
/* Calculate the rect in the pointer bitmap */
rclPointer.left = rclSurf.left - pt.x;
rclPointer.top = rclSurf.top - pt.y;
rclPointer.right = min(pgp->Size.cx, psoDest->sizlBitmap.cx - pt.x);
rclPointer.bottom = min(pgp->Size.cy, psoDest->sizlBitmap.cy - pt.y);
/* Copy the pixels under the cursor to temporary surface. */
IntEngBitBlt(&pgp->psurfSave->SurfObj,
psoDest,
NULL,
NULL,
NULL,
&rclPointer,
(POINTL*)&rclSurf,
NULL,
NULL,
NULL,
ROP4_FROM_INDEX(R3_OPINDEX_SRCCOPY));
/* Blt the pointer on the screen. */
if (pgp->psurfColor)
{
if(!(pgp->flags & SPS_ALPHA))
{
IntEngBitBlt(psoDest,
&pgp->psurfMask->SurfObj,
NULL,
NULL,
NULL,
&rclSurf,
(POINTL*)&rclPointer,
NULL,
NULL,
NULL,
ROP4_SRCAND);
IntEngBitBlt(psoDest,
&pgp->psurfColor->SurfObj,
NULL,
NULL,
NULL,
&rclSurf,
(POINTL*)&rclPointer,
NULL,
NULL,
NULL,
ROP4_SRCINVERT);
}
else
{
BLENDOBJ blendobj = { {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA } };
EXLATEOBJ exlo;
EXLATEOBJ_vInitialize(&exlo,
&gpalRGB,
ppdev->ppalSurf,
0, 0, 0);
IntEngAlphaBlend(psoDest,
&pgp->psurfColor->SurfObj,
NULL,
&exlo.xlo,
&rclSurf,
&rclPointer,
&blendobj);
EXLATEOBJ_vCleanup(&exlo);
}
}
else
{
IntEngBitBlt(psoDest,
&pgp->psurfMask->SurfObj,
NULL,
NULL,
NULL,
&rclSurf,
(POINTL*)&rclPointer,
NULL,
NULL,
NULL,
ROP4_FROM_INDEX(R3_OPINDEX_SRCAND));
rclPointer.top += pgp->Size.cy;
IntEngBitBlt(psoDest,
&pgp->psurfMask->SurfObj,
NULL,
NULL,
NULL,
&rclSurf,
(POINTL*)&rclPointer,
NULL,
NULL,
NULL,
ROP4_FROM_INDEX(R3_OPINDEX_SRCINVERT));
}
}
/*
* @implemented
*/
ULONG
APIENTRY
EngSetPointerShape(
_In_ SURFOBJ *pso,
_In_opt_ SURFOBJ *psoMask,
_In_opt_ SURFOBJ *psoColor,
_In_opt_ XLATEOBJ *pxlo,
_In_ LONG xHot,
_In_ LONG yHot,
_In_ LONG x,
_In_ LONG y,
_In_ RECTL *prcl,
_In_ FLONG fl)
{
PDEVOBJ *ppdev;
GDIPOINTER *pgp;
LONG lDelta = 0;
HBITMAP hbmSave = NULL, hbmColor = NULL, hbmMask = NULL;
PSURFACE psurfSave = NULL, psurfColor = NULL, psurfMask = NULL;
RECTL rectl;
SIZEL sizel = {0, 0};
ASSERT(pso);
ppdev = GDIDEV(pso);
pgp = &ppdev->Pointer;
/* Handle the case where we have no XLATEOBJ */
if (pxlo == NULL)
pxlo = &gexloTrivial.xlo;
/* Do we have any bitmap at all? */
if (psoColor || psoMask)
{
/* Get the size of the new pointer */
if (psoColor)
{
sizel.cx = psoColor->sizlBitmap.cx;
sizel.cy = psoColor->sizlBitmap.cy;
}
else// if (psoMask)
{
sizel.cx = psoMask->sizlBitmap.cx;
sizel.cy = psoMask->sizlBitmap.cy / 2;
}
rectl.left = 0;
rectl.top = 0;
rectl.right = sizel.cx;
rectl.bottom = sizel.cy;
/* Calculate lDelta for our surfaces. */
lDelta = WIDTH_BYTES_ALIGN32(sizel.cx,
BitsPerFormat(pso->iBitmapFormat));
/* Create a bitmap for saving the pixels under the cursor. */
hbmSave = EngCreateBitmap(sizel,
lDelta,
pso->iBitmapFormat,
BMF_TOPDOWN | BMF_NOZEROINIT,
NULL);
psurfSave = SURFACE_ShareLockSurface(hbmSave);
if (!psurfSave) goto failure;
}
if (psoColor)
{
if (fl & SPS_ALPHA)
{
/* Always store the alpha cursor in RGB. */
EXLATEOBJ exloSrcRGB;
PEXLATEOBJ pexlo;
pexlo = CONTAINING_RECORD(pxlo, EXLATEOBJ, xlo);
EXLATEOBJ_vInitialize(&exloSrcRGB, pexlo->ppalSrc, &gpalRGB, 0, 0, 0);
hbmColor = EngCreateBitmap(psoColor->sizlBitmap,
WIDTH_BYTES_ALIGN32(sizel.cx, 32),
BMF_32BPP,
BMF_TOPDOWN | BMF_NOZEROINIT,
NULL);
psurfColor = SURFACE_ShareLockSurface(hbmColor);
if (!psurfColor) goto failure;
/* Now copy the given bitmap. */
rectl.bottom = psoColor->sizlBitmap.cy;
IntEngCopyBits(&psurfColor->SurfObj,
psoColor,
NULL,
&exloSrcRGB.xlo,
&rectl,
(POINTL*)&rectl);
EXLATEOBJ_vCleanup(&exloSrcRGB);
}
else
{
/* Color bitmap must have the same format as the dest surface */
if (psoColor->iBitmapFormat != pso->iBitmapFormat)
{
DPRINT1("Screen surface and cursor color bitmap format don't match!.\n");
goto failure;
}
/* Create a bitmap to copy the color bitmap to */
hbmColor = EngCreateBitmap(psoColor->sizlBitmap,
lDelta,
pso->iBitmapFormat,
BMF_TOPDOWN | BMF_NOZEROINIT,
NULL);
psurfColor = SURFACE_ShareLockSurface(hbmColor);
if (!psurfColor) goto failure;
/* Now copy the given bitmap. */
rectl.bottom = psoColor->sizlBitmap.cy;
IntEngCopyBits(&psurfColor->SurfObj,
psoColor,
NULL,
pxlo,
&rectl,
(POINTL*)&rectl);
}
}
/* Create a mask surface */
if (psoMask)
{
EXLATEOBJ exlo;
PPALETTE ppal;
lDelta = WIDTH_BYTES_ALIGN32(sizel.cx, BitsPerFormat(pso->iBitmapFormat));
/* Create a bitmap for the mask */
hbmMask = EngCreateBitmap(psoMask->sizlBitmap,
lDelta,
pso->iBitmapFormat,
BMF_TOPDOWN | BMF_NOZEROINIT,
NULL);
psurfMask = SURFACE_ShareLockSurface(hbmMask);
if (!psurfMask) goto failure;
/* Initialize an EXLATEOBJ */
ppal = PALETTE_ShareLockPalette(ppdev->devinfo.hpalDefault);
EXLATEOBJ_vInitialize(&exlo,
gppalMono,
ppal,
0,
RGB(0xff,0xff,0xff),
RGB(0,0,0));
/* Copy the mask bitmap */
rectl.bottom = psoMask->sizlBitmap.cy;
IntEngCopyBits(&psurfMask->SurfObj,
psoMask,
NULL,
&exlo.xlo,
&rectl,
(POINTL*)&rectl);
/* Cleanup */
EXLATEOBJ_vCleanup(&exlo);
if (ppal) PALETTE_ShareUnlockPalette(ppal);
}
/* Hide mouse pointer */
IntHideMousePointer(ppdev, pso);
/* Free old color bitmap */
if (pgp->psurfColor)
{
EngDeleteSurface(pgp->psurfColor->BaseObject.hHmgr);
SURFACE_ShareUnlockSurface(pgp->psurfColor);
pgp->psurfColor = NULL;
}
/* Free old mask bitmap */
if (pgp->psurfMask)
{
EngDeleteSurface(pgp->psurfMask->BaseObject.hHmgr);
SURFACE_ShareUnlockSurface(pgp->psurfMask);
pgp->psurfMask = NULL;
}
/* Free old save bitmap */
if (pgp->psurfSave)
{
EngDeleteSurface(pgp->psurfSave->BaseObject.hHmgr);
SURFACE_ShareUnlockSurface(pgp->psurfSave);
pgp->psurfSave = NULL;
}
/* See if we are being asked to hide the pointer. */
if (psoMask == NULL && psoColor == NULL)
{
/* We're done */
return SPS_ACCEPT_NOEXCLUDE;
}
/* Now set the new cursor */
pgp->psurfColor = psurfColor;
pgp->psurfMask = psurfMask;
pgp->psurfSave = psurfSave;
pgp->HotSpot.x = xHot;
pgp->HotSpot.y = yHot;
pgp->Size = sizel;
pgp->flags = fl;
if (x != -1)
{
ppdev->ptlPointer.x = x;
ppdev->ptlPointer.y = y;
IntShowMousePointer(ppdev, pso);
if (prcl != NULL)
{
prcl->left = x - pgp->HotSpot.x;
prcl->top = y - pgp->HotSpot.x;
prcl->right = prcl->left + pgp->Size.cx;
prcl->bottom = prcl->top + pgp->Size.cy;
}
}
else if (prcl != NULL)
{
prcl->left = prcl->top = prcl->right = prcl->bottom = -1;
}
return SPS_ACCEPT_NOEXCLUDE;
failure:
/* Cleanup surfaces */
if (hbmMask) EngDeleteSurface((HSURF)hbmMask);
if (psurfMask) SURFACE_ShareUnlockSurface(psurfMask);
if (hbmColor) EngDeleteSurface((HSURF)hbmColor);
if (psurfColor) SURFACE_ShareUnlockSurface(psurfColor);
if (hbmSave) EngDeleteSurface((HSURF)hbmSave);
if (psurfSave) SURFACE_ShareUnlockSurface(psurfSave);
return SPS_ERROR;
}
/*
* @implemented
*/
VOID
APIENTRY
EngMovePointer(
_In_ SURFOBJ *pso,
_In_ LONG x,
_In_ LONG y,
_In_ RECTL *prcl)
{
PDEVOBJ *ppdev;
GDIPOINTER *pgp;
ASSERT(pso);
ppdev = GDIDEV(pso);
ASSERT(ppdev);
pgp = &ppdev->Pointer;
IntHideMousePointer(ppdev, pso);
ppdev->ptlPointer.x = x;
ppdev->ptlPointer.y = y;
if (x != -1)
{
IntShowMousePointer(ppdev, pso);
if (prcl != NULL)
{
prcl->left = x - pgp->HotSpot.x;
prcl->top = y - pgp->HotSpot.y;
prcl->right = prcl->left + pgp->Size.cx;
prcl->bottom = prcl->top + pgp->Size.cy;
}
}
else if (prcl != NULL)
{
prcl->left = prcl->top = prcl->right = prcl->bottom = -1;
}
}
ULONG
NTAPI
IntEngSetPointerShape(
_In_ SURFOBJ *pso,
_In_opt_ SURFOBJ *psoMask,
_In_opt_ SURFOBJ *psoColor,
_In_opt_ XLATEOBJ *pxlo,
_In_ LONG xHot,
_In_ LONG yHot,
_In_ LONG x,
_In_ LONG y,
_In_ RECTL *prcl,
_In_ FLONG fl)
{
ULONG ulResult = SPS_DECLINE;
PFN_DrvSetPointerShape pfnSetPointerShape;
PPDEVOBJ ppdev = GDIDEV(pso);
BOOL bHardwarePointer = FALSE;
BOOL bSoftwarePointer = TRUE;
pfnSetPointerShape = GDIDEVFUNCS(pso).SetPointerShape;
if (pfnSetPointerShape)
{
/* Drivers expect to get an XLATEOBJ */
if (pxlo == NULL)
pxlo = &gexloTrivial.xlo;
/* Call the driver */
ulResult = pfnSetPointerShape(pso,
psoMask,
psoColor,
pxlo,
xHot,
yHot,
x,
y,
prcl,
fl);
/* Check if the driver accepted it */
if (ulResult == SPS_ACCEPT_NOEXCLUDE)
bHardwarePointer = TRUE;
bSoftwarePointer = !bHardwarePointer;
}
if (bSoftwarePointer)
{
/* Set software pointer */
ulResult = EngSetPointerShape(pso,
psoMask,
psoColor,
pxlo,
xHot,
yHot,
x,
y,
prcl,
fl);
}
if (!bSoftwarePointer && ppdev->flFlags & PDEV_SOFTWARE_POINTER)
{
/* Disable software pointer */
EngMovePointer(pso, -1, -1, NULL);
}
if (!bHardwarePointer && ppdev->flFlags & PDEV_HARDWARE_POINTER)
{
/* Disable hardware pointer */
ppdev->pfnMovePointer(pso, -1, -1, NULL);
}
/* Update flags */
if (bSoftwarePointer)
ppdev->flFlags |= PDEV_SOFTWARE_POINTER;
else
ppdev->flFlags &= ~PDEV_SOFTWARE_POINTER;
if (bHardwarePointer)
ppdev->flFlags |= PDEV_HARDWARE_POINTER;
else
ppdev->flFlags &= ~PDEV_HARDWARE_POINTER;
return ulResult;
}
ULONG
NTAPI
GreSetPointerShape(
_In_ HDC hdc,
_In_opt_ HBITMAP hbmMask,
_In_opt_ HBITMAP hbmColor,
_In_ LONG xHot,
_In_ LONG yHot,
_In_ LONG x,
_In_ LONG y,
_In_ FLONG fl)
{
PDC pdc;
PSURFACE psurf, psurfMask, psurfColor;
EXLATEOBJ exlo;
ULONG ulResult = 0;
pdc = DC_LockDc(hdc);
if (!pdc)
{
DPRINT1("Failed to lock the DC.\n");
return 0;
}
ASSERT(pdc->dctype == DCTYPE_DIRECT);
EngAcquireSemaphore(pdc->ppdev->hsemDevLock);
/* We're not sure DC surface is the good one */
psurf = pdc->ppdev->pSurface;
if (!psurf)
{
DPRINT1("DC has no surface.\n");
EngReleaseSemaphore(pdc->ppdev->hsemDevLock);
DC_UnlockDc(pdc);
return 0;
}
/* Lock the mask bitmap */
if (hbmMask)
{
psurfMask = SURFACE_ShareLockSurface(hbmMask);
}
else
{
//ASSERT(fl & SPS_ALPHA);
psurfMask = NULL;
}
/* Check for color bitmap */
if (hbmColor)
{
/* We have one, lock it */
psurfColor = SURFACE_ShareLockSurface(hbmColor);
if (psurfColor)
{
/* Create an XLATEOBJ, no mono support */
EXLATEOBJ_vInitialize(&exlo, psurfColor->ppal, psurf->ppal, 0, 0, 0);
}
}
else
psurfColor = NULL;
/* We must have a valid surface in case of alpha bitmap */
ASSERT(((fl & SPS_ALPHA) && psurfColor) || !(fl & SPS_ALPHA));
/* Call the driver or eng function */
ulResult = IntEngSetPointerShape(&psurf->SurfObj,
psurfMask ? &psurfMask->SurfObj : NULL,
psurfColor ? &psurfColor->SurfObj : NULL,
psurfColor ? &exlo.xlo : NULL,
xHot,
yHot,
x,
y,
&pdc->ppdev->Pointer.Exclude,
fl | SPS_CHANGE);
/* Cleanup */
if (psurfColor)
{
EXLATEOBJ_vCleanup(&exlo);
SURFACE_ShareUnlockSurface(psurfColor);
}
if (psurfMask)
SURFACE_ShareUnlockSurface(psurfMask);
EngReleaseSemaphore(pdc->ppdev->hsemDevLock);
/* Unlock the DC */
DC_UnlockDc(pdc);
/* Return result */
return ulResult;
}
VOID
NTAPI
GreMovePointer(
_In_ HDC hdc,
_In_ LONG x,
_In_ LONG y)
{
PDC pdc;
PRECTL prcl;
/* Lock the DC */
pdc = DC_LockDc(hdc);
if (!pdc)
{
DPRINT1("Failed to lock the DC.\n");
return;
}
ASSERT(pdc->dctype == DCTYPE_DIRECT);
/* Acquire PDEV lock */
EngAcquireSemaphore(pdc->ppdev->hsemDevLock);
/* Check if we need to move it */
if(pdc->ppdev->SafetyRemoveLevel == 0)
{
SURFOBJ* pso = &pdc->ppdev->pSurface->SurfObj;
/* Store the cursor exclude position in the PDEV */
prcl = &pdc->ppdev->Pointer.Exclude;
/* Send new position of the hot spot of the pointer (will likely redraw cursor) */
if (pdc->ppdev->flFlags & PDEV_HARDWARE_POINTER)
pdc->ppdev->pfnMovePointer(pso, x, y, prcl);
else if (pdc->ppdev->flFlags & PDEV_SOFTWARE_POINTER)
EngMovePointer(pso, x, y, prcl);
/* If panning device, and we're not hiding the cursor, notify cursor's current position.
* (driver may already have been called if it also supports hardware pointers) */
if (pdc->ppdev->devinfo.flGraphicsCaps & GCAPS_PANNING && y >= 0)
pdc->ppdev->pfnMovePointer(pso, x, y - pso->sizlBitmap.cy, NULL);
}
/* Release PDEV lock */
EngReleaseSemaphore(pdc->ppdev->hsemDevLock);
/* Unlock the DC */
DC_UnlockDc(pdc);
}
/* EOF */