mirror of https://github.com/lianthony/NT4.0
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.
1916 lines
60 KiB
1916 lines
60 KiB
/****************************** Module Header ******************************\
|
|
* Module Name: fullscr.c
|
|
*
|
|
* Copyright (c) 1985-96, Microsoft Corporation
|
|
*
|
|
* This module contains all the fullscreen code for the USERSRV.DLL.
|
|
*
|
|
* History:
|
|
* 12-Dec-1991 mikeke Created
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#include "ntddvdeo.h"
|
|
|
|
/***************************************************************************\
|
|
* We can only have one fullscreen window at a time so this information can
|
|
* be store globally
|
|
*
|
|
* We partially use busy waiting to set the state of the hardware.
|
|
* The problem is that while we are in the middle of a fullscreen switch,
|
|
* we leave the critical section ! So someone else could come in and change
|
|
* the state of the fullscreen stuff.
|
|
* In order to keep the system from getting confused about the state of the
|
|
* device, we actually "post" the request.
|
|
*
|
|
* What we do with external requests for switching, is that we will do busy
|
|
* waiting on these state variables.
|
|
* So an app won't be able to request a fullscreen switch while one is under
|
|
* way. This is a way to make the system completely reentrant for state
|
|
* switches.
|
|
*
|
|
* The state variables themselves can only be touched while owning the
|
|
* critical section. We are guaranteed that we will not busy wait forever
|
|
* since the switch operations (although long) will eventually finish.
|
|
*
|
|
* 20-Mar-1996 andreva Created
|
|
\***************************************************************************/
|
|
|
|
#ifdef ANDREVA_DBG
|
|
LONG TraceFullscreenSwitch = 1;
|
|
#else
|
|
LONG TraceFullscreenSwitch = 0;
|
|
#endif
|
|
|
|
#define NOSWITCHER ((HANDLE)-1)
|
|
|
|
HANDLE idSwitcher = NOSWITCHER;
|
|
BOOL fRedoFullScreenSwitch = FALSE;
|
|
BOOL fGdiEnabled = TRUE;
|
|
PWND gspwndShouldBeForeground = NULL;
|
|
BYTE gbFullScreen = GDIFULLSCREEN;
|
|
POINT gptCursorFullScreen;
|
|
|
|
extern POINT gptSSCursor;
|
|
|
|
void SetVDMCursorBounds(LPRECT lprc);
|
|
|
|
extern BOOL bMultipleDisplaySystem;
|
|
|
|
/***************************************************************************\
|
|
* FullScreenCleanup
|
|
*
|
|
* This is called during thread cleanup, we test to see if we died during a
|
|
* full screen switch and switch back to the GDI desktop if we did.
|
|
*
|
|
* NOTE:
|
|
* All the variables touched here are guaranteed to be touched under
|
|
* the CritSect.
|
|
*
|
|
* 12-Dec-1991 mikeke Created
|
|
\***************************************************************************/
|
|
|
|
void FullScreenCleanup()
|
|
{
|
|
if (PsGetCurrentThread()->Cid.UniqueThread == idSwitcher) {
|
|
|
|
/*
|
|
* correct the full screen state
|
|
*/
|
|
|
|
if (fGdiEnabled) {
|
|
|
|
TRACE_SWITCH(("Switching: FullScreenCleanup: Gdi Enabled\n"));
|
|
|
|
/*
|
|
* gdi is enabled, we are switching away from gdi the only thing we
|
|
* could have done so far is locking the screen so unlock it.
|
|
*/
|
|
gfLockFullScreen = FALSE;
|
|
xxxLockWindowUpdate2(NULL, TRUE);
|
|
|
|
} else {
|
|
|
|
/*
|
|
* GDI is not enabled . This means we were switching from a full
|
|
* screen to another fullscreen or back to GDI. Or we could have
|
|
* disabled gdi and sent a message to the new full screen which
|
|
* never got completed.
|
|
*
|
|
* In any case this probably means the fullscreen guy is gone so
|
|
* we will switch back to gdi.
|
|
*
|
|
* delete any left over saved screen state stuff
|
|
* set the fullscreen to nothing and then send a message that will
|
|
* cause us to switch back to the gdi desktop
|
|
*/
|
|
TL tlpwndT;
|
|
|
|
TRACE_SWITCH(("Switching: FullScreenCleanup: Gdi Disabled\n"));
|
|
|
|
Unlock(&gspwndFullScreen);
|
|
gbFullScreen = FULLSCREEN;
|
|
|
|
ThreadLock(grpdeskRitInput->pDeskInfo->spwnd, &tlpwndT);
|
|
xxxSendNotifyMessage(
|
|
grpdeskRitInput->pDeskInfo->spwnd, WM_FULLSCREEN,
|
|
GDIFULLSCREEN, (LONG)HW(grpdeskRitInput->pDeskInfo->spwnd));
|
|
ThreadUnlock(&tlpwndT);
|
|
}
|
|
|
|
idSwitcher = NOSWITCHER;
|
|
fRedoFullScreenSwitch = FALSE;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxMakeWindowForegroundWithState
|
|
*
|
|
* Syncs the screen graphics mode with the mode of the specified (foreground)
|
|
* window
|
|
*
|
|
* We make sure only one thread is going through this code by checking
|
|
* idSwitcher. If idSwticher is non-null someone is allready in this code
|
|
*
|
|
* 12-Dec-1991 mikeke Created
|
|
\***************************************************************************/
|
|
|
|
void xxxMakeWindowForegroundWithState(
|
|
PWND pwnd,
|
|
BYTE NewState)
|
|
{
|
|
PWND pwndNewFG;
|
|
TL tlpwndNewFG;
|
|
|
|
TRACE_SWITCH(("Switching: xxxMakeWindowForeground: Enter\n"));
|
|
TRACE_SWITCH(("\t \t pwnd = %08lx\n", pwnd));
|
|
TRACE_SWITCH(("\t \t NewState = %d\n", NewState));
|
|
|
|
CheckLock(pwnd);
|
|
|
|
/*
|
|
* If we should switch to a specific window save that window
|
|
*/
|
|
|
|
if (pwnd != NULL) {
|
|
|
|
if (NewState == GDIFULLSCREEN) {
|
|
Lock(&gspwndShouldBeForeground, pwnd);
|
|
}
|
|
|
|
/*
|
|
* Change to the new state
|
|
*/
|
|
|
|
pwnd->bFullScreen = NewState;
|
|
|
|
if (NewState == FULLSCREEN &&
|
|
(gpqForeground == NULL ||
|
|
gpqForeground->spwndActive != pwnd)) {
|
|
|
|
pwnd->bFullScreen = FULLSCREENMIN;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Since we leave the critical section during the switch, some other
|
|
// thread could come into this routine and request a switch. The global
|
|
// will be reset, and we will use the loop to perform the next switch.
|
|
//
|
|
|
|
if (idSwitcher != NOSWITCHER) {
|
|
fRedoFullScreenSwitch = TRUE;
|
|
TRACE_SWITCH(("Switching: xxxMakeWindowForeground was posted: Exit\n"));
|
|
|
|
return;
|
|
}
|
|
|
|
UserAssert(!fRedoFullScreenSwitch);
|
|
idSwitcher = PsGetCurrentThread()->Cid.UniqueThread;
|
|
|
|
/*
|
|
* We loop, switching full screens until all states have stabilized
|
|
*/
|
|
|
|
while (TRUE) {
|
|
/*
|
|
* figure out who should be foreground
|
|
*/
|
|
fRedoFullScreenSwitch = FALSE;
|
|
|
|
if (gspwndShouldBeForeground != NULL) {
|
|
pwndNewFG = gspwndShouldBeForeground;
|
|
Unlock(&gspwndShouldBeForeground);
|
|
} else {
|
|
if (gpqForeground != NULL &&
|
|
gpqForeground->spwndActive != NULL) {
|
|
|
|
pwndNewFG = gpqForeground->spwndActive;
|
|
|
|
if (pwndNewFG->bFullScreen == WINDOWED ||
|
|
pwndNewFG->bFullScreen == FULLSCREENMIN) {
|
|
|
|
pwndNewFG = PWNDDESKTOP(pwndNewFG);
|
|
}
|
|
} else {
|
|
/*
|
|
* No active window, switch to current desktop
|
|
*/
|
|
pwndNewFG = grpdeskRitInput->pDeskInfo->spwnd;
|
|
}
|
|
|
|
/*
|
|
* We don't need to switch if the right window is already foreground
|
|
*/
|
|
if (pwndNewFG == gspwndFullScreen) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
ThreadLock(pwndNewFG, &tlpwndNewFG);
|
|
|
|
{
|
|
BYTE bStateNew = pwndNewFG->bFullScreen;
|
|
TL tlpwndOldFG;
|
|
PWND pwndOldFG = gspwndFullScreen;
|
|
BYTE bStateOld = gbFullScreen;
|
|
|
|
ThreadLock(pwndOldFG, &tlpwndOldFG);
|
|
|
|
Lock(&gspwndFullScreen, pwndNewFG);
|
|
gbFullScreen = bStateNew;
|
|
|
|
UserAssert(!HMIsMarkDestroy(gspwndFullScreen));
|
|
|
|
/*
|
|
* If the old screen was GDIFULLSCREEN and we are switching to
|
|
* GDIFULLSCREEN then just repaint
|
|
*/
|
|
if (pwndOldFG != NULL &&
|
|
bStateOld == GDIFULLSCREEN &&
|
|
bStateNew == GDIFULLSCREEN) {
|
|
|
|
xxxRedrawWindow(pwndNewFG, NULL, NULL,
|
|
RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE | RDW_ERASENOW);
|
|
|
|
ThreadUnlock(&tlpwndOldFG);
|
|
|
|
} else {
|
|
|
|
/*
|
|
* tell old 'foreground' window it is loosing control of the screen
|
|
*/
|
|
if (pwndOldFG != NULL) {
|
|
switch (bStateOld) {
|
|
case FULLSCREEN:
|
|
if (pwndOldFG->bFullScreen == FULLSCREEN) {
|
|
pwndOldFG->bFullScreen = FULLSCREENMIN;
|
|
}
|
|
xxxSendMessage(pwndOldFG, WM_FULLSCREEN, FALSE, 0);
|
|
xxxCapture(GETPTI(pwndOldFG), NULL, FULLSCREEN_CAPTURE);
|
|
SetVDMCursorBounds(NULL);
|
|
break;
|
|
|
|
case GDIFULLSCREEN:
|
|
/*
|
|
* Lock out other windows from drawing while we are fullscreen
|
|
*/
|
|
xxxLockWindowUpdate2(pwndOldFG, TRUE);
|
|
gfLockFullScreen = TRUE;
|
|
gptCursorFullScreen = ptCursor;
|
|
UserAssert(fGdiEnabled == TRUE);
|
|
bDisableDisplay(gpDispInfo->hDev);
|
|
fGdiEnabled = FALSE;
|
|
break;
|
|
|
|
default:
|
|
RIPMSG0(RIP_ERROR, "xxxDoFullScreenSwitch: bad screen state");
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndOldFG);
|
|
|
|
switch(bStateNew) {
|
|
case FULLSCREEN:
|
|
xxxCapture(GETPTI(pwndNewFG), pwndNewFG, FULLSCREEN_CAPTURE);
|
|
xxxSendMessage(pwndNewFG, WM_FULLSCREEN, TRUE, 0);
|
|
break;
|
|
|
|
case GDIFULLSCREEN:
|
|
|
|
UserAssert(fGdiEnabled == FALSE);
|
|
vEnableDisplay(gpDispInfo->hDev);
|
|
fGdiEnabled = TRUE;
|
|
|
|
/*
|
|
* Return the cursor to it's old state. Reset the screen saver mouse
|
|
* position or it'll go away by accident.
|
|
*/
|
|
gpqCursor = NULL;
|
|
gptSSCursor = gptCursorFullScreen;
|
|
InternalSetCursorPos(gptCursorFullScreen.x,
|
|
gptCursorFullScreen.y,
|
|
grpdeskRitInput);
|
|
|
|
if (gpqCursor && gpqCursor->spcurCurrent && SYSMET(MOUSEPRESENT)) {
|
|
GreSetPointer(gpDispInfo->hDev, (PCURSINFO)&(gpqCursor->spcurCurrent->xHotspot),0);
|
|
}
|
|
|
|
gfLockFullScreen = FALSE;
|
|
xxxLockWindowUpdate2(NULL, TRUE);
|
|
|
|
xxxRedrawWindow(pwndNewFG, NULL, NULL,
|
|
RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_ERASE | RDW_ERASENOW);
|
|
break;
|
|
|
|
default:
|
|
RIPMSG0(RIP_ERROR, "xxxDoFullScreenSwitch: bad screen state\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ThreadUnlock(&tlpwndNewFG);
|
|
|
|
if (!fRedoFullScreenSwitch) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
TRACE_SWITCH(("Switching: xxxMakeWindowForeground: Exit\n"));
|
|
|
|
idSwitcher = NOSWITCHER;
|
|
return;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* Is this still TRUE ?
|
|
*
|
|
* When a window becomes FULLSCREEN, it is minimized and
|
|
* treated like any other minimized window. Whenever the
|
|
* minimized window is restored, by double clicking, menu
|
|
* or keyboard, it remains minimized and the application
|
|
* is given control of the screen device.
|
|
*
|
|
* 12-Dec-1991 mikeke Created
|
|
\***************************************************************************/
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* NtUserFullscreenControl
|
|
*
|
|
* routine to support console calls to the video driver
|
|
*
|
|
* 01-Sep-1995 andreva Created
|
|
\***************************************************************************/
|
|
|
|
NTSTATUS
|
|
NtUserFullscreenControl(
|
|
IN FULLSCREENCONTROL FullscreenCommand,
|
|
PVOID FullscreenInput,
|
|
DWORD FullscreenInputLength,
|
|
PVOID FullscreenOutput,
|
|
PULONG FullscreenOutputLength)
|
|
{
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
ULONG BytesReturned;
|
|
PVOID pCapBuffer = NULL;
|
|
ULONG cCapBuffer = 0;
|
|
ULONG ioctl;
|
|
|
|
//
|
|
// First validate the ioctl
|
|
//
|
|
|
|
switch(FullscreenCommand) {
|
|
|
|
case FullscreenControlEnable:
|
|
ioctl = IOCTL_VIDEO_ENABLE_VDM;
|
|
TRACE_SWITCH(("Switching: FullscreenControlEnable\n"));
|
|
break;
|
|
|
|
case FullscreenControlDisable:
|
|
ioctl = IOCTL_VIDEO_DISABLE_VDM;
|
|
TRACE_SWITCH(("Switching: FullscreenControlDisable\n"));
|
|
break;
|
|
|
|
case FullscreenControlSetCursorPosition:
|
|
ioctl = IOCTL_VIDEO_SET_CURSOR_POSITION;
|
|
TRACE_SWITCH(("Switching: FullscreenControlSetCursorPosition\n"));
|
|
break;
|
|
|
|
case FullscreenControlSetCursorAttributes:
|
|
ioctl = IOCTL_VIDEO_SET_CURSOR_ATTR;
|
|
TRACE_SWITCH(("Switching: FullscreenControlSetCursorAttributes\n"));
|
|
break;
|
|
|
|
case FullscreenControlRegisterVdm:
|
|
ioctl = IOCTL_VIDEO_REGISTER_VDM;
|
|
TRACE_SWITCH(("Switching: FullscreenControlRegisterVdm\n"));
|
|
break;
|
|
|
|
case FullscreenControlSetPalette:
|
|
ioctl = IOCTL_VIDEO_SET_PALETTE_REGISTERS;
|
|
TRACE_SWITCH(("Switching: FullscreenControlSetPalette\n"));
|
|
break;
|
|
|
|
case FullscreenControlSetColors:
|
|
ioctl = IOCTL_VIDEO_SET_COLOR_REGISTERS;
|
|
TRACE_SWITCH(("Switching: FullscreenControlSetColors\n"));
|
|
break;
|
|
|
|
case FullscreenControlLoadFont:
|
|
ioctl = IOCTL_VIDEO_LOAD_AND_SET_FONT;
|
|
TRACE_SWITCH(("Switching: FullscreenControlLoadFont\n"));
|
|
break;
|
|
|
|
case FullscreenControlRestoreHardwareState:
|
|
ioctl = IOCTL_VIDEO_RESTORE_HARDWARE_STATE;
|
|
TRACE_SWITCH(("Switching: FullscreenControlRestoreHardwareState\n"));
|
|
break;
|
|
|
|
case FullscreenControlSaveHardwareState:
|
|
ioctl = IOCTL_VIDEO_SAVE_HARDWARE_STATE;
|
|
TRACE_SWITCH(("Switching: FullscreenControlSaveHardwareState\n"));
|
|
break;
|
|
|
|
case FullscreenControlCopyFrameBuffer:
|
|
case FullscreenControlReadFromFrameBuffer:
|
|
case FullscreenControlWriteToFrameBuffer:
|
|
case FullscreenControlReverseMousePointer:
|
|
// TRACE_SWITCH(("Switching: Fullscreen output command\n"));
|
|
ioctl = 0;
|
|
break;
|
|
|
|
case FullscreenControlSetMode:
|
|
TRACE_SWITCH(("Switching: Fullscreen setmode command\n"));
|
|
ioctl = 0;
|
|
break;
|
|
|
|
default:
|
|
RIPMSG0(RIP_ERROR, "NtUserFullscreenControl: invalid IOCTL\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
|
|
}
|
|
|
|
EnterCrit();
|
|
|
|
//
|
|
// Check if the system is in a fullscreen state, where the IOCTL can
|
|
// be safely sent to the device, or the device memory can be
|
|
// manipulated.
|
|
//
|
|
|
|
if ((fGdiEnabled == TRUE) &&
|
|
(FullscreenCommand != FullscreenControlRegisterVdm))
|
|
{
|
|
RIPMSG0(RIP_ERROR, "Fullscreen control - Not in fullscreen !\n");
|
|
LeaveCrit();
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
|
|
//
|
|
// If this is a frame buffer function, that we can just deal with the
|
|
// device directly, and not send any IOCTL to the device.
|
|
//
|
|
|
|
if (ioctl == 0)
|
|
{
|
|
//
|
|
// First get the frame buffer pointer for the device.
|
|
//
|
|
|
|
PUCHAR pFrameBuf = gpFullscreenFrameBufPtr;
|
|
PCHAR_INFO pCharInfo;
|
|
|
|
CHAR Attribute;
|
|
|
|
//
|
|
// Assume success for all these operations.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
switch(FullscreenCommand) {
|
|
|
|
case FullscreenControlSetMode:
|
|
#if 1
|
|
//
|
|
// BUGBUG temporary workaround to get Alt-Tab working.
|
|
//
|
|
|
|
{
|
|
LPDEVMODEW pDevmode = gphysDevInfo[0].devmodeInfo;
|
|
VIDEO_MODE VideoMode;
|
|
BOOLEAN modeFound = FALSE;
|
|
ULONG BytesReturned;
|
|
ULONG i;
|
|
|
|
//
|
|
// Fullscreen VGA modes require us to call the miniport driver
|
|
// directly.
|
|
//
|
|
// Lets check the VGA Device handle, which is in the first entry
|
|
//
|
|
|
|
if (gphysDevInfo[0].pDeviceHandle == NULL)
|
|
{
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// NOTE We know that if there is a vgacompatible device, then
|
|
// there are some text modes for it.
|
|
//
|
|
// NOTE !!!
|
|
// As a hack, lets use the mode number we stored in the DEVMODE
|
|
// a field we don't use
|
|
//
|
|
|
|
for (i = 0;
|
|
i < gphysDevInfo[0].cbdevmodeInfo;
|
|
i += sizeof(DEVMODEW), pDevmode += 1) {
|
|
|
|
// !!! BUGBUG lpDevmode needs to be captured
|
|
|
|
if ((pDevmode->dmPelsWidth == ((LPDEVMODEW) FullscreenInput)->dmPelsWidth) &&
|
|
(pDevmode->dmPelsHeight == ((LPDEVMODEW) FullscreenInput)->dmPelsHeight))
|
|
{
|
|
VideoMode.RequestedMode = (ULONG) ((LPDEVMODEW) FullscreenInput)->dmOrientation;
|
|
modeFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (modeFound == FALSE)
|
|
{
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: Console [assed in bad DEVMODE\n");
|
|
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We have the mode number.
|
|
// Call the driver to set the mode
|
|
//
|
|
|
|
Status = GreDeviceIoControl(gphysDevInfo[0].pDeviceHandle,
|
|
IOCTL_VIDEO_SET_CURRENT_MODE,
|
|
&VideoMode,
|
|
sizeof(VideoMode),
|
|
NULL,
|
|
0,
|
|
&BytesReturned);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// We also map the memory so we can use it to
|
|
// process string commands from the console
|
|
//
|
|
|
|
VIDEO_MEMORY FrameBufferMap;
|
|
VIDEO_MEMORY_INFORMATION FrameBufferInfo;
|
|
|
|
FrameBufferMap.RequestedVirtualAddress = NULL;
|
|
|
|
Status = GreDeviceIoControl(gphysDevInfo[0].pDeviceHandle,
|
|
IOCTL_VIDEO_MAP_VIDEO_MEMORY,
|
|
&FrameBufferMap,
|
|
sizeof(FrameBufferMap),
|
|
&FrameBufferInfo,
|
|
sizeof(FrameBufferInfo),
|
|
&BytesReturned);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// get address of frame buffer
|
|
//
|
|
|
|
gpFullscreenFrameBufPtr = (PUCHAR) FrameBufferInfo.FrameBufferBase;
|
|
}
|
|
else
|
|
{
|
|
|
|
RIPMSG0(RIP_ERROR, "Fullscreen setmode: memory mapping failed\n");
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
RIPMSG0(RIP_ERROR, "Fullscreen setmode: fullscreen MODESET failed\n");
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case FullscreenControlCopyFrameBuffer:
|
|
|
|
TRACE_SWITCH(("Switching: FullscreenControlCopyFrameBuffer\n"));
|
|
|
|
UserAssert(FullscreenInputLength == (DWORD) FullscreenOutputLength);
|
|
|
|
RtlMoveMemory(pFrameBuf + (ULONG)FullscreenOutput,
|
|
pFrameBuf + (ULONG)FullscreenInput,
|
|
FullscreenInputLength);
|
|
|
|
break;
|
|
|
|
case FullscreenControlReadFromFrameBuffer:
|
|
|
|
TRACE_SWITCH(("Switching: FullscreenControlReadFromFrameBuffer\n"));
|
|
|
|
pFrameBuf += (ULONG) FullscreenInput;
|
|
pCharInfo = (PCHAR_INFO) FullscreenOutput;
|
|
|
|
UserAssert(FullscreenInputLength * 2 == (ULONG) FullscreenOutputLength);
|
|
|
|
while (FullscreenInputLength)
|
|
{
|
|
pCharInfo->Char.AsciiChar = *pFrameBuf++;
|
|
(UCHAR) (pCharInfo->Attributes) = *pFrameBuf++;
|
|
|
|
FullscreenInputLength -= 2;
|
|
pCharInfo++;
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
case FullscreenControlWriteToFrameBuffer:
|
|
|
|
// TRACE_SWITCH(("Switching: FullscreenControlWriteToFrameBuffer\n"));
|
|
|
|
pFrameBuf += (ULONG) FullscreenOutput;
|
|
pCharInfo = (PCHAR_INFO) FullscreenInput;
|
|
|
|
UserAssert(FullscreenInputLength == (ULONG) FullscreenOutputLength * 2);
|
|
|
|
while ((ULONG)FullscreenOutputLength)
|
|
{
|
|
*pFrameBuf++ = pCharInfo->Char.AsciiChar;
|
|
*pFrameBuf++ = (UCHAR) (pCharInfo->Attributes);
|
|
|
|
((ULONG)FullscreenOutputLength) -= 2;
|
|
pCharInfo++;
|
|
}
|
|
|
|
break;
|
|
|
|
case FullscreenControlReverseMousePointer:
|
|
|
|
TRACE_SWITCH(("Switching: FullscreenControlReverseMousePointer\n"));
|
|
|
|
pFrameBuf += (ULONG) FullscreenInput;
|
|
|
|
Attribute = (*(pFrameBuf + 1) & 0xF0) >> 4;
|
|
Attribute |= (*(pFrameBuf + 1) & 0x0F) << 4;
|
|
*(pFrameBuf + 1) = Attribute;
|
|
|
|
break;
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// For all real operations, check the output buffer parameters
|
|
//
|
|
|
|
if ((FullscreenOutput == NULL) != (FullscreenOutputLength == NULL))
|
|
{
|
|
RIPMSG0(RIP_ERROR, "Fullscreen control - inconsistent output buffer information\n");
|
|
Status = STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We must now capture the buffers so they can be safely passed down to the
|
|
// video miniport driver
|
|
//
|
|
|
|
cCapBuffer = FullscreenInputLength;
|
|
|
|
if (FullscreenOutputLength)
|
|
{
|
|
cCapBuffer = max(cCapBuffer,*FullscreenOutputLength);
|
|
}
|
|
|
|
if (cCapBuffer)
|
|
{
|
|
pCapBuffer = UserAllocPoolWithQuota(cCapBuffer, TAG_FULLSCREEN);
|
|
|
|
try
|
|
{
|
|
ProbeForRead(FullscreenInput, FullscreenInputLength, sizeof(UCHAR));
|
|
RtlCopyMemory(pCapBuffer, FullscreenInput, FullscreenInputLength);
|
|
}
|
|
except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
RIPMSG0(RIP_ERROR, "Fullscreen control - error processing input buffer\n");
|
|
}
|
|
}
|
|
|
|
//
|
|
// For now, the IOCTL will always be sent to the VGA compatible device.
|
|
// We have a global for the handle to this device.
|
|
//
|
|
|
|
Status = GreDeviceIoControl(gphysDevInfo[0].pDeviceHandle,
|
|
ioctl,
|
|
pCapBuffer,
|
|
cCapBuffer,
|
|
pCapBuffer,
|
|
cCapBuffer,
|
|
&BytesReturned);
|
|
|
|
TRACE_SWITCH(("Switching: FullscreenControl: IOCTL status is %08lx\n",
|
|
Status));
|
|
|
|
if (cCapBuffer && FullscreenOutputLength && NT_SUCCESS(Status))
|
|
{
|
|
try
|
|
{
|
|
*FullscreenOutputLength = BytesReturned;
|
|
|
|
ProbeForWrite(FullscreenOutput, *FullscreenOutputLength, sizeof(UCHAR));
|
|
RtlCopyMemory(FullscreenOutput, pCapBuffer, *FullscreenOutputLength);
|
|
}
|
|
except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
RIPMSG0(RIP_ERROR, "Fullscreen control - error processing output buffer\n");
|
|
}
|
|
}
|
|
|
|
if (pCapBuffer)
|
|
{
|
|
UserFreePool(pCapBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
LeaveCrit();
|
|
|
|
return (Status);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* ResetSharedDesktops
|
|
*
|
|
* Resets the attributes for other desktops which share the DISPINFO that
|
|
* was just changed. We need to resize all visrgns of the other desktops
|
|
* so that clipping is allright.
|
|
*
|
|
* NOTE: For now, we have to change all the desktop even though we keep
|
|
* track of the devmode on a per desktop basis, because we can switch
|
|
* back to a desktop that has a different resolution and paint it before
|
|
* we can change the resolution again.
|
|
* There is also an issue with CDS_FULLSCREEN where we currently loose track
|
|
* of whether or not the desktop settings need to be reset or not. [andreva]
|
|
*
|
|
* 19-Feb-1996 ChrisWil Created.
|
|
\***************************************************************************/
|
|
|
|
VOID ResetSharedDesktops(
|
|
PDISPLAYINFO pDIChanged,
|
|
PDESKTOP pdeskChanged,
|
|
LPRECT lprOldWork,
|
|
DWORD CDS_Flags)
|
|
{
|
|
PWINDOWSTATION pwinsta = _GetProcessWindowStation(NULL);
|
|
PDESKTOP pdesk;
|
|
UINT xNew;
|
|
UINT yNew;
|
|
HRGN hrgn;
|
|
|
|
if (pwinsta == NULL) {
|
|
|
|
TRACE_SWITCH(("ResetSharedDesktops - NULL window station !\n"));
|
|
return;
|
|
}
|
|
|
|
for (pdesk = pwinsta->rpdeskList; pdesk; pdesk = pdesk->rpdeskNext) {
|
|
|
|
/*
|
|
* Make sure this is a shared DISPINFO.
|
|
*/
|
|
if (pdesk->pDispInfo == pDIChanged) {
|
|
|
|
#if 0
|
|
/*
|
|
* This is the preferable method to set the desktop-window.
|
|
* However, this causes synchronization problems where we
|
|
* leave the critical-section allowing other apps to call
|
|
* ChangeDisplaySettings() and thus mucking up the works.
|
|
*
|
|
* By calculating the vis-rgn ourselves, we can assure that
|
|
* the clipping is current for the desktop even when we leave
|
|
* the section.
|
|
*/
|
|
{
|
|
TL tlpwnd;
|
|
|
|
ThreadLockAlways(pdesk->pDeskInfo->spwnd, &tlpwnd);
|
|
xxxSetWindowPos(pdesk->pDeskInfo->spwnd,
|
|
PWND_TOP,
|
|
pDIChanged->rcScreen.left,
|
|
pDIChanged->rcScreen.top,
|
|
pDIChanged->rcScreen.right - pDIChanged->rcScreen.left,
|
|
pDIChanged->rcScreen.bottom - pDIChanged->rcScreen.top,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
#else
|
|
CopyRect(&pdesk->pDeskInfo->spwnd->rcWindow, &pDIChanged->rcScreen);
|
|
CopyRect(&pdesk->pDeskInfo->spwnd->rcClient, &pDIChanged->rcScreen);
|
|
#endif
|
|
|
|
if (!(CDS_Flags & CDS_FULLSCREEN))
|
|
DesktopRecalc(lprOldWork, &gpsi->rcWork, FALSE);
|
|
|
|
/*
|
|
* Position mouse so that it is within the new visrgn, once we
|
|
* recalc it.
|
|
*/
|
|
xNew = (pDIChanged->rcScreen.right - pDIChanged->rcScreen.left) >> 1;
|
|
yNew = (pDIChanged->rcScreen.bottom - pDIChanged->rcScreen.top) >> 1;
|
|
|
|
InternalSetCursorPos(xNew, yNew, pdesk);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Recalc the desktop visrgn. Since the hdcScreen is shared amoungts
|
|
* all the
|
|
*/
|
|
hrgn = GreCreateRectRgn(0, 0, 0, 0);
|
|
|
|
CalcVisRgn(&hrgn,
|
|
pdeskChanged->pDeskInfo->spwnd,
|
|
pdeskChanged->pDeskInfo->spwnd,
|
|
DCX_WINDOW);
|
|
|
|
GreSelectVisRgn(pDIChanged->hdcScreen, hrgn, NULL, SVR_DELETEOLD);
|
|
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* ResetDisplayDevice
|
|
*
|
|
* Resets the user-globals with the new hdev settings.
|
|
*
|
|
* 19-Feb-1996 ChrisWil Created.
|
|
\***************************************************************************/
|
|
|
|
VOID ResetDisplayDevice(
|
|
PDESKTOP pdesk,
|
|
PDISPLAYINFO pDI,
|
|
DWORD CDS_Flags)
|
|
{
|
|
RECT rcOldWork;
|
|
RECT rcOldScreen;
|
|
WORD wOldBpp;
|
|
|
|
/*
|
|
* Save old dimenstions for use in calculating the new work areas.
|
|
*/
|
|
CopyRect(&rcOldScreen, &pDI->rcScreen);
|
|
CopyRect(&rcOldWork, &gpsi->rcWork);
|
|
|
|
/*
|
|
* Initialize the new rectangle dimensions.
|
|
*/
|
|
SetRect(&pDI->rcScreen,
|
|
0,
|
|
0,
|
|
GreGetDeviceCaps(pDI->hdcScreen, DESKTOPHORZRES),
|
|
GreGetDeviceCaps(pDI->hdcScreen, DESKTOPVERTRES));
|
|
|
|
SetRect(&pDI->rcPrimaryScreen,
|
|
0,
|
|
0,
|
|
GreGetDeviceCaps(pDI->hdcScreen, HORZRES),
|
|
GreGetDeviceCaps(pDI->hdcScreen, VERTRES));
|
|
|
|
/*
|
|
* Initialize the desktop work area. Currently this is using the
|
|
* global gpsi. Future implementation will be off the desktop.
|
|
*/
|
|
gpsi->rcWork.left = rcOldWork.left;
|
|
gpsi->rcWork.top = rcOldWork.top;
|
|
gpsi->rcWork.right = pDI->rcScreen.right - (rcOldScreen.right - rcOldWork.right);
|
|
gpsi->rcWork.bottom = pDI->rcScreen.bottom - (rcOldScreen.bottom - rcOldWork.bottom);
|
|
|
|
/*
|
|
* Reset palettized flag.
|
|
*/
|
|
gpsi->fPaletteDisplay =
|
|
GreGetDeviceCaps(pDI->hdcScreen, RASTERCAPS) & RC_PALETTE;
|
|
|
|
/*
|
|
* Reset color depth.
|
|
*/
|
|
wOldBpp = oemInfo.BitCount;
|
|
|
|
oemInfo.Planes = GreGetDeviceCaps(gpDispInfo->hdcScreen, PLANES);
|
|
oemInfo.BitsPixel = GreGetDeviceCaps(gpDispInfo->hdcScreen, BITSPIXEL);
|
|
oemInfo.BitCount = oemInfo.Planes * oemInfo.BitsPixel;
|
|
|
|
/*
|
|
* Reset the dimenstions of the displayinfo.
|
|
*/
|
|
pDI->cxPixelsPerInch = GreGetDeviceCaps(pDI->hdcScreen, LOGPIXELSX);
|
|
pDI->cyPixelsPerInch = GreGetDeviceCaps(pDI->hdcScreen, LOGPIXELSY);
|
|
|
|
/*
|
|
* Set the desktop-metrics stuff--working area.
|
|
*/
|
|
SetDesktopMetrics();
|
|
|
|
SYSMET(CXSCREEN) = pDI->rcPrimaryScreen.right - pDI->rcPrimaryScreen.left;
|
|
SYSMET(CYSCREEN) = pDI->rcPrimaryScreen.bottom - pDI->rcPrimaryScreen.top;
|
|
SYSMET(CXMAXTRACK) = SYSMET(CXSCREEN) + (2 * (SYSMET(CXSIZEFRAME) + SYSMET(CXEDGE)));
|
|
SYSMET(CYMAXTRACK) = SYSMET(CYSCREEN) + (2 * (SYSMET(CYSIZEFRAME) + SYSMET(CYEDGE)));
|
|
|
|
/*
|
|
* Reset magic colors.
|
|
*/
|
|
SetSysColor(COLOR_3DSHADOW , SYSRGB(3DSHADOW) , SSCF_SETMAGICCOLORS | SSCF_FORCESOLIDCOLOR);
|
|
SetSysColor(COLOR_3DFACE , SYSRGB(3DFACE) , SSCF_SETMAGICCOLORS | SSCF_FORCESOLIDCOLOR);
|
|
SetSysColor(COLOR_3DHIGHLIGHT, SYSRGB(3DHIGHLIGHT), SSCF_SETMAGICCOLORS | SSCF_FORCESOLIDCOLOR);
|
|
|
|
/*
|
|
* Resize all the desktops which share this DISPINFO.
|
|
*/
|
|
ResetSharedDesktops(gpDispInfo, pdesk, &rcOldWork, CDS_Flags);
|
|
|
|
|
|
if (ghbmCaption) {
|
|
GreDeleteObject(ghbmCaption);
|
|
ghbmCaption = CreateCaptionStrip();
|
|
}
|
|
|
|
/*
|
|
* Change the wallpaper metrics.
|
|
*/
|
|
if (ghbmWallpaper)
|
|
xxxSetDeskWallpaper(SETWALLPAPER_METRICS);
|
|
|
|
_ClipCursor(&pDI->rcScreen);
|
|
|
|
/*
|
|
* Invalidate all DCE's visrgns.
|
|
*/
|
|
InvalidateDCCache(pdesk->pDeskInfo->spwnd, 0);
|
|
|
|
#ifdef LATER // Maybe Never
|
|
/*
|
|
* Flush icons and cursors and more metric stuff.
|
|
*
|
|
* We may never want to do this, since we do ChangeDisplaySettings
|
|
* across desktop-switches and console-fullscreen modes. By calling
|
|
* the metric changes, we would be forcing desktop-attribute settings
|
|
* to change to the default (from win.ini). It looks much nicer to
|
|
* allow this to be preserved across switches.
|
|
*/
|
|
SetWindowNCMetrics(NULL, TRUE, -1);
|
|
SetMinMetrics(NULL);
|
|
|
|
SetIconMetrics(NULL);
|
|
UpdateSystemCursorsFromRegistry();
|
|
UpdateSystemIconsFromRegistry();
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Broadcast that the display has changed resolution. We are going
|
|
* to specify the desktop for the changing-desktop. That way we
|
|
* don't get confused as to what desktop to broadcast to.
|
|
*/
|
|
xxxBroadcastMessage(pdesk->pDeskInfo->spwnd,
|
|
WM_DISPLAYCHANGE,
|
|
oemInfo.BitCount,
|
|
MAKELONG(SYSMET(CXSCREEN), SYSMET(CYSCREEN)),
|
|
BMSG_SENDNOTIFYMSG,
|
|
NULL);
|
|
|
|
/*
|
|
* Broadcast a color-change if we were not in fullscreen, and a
|
|
* color-change took effect.
|
|
*/
|
|
if (!(CDS_Flags & CDS_FULLSCREEN) && (oemInfo.BitCount != wOldBpp)) {
|
|
|
|
#if 1 // We might want to remove this call, since color-change seems
|
|
// to provide apps the notification. Need to review
|
|
// chriswil - 06/11/96
|
|
|
|
xxxBroadcastMessage(pdesk->pDeskInfo->spwnd,
|
|
WM_SETTINGCHANGE,
|
|
0,
|
|
0,
|
|
BMSG_SENDNOTIFYMSG,
|
|
NULL);
|
|
#endif
|
|
|
|
xxxBroadcastMessage(pdesk->pDeskInfo->spwnd,
|
|
WM_SYSCOLORCHANGE,
|
|
0,
|
|
0,
|
|
BMSG_SENDNOTIFYMSG,
|
|
NULL);
|
|
}
|
|
|
|
/*
|
|
* If the user performed a CTL-ESC, it is possible that the
|
|
* tray-window is then in the menu-loop. We want to clear this
|
|
* out so that we don't leave improper menu positioning.
|
|
*/
|
|
if (gpqForeground && gpqForeground->spwndCapture)
|
|
QueueNotifyMessage(gpqForeground->spwndCapture, WM_CANCELMODE, 0, 0l);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* NtUserChangeDisplaySettings
|
|
*
|
|
* ChangeDisplaySettings API
|
|
*
|
|
* 01-Sep-1995 andreva Created
|
|
* 19-Feb-1996 ChrisWil Implemented Dynamic-Resolution changes.
|
|
\***************************************************************************/
|
|
|
|
LONG NtUserChangeDisplaySettings(
|
|
IN PUNICODE_STRING pstrDeviceName,
|
|
IN LPDEVMODEW pDevMode,
|
|
IN HWND hwnd,
|
|
IN DWORD dwFlags,
|
|
IN PVOID lParam)
|
|
{
|
|
PWND pwnd = NULL;
|
|
LONG retval;
|
|
|
|
EnterCrit();
|
|
|
|
if (hwnd) {
|
|
|
|
pwnd = ValidateHwnd(hwnd);
|
|
|
|
if (!pwnd) {
|
|
LeaveCrit();
|
|
return DISP_CHANGE_BADPARAM;
|
|
}
|
|
}
|
|
|
|
retval = UserChangeDisplaySettings(pstrDeviceName,
|
|
pDevMode,
|
|
pwnd,
|
|
NULL,
|
|
dwFlags,
|
|
lParam,
|
|
FALSE);
|
|
|
|
LeaveCrit();
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
LONG UserChangeDisplaySettings(
|
|
IN PUNICODE_STRING pstrDeviceName,
|
|
IN LPDEVMODEW pDevMode,
|
|
IN PWND pwnd,
|
|
IN PDESKTOP pdesk,
|
|
IN DWORD dwFlags,
|
|
IN PVOID lParam,
|
|
IN BOOL bKernelMode)
|
|
{
|
|
NTSTATUS ntStatus;
|
|
UNICODE_STRING strDevice;
|
|
UNICODE_STRING us;
|
|
PDEVMODEW pCaptDevmode = NULL;
|
|
LONG status = DISP_CHANGE_SUCCESSFUL;
|
|
TL tlpwnd;
|
|
BOOL bTestMode;
|
|
BOOL bCurrentDevice = FALSE;
|
|
BOOL bExclusiveDevice = FALSE;
|
|
RECT rect;
|
|
PRECT prect = NULL;
|
|
PPHYSICAL_DEV_INFO physdevinfo;
|
|
|
|
HANDLE hkRegistry;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING UnicodeString;
|
|
ULONG disableAll;
|
|
ULONG defaultValue = 0;
|
|
|
|
RTL_QUERY_REGISTRY_TABLE QueryTable[] = {
|
|
{NULL, RTL_QUERY_REGISTRY_DIRECT, L"DisableAll", &disableAll,
|
|
REG_DWORD, &defaultValue, 4},
|
|
{NULL, 0, NULL}
|
|
};
|
|
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings - Entering\n"));
|
|
TRACE_SWITCH(("ChangeDisplaySettings - Entering\n"));
|
|
|
|
TRACE_INIT((" Flags -"));
|
|
|
|
if (dwFlags & CDS_UPDATEREGISTRY) TRACE_INIT((" CDS_UPDATEREGISTRY"));
|
|
if (dwFlags & CDS_TEST ) TRACE_INIT((" CDS_TEST "));
|
|
if (dwFlags & CDS_FULLSCREEN ) TRACE_INIT((" CDS_FULLSCREEN "));
|
|
if (dwFlags & CDS_GLOBAL ) TRACE_INIT((" CDS_GLOBAL "));
|
|
if (dwFlags & CDS_SET_PRIMARY ) TRACE_INIT((" CDS_SET_PRIMARY "));
|
|
if (dwFlags & CDS_RESET ) TRACE_INIT((" CDS_RESET "));
|
|
if (dwFlags & CDS_SETRECT ) TRACE_INIT((" CDS_SETRECT "));
|
|
if (dwFlags & CDS_NORESET ) TRACE_INIT((" CDS_NORESET "));
|
|
|
|
TRACE_INIT(("\n"));
|
|
TRACE_INIT((" pDevMode %08lx\n", pDevMode));
|
|
|
|
if (pDevMode) {
|
|
|
|
TRACE_INIT((" Size = %d\n", pDevMode->dmSize));
|
|
TRACE_INIT((" Fields = %08lx\n", pDevMode->dmFields));
|
|
TRACE_INIT((" XResolution = %d\n", pDevMode->dmPelsWidth));
|
|
TRACE_INIT((" YResolution = %d\n", pDevMode->dmPelsHeight));
|
|
TRACE_INIT((" Bpp = %d\n", pDevMode->dmBitsPerPel));
|
|
TRACE_INIT((" Frequency = %d\n", pDevMode->dmDisplayFrequency));
|
|
TRACE_INIT((" Flags = %d\n", pDevMode->dmDisplayFlags));
|
|
TRACE_INIT((" XPanning = %d\n", pDevMode->dmPanningWidth));
|
|
TRACE_INIT((" YPanning = %d\n", pDevMode->dmPanningHeight));
|
|
TRACE_INIT((" DPI = %d\n", pDevMode->dmLogPixels));
|
|
TRACE_INIT((" DriverExtra = %d", pDevMode->dmDriverExtra));
|
|
if (pDevMode->dmDriverExtra) {
|
|
TRACE_INIT((" - %08lx %08lx\n",
|
|
*(PULONG)(((PUCHAR)pDevMode)+pDevMode->dmSize),
|
|
*(PULONG)(((PUCHAR)pDevMode)+pDevMode->dmSize + 4)));
|
|
} else {
|
|
TRACE_INIT(("\n"));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine if we are in the test mode of the API
|
|
*/
|
|
|
|
bTestMode = dwFlags & CDS_TEST;
|
|
dwFlags &= ~CDS_TEST;
|
|
|
|
/*
|
|
* Perform Error Checking to verify flag combinations are valid.
|
|
*/
|
|
if (dwFlags & (~CDS_VALID)) {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: invalid flags specified\n");
|
|
return DISP_CHANGE_BADFLAGS;
|
|
}
|
|
|
|
/*
|
|
* CDS_GLOBAL and CDS_NORESET can only be specified if UPDAREREGISTRY
|
|
* is specified.
|
|
*/
|
|
|
|
if ( (dwFlags & (CDS_GLOBAL | CDS_NORESET)) &&
|
|
(!(dwFlags & CDS_UPDATEREGISTRY))) {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: invalid registry flags specified\n");
|
|
return DISP_CHANGE_BADFLAGS;
|
|
}
|
|
|
|
if ( (dwFlags & CDS_NORESET) &&
|
|
(dwFlags & CDS_RESET)) {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: RESET and NORESET can not be put together\n");
|
|
return DISP_CHANGE_BADFLAGS;
|
|
}
|
|
|
|
if ((dwFlags & CDS_EXCLUSIVE) && (dwFlags & CDS_FULLSCREEN) && (dwFlags & CDS_RESET)) {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: invalid flags specified\n");
|
|
return DISP_CHANGE_BADFLAGS;
|
|
}
|
|
|
|
if (lParam && (!(dwFlags & CDS_SETRECT))) {
|
|
return DISP_CHANGE_BADPARAM;
|
|
}
|
|
|
|
/*
|
|
* Lets capture our parameters. They are both required.
|
|
*
|
|
* If the input string is not NULL, then we are trying to affect another
|
|
* Device. The device name is the same as for EnumDisplaySettings.
|
|
*/
|
|
|
|
if (dwFlags & CDS_SETRECT) {
|
|
|
|
if (lParam) {
|
|
|
|
prect = ▭
|
|
|
|
if (bKernelMode) {
|
|
|
|
rect = *((PRECT) lParam);
|
|
|
|
} else {
|
|
|
|
try {
|
|
rect = ProbeAndReadRect((PRECT) lParam);
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return DISP_CHANGE_BADPARAM;;
|
|
}
|
|
}
|
|
} else {
|
|
prect = (PRECT) -1;
|
|
}
|
|
}
|
|
|
|
if (bKernelMode) {
|
|
|
|
strDevice = *pstrDeviceName;
|
|
|
|
} else {
|
|
|
|
strDevice.Buffer = NULL;
|
|
|
|
if (!ProbeAndCaptureDeviceName(&strDevice, pstrDeviceName)) {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: Bad string\n");
|
|
status = DISP_CHANGE_BADPARAM;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If the modeset is being done on a non-active desktop, we don't want
|
|
* it too happen.
|
|
*
|
|
* PtiCurrent()->rpdesk can be NULL !!! (in the case of thread shutdown).
|
|
*/
|
|
|
|
if (pdesk) {
|
|
|
|
if (pdesk != grpdeskRitInput) {
|
|
RIPMSG0(RIP_WARNING, "ChangeDisplaySettings on wrong desktop pdesk\n");
|
|
status = DISP_CHANGE_FAILED;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
RtlInitUnicodeString(&us, pdesk->pDispInfo->pDevInfo->szNtDeviceName);
|
|
|
|
} else {
|
|
|
|
if (PtiCurrent()->rpdesk != grpdeskRitInput) {
|
|
RIPMSG0(RIP_WARNING, "ChangeDisplaySettings on wrong desktop rpdesk\n");
|
|
status = DISP_CHANGE_FAILED;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
RtlInitUnicodeString(&us, PtiCurrent()->rpdesk->pDispInfo->pDevInfo->szNtDeviceName);
|
|
}
|
|
|
|
|
|
if (RtlEqualUnicodeString(&strDevice,
|
|
&us,
|
|
TRUE)) {
|
|
|
|
bCurrentDevice = TRUE;
|
|
|
|
} else {
|
|
|
|
TRACE_INIT(("\n ChangeDisplaySettings: This better be an Exclusive device !!!\n"));
|
|
|
|
/* fix when we track ownership of secondary display devices */
|
|
|
|
/* VGACOMPATIBLE will end up in here also right now */
|
|
bExclusiveDevice = TRUE;
|
|
}
|
|
|
|
#if DBG
|
|
/*
|
|
* Turn off Tracing for TEST_MODE since it's generally not interesting.
|
|
*/
|
|
if (bTestMode)
|
|
(ULONG) TraceDisplayDriverLoad |= 0x80000000;
|
|
#endif
|
|
|
|
ntStatus = ProbeAndCaptureDevmode(&strDevice,
|
|
&pCaptDevmode,
|
|
pDevMode,
|
|
bKernelMode);
|
|
|
|
#if DBG
|
|
if (bTestMode)
|
|
(ULONG) TraceDisplayDriverLoad &= 0x7FFFFFFF;
|
|
#endif
|
|
|
|
if ((!NT_SUCCESS(ntStatus)) ||
|
|
(pCaptDevmode == NULL)) {
|
|
|
|
RIPMSG0(RIP_WARNING, "ChangeDisplaySettings: Bad DEVMODE\n");
|
|
status = DISP_CHANGE_BADPARAM;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
|
|
/*
|
|
* Write the data to the registry.
|
|
*
|
|
* This is not supported for the vgacompatible device - so it should
|
|
* just fail. UserGetRegistryHandle should fail with this
|
|
* unrecognized string
|
|
*/
|
|
if (dwFlags & CDS_UPDATEREGISTRY) {
|
|
|
|
NTSTATUS Status = STATUS_UNSUCCESSFUL;
|
|
|
|
/*
|
|
* CDS_GLOBAL is the default right now.
|
|
* When we store the settings on a per-user basis, then this flag
|
|
* will override that behaviour.
|
|
*/
|
|
|
|
/*
|
|
* Check if the Administrator has diabled this privilege.
|
|
* It is on by default.
|
|
*/
|
|
|
|
disableAll = 0;
|
|
|
|
RtlInitUnicodeString(&UnicodeString,
|
|
L"\\Registry\\Machine\\System\\CurrentControlSet\\"
|
|
L"Control\\GraphicsDrivers\\PermanentSettingChanges");
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&UnicodeString,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (NT_SUCCESS(ZwOpenKey(&hkRegistry, GENERIC_READ, &ObjectAttributes))) {
|
|
|
|
RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)hkRegistry,
|
|
&QueryTable[0],
|
|
NULL,
|
|
NULL);
|
|
|
|
ZwClose(hkRegistry);
|
|
}
|
|
|
|
if (disableAll == 0) {
|
|
|
|
/*
|
|
* Only do the operation if we are not in test mode.
|
|
* We can always assume success for this operation in test mode.
|
|
*
|
|
* It is OK to save parameters to the registry for any device.
|
|
* So we just pass the device name on.
|
|
*/
|
|
|
|
if (bTestMode) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} else {
|
|
|
|
Status = UserSetDisplayDriverParameters(&strDevice,
|
|
DispDriverParamDefault,
|
|
pCaptDevmode,
|
|
prect);
|
|
if (!NT_SUCCESS(Status)) {
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: Failed to save registry parameters\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the appropriate win32 error code
|
|
*/
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
status = DISP_CHANGE_SUCCESSFUL;
|
|
} else {
|
|
status = DISP_CHANGE_NOTUPDATED;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
|
|
/*
|
|
* This flag indicates we should exit right now, and not reset
|
|
* the current screen resolution.
|
|
*/
|
|
|
|
if (dwFlags & CDS_NORESET) {
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if there are restrictions on changing the resolution dymanically
|
|
*/
|
|
|
|
disableAll = 0;
|
|
|
|
RtlInitUnicodeString(&UnicodeString,
|
|
L"\\Registry\\Machine\\System\\CurrentControlSet\\"
|
|
L"Control\\GraphicsDrivers\\TemporarySettingChanges");
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&UnicodeString,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (NT_SUCCESS(ZwOpenKey(&hkRegistry, GENERIC_READ, &ObjectAttributes))) {
|
|
|
|
RtlQueryRegistryValues(RTL_REGISTRY_HANDLE,
|
|
(PWSTR)hkRegistry,
|
|
&QueryTable[0],
|
|
NULL,
|
|
NULL);
|
|
|
|
ZwClose(hkRegistry);
|
|
}
|
|
|
|
if (disableAll) {
|
|
status = DISP_CHANGE_FAILED;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
|
|
/*
|
|
* Get the physdevinfo so we can lock the device and get the current
|
|
* mode from it.
|
|
*/
|
|
physdevinfo = UserGetDeviceFromName(&strDevice,
|
|
USER_DEVICE_SHARED);
|
|
|
|
if (physdevinfo == NULL) {
|
|
status = DISP_CHANGE_FAILED;
|
|
goto CDS_Exit_NoUnlock;
|
|
}
|
|
|
|
/*
|
|
* We already validated the DEVMODE. So we know it's good (and that the
|
|
* mode can be set right now). All we need to check is if the bit-depth
|
|
* could fail.
|
|
*/
|
|
|
|
if (bTestMode) {
|
|
|
|
/*
|
|
* An application can only change the resolution on the fly for a
|
|
* device on which it is currently running on (see desktop) or for
|
|
* a device that is owns.
|
|
*
|
|
* Lets check those conditions.
|
|
*/
|
|
|
|
if (bCurrentDevice || bExclusiveDevice) {
|
|
|
|
/*
|
|
* Changing resolution on the fly should always work.
|
|
*
|
|
* For now, we will let everything go through unless we find bugs
|
|
* and andrew wants to disable it
|
|
*/
|
|
|
|
status = DISP_CHANGE_SUCCESSFUL;
|
|
|
|
/*
|
|
* multi-display or mirroring drivers can not change resolution on
|
|
* the fly for now since the GreDynamicMode change API does not
|
|
* support this yet.
|
|
*/
|
|
|
|
if (bMultipleDisplaySystem) {
|
|
status = DISP_CHANGE_RESTART;
|
|
}
|
|
|
|
/*
|
|
* Let's determine if we can change the mode on the fly by
|
|
* comparing the string in the DEVMODE to that of the current
|
|
* DEVMODE. If they are different - we need to change drivers -
|
|
* then the mode switch requires a restart.
|
|
*/
|
|
|
|
if (wcsncmp(&(pCaptDevmode->dmDeviceName[0]),
|
|
&(physdevinfo->pCurrentDevmode->dmDeviceName[0]),
|
|
32)) {
|
|
|
|
status = DISP_CHANGE_RESTART;
|
|
|
|
}
|
|
|
|
/*
|
|
* BUGBUG
|
|
* Check the registry key that allows color depths on the fly.
|
|
*/
|
|
|
|
} else {
|
|
|
|
/*
|
|
* We can not change the mode on this device.
|
|
*/
|
|
|
|
status = DISP_CHANGE_BADPARAM;
|
|
}
|
|
|
|
goto CDS_Exit_Free_Device;
|
|
}
|
|
|
|
/*
|
|
* Lock the PWND, if it is provided
|
|
*/
|
|
|
|
if (pwnd) {
|
|
ThreadLock(pwnd, &tlpwnd);
|
|
}
|
|
|
|
/*
|
|
* We don't want our mode switch to be posted on the looping thread.
|
|
* So let's loop until the system has settled down and no mode switch
|
|
* is currently occuring.
|
|
*/
|
|
|
|
while (idSwitcher != NOSWITCHER) {
|
|
LeaveCrit();
|
|
UserSleep(1);
|
|
EnterCrit();
|
|
}
|
|
|
|
/*
|
|
* If there is a window, we want to check the state of the window.
|
|
* For most calls, we want to ensure we are in windowed mode.
|
|
* However, for Console, we want to make sure we are in fullscreen mode.
|
|
* So differentiate between the two. We will check if the TEXTMODE
|
|
* flag is passed in the DEVMODE.
|
|
*/
|
|
|
|
if (pwnd) {
|
|
|
|
if (pCaptDevmode->dmDisplayFlags == DMDISPLAYFLAGS_TEXTMODE) {
|
|
|
|
if (pwnd->bFullScreen != FULLSCREEN) {
|
|
|
|
xxxShowWindow(pwnd, MAKELONG(SW_SHOWMINIMIZED, gfAnimate));
|
|
xxxUpdateWindow(pwnd);
|
|
|
|
}
|
|
|
|
xxxMakeWindowForegroundWithState(pwnd, FULLSCREEN);
|
|
|
|
if ((idSwitcher != NOSWITCHER) ||
|
|
(gbFullScreen != FULLSCREEN)) {
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings: Can not switch into fullscreen\n"));
|
|
status = DISP_CHANGE_FAILED;
|
|
goto CDS_Exit;
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
* For the console windows, we want to call with WINDOWED
|
|
* We base this check on whether gdi is enabled or not.
|
|
*/
|
|
|
|
if (fGdiEnabled == FALSE) {
|
|
xxxMakeWindowForegroundWithState(pwnd, WINDOWED);
|
|
}
|
|
|
|
if ((idSwitcher != NOSWITCHER) ||
|
|
(gbFullScreen != GDIFULLSCREEN)) {
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings: Can not switch out of fullscreen\n"));
|
|
status = DISP_CHANGE_FAILED;
|
|
goto CDS_Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check for console fullscreen.
|
|
*/
|
|
|
|
if (pwnd &&
|
|
(pCaptDevmode->dmDisplayFlags == DMDISPLAYFLAGS_TEXTMODE) &&
|
|
(dwFlags & CDS_FULLSCREEN)) {
|
|
|
|
#if 0
|
|
UNICODE_STRING vgaString;
|
|
|
|
/*
|
|
* "VGACOMPATIBLE" is a special name that indicates we want to use
|
|
* the default VGA device of the machine (for console)
|
|
*/
|
|
RtlInitUnicodeString(&vgaString, L"VGACOMPATIBLE");
|
|
|
|
if (RtlEqualUnicodeString(&strDevice, &vgaString, TRUE)) {
|
|
|
|
LPDEVMODEW pDevicemode = gphysDevInfo[0].devmodeInfo;
|
|
VIDEO_MODE VideoMode;
|
|
BOOLEAN modeFound = FALSE;
|
|
ULONG BytesReturned;
|
|
NTSTATUS Status;
|
|
ULONG i;
|
|
|
|
/*
|
|
* Fullscreen VGA modes require us to call the miniport driver
|
|
* directly.
|
|
*
|
|
* Lets check the VGA Device handle, which is in the first
|
|
* entry.
|
|
*/
|
|
|
|
if (gphysDevInfo[0].pDeviceHandle == NULL) {
|
|
|
|
status = DISP_CHANGE_BADPARAM;
|
|
|
|
} else {
|
|
|
|
VideoMode.RequestedMode = (ULONG) pCaptDevmode->dmOrientation;
|
|
|
|
/*
|
|
* We have the mode number.
|
|
* Call the driver to set the mode
|
|
*/
|
|
Status = GreDeviceIoControl(
|
|
gphysDevInfo[0].pDeviceHandle,
|
|
IOCTL_VIDEO_SET_CURRENT_MODE,
|
|
&VideoMode,
|
|
sizeof(VideoMode),
|
|
NULL,
|
|
0,
|
|
&BytesReturned);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
/*
|
|
* We also map the memory so we can use it to
|
|
* process string commands from the console
|
|
*/
|
|
VIDEO_MEMORY FrameBufferMap;
|
|
VIDEO_MEMORY_INFORMATION FrameBufferInfo;
|
|
|
|
FrameBufferMap.RequestedVirtualAddress = NULL;
|
|
|
|
Status = GreDeviceIoControl(
|
|
gphysDevInfo[0].pDeviceHandle,
|
|
IOCTL_VIDEO_MAP_VIDEO_MEMORY,
|
|
&FrameBufferMap,
|
|
sizeof(FrameBufferMap),
|
|
&FrameBufferInfo,
|
|
sizeof(FrameBufferInfo),
|
|
&BytesReturned);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
/*
|
|
* get address of frame buffer
|
|
*/
|
|
gpFullscreenFrameBufPtr = (PUCHAR) FrameBufferInfo.FrameBufferBase;
|
|
|
|
} else {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: memory mapping failed\n");
|
|
status = DISP_CHANGE_FAILED;
|
|
}
|
|
|
|
} else {
|
|
|
|
RIPMSG0(RIP_ERROR, "ChangeDisplaySettings: fullscreen MODESET failed\n");
|
|
status = DISP_CHANGE_FAILED;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
/*
|
|
* For now, don't allow dynamic-resolution changes to occur
|
|
* when GDI is in fullscreen. We should almost never hit this
|
|
* condition, except for rare stress cases.
|
|
*/
|
|
if (gfLockFullScreen) {
|
|
status = DISP_CHANGE_FAILED;
|
|
}
|
|
|
|
/*
|
|
* multi-display or mirroring drivers can not change resolution on
|
|
* the fly for now since the GreDynamicMode change API does not
|
|
* support this yet.
|
|
*/
|
|
if (bMultipleDisplaySystem) {
|
|
status = DISP_CHANGE_RESTART;
|
|
}
|
|
|
|
/*
|
|
* Dynamically change the screen resolution. Only do if
|
|
* everything up to point succeeded.
|
|
*/
|
|
if (status == DISP_CHANGE_SUCCESSFUL) {
|
|
|
|
PDESKTOP pDesktop = pdesk;
|
|
|
|
if (bCurrentDevice) {
|
|
|
|
/*
|
|
* BUGBUG we have to do this because PtiCurrent()->rpdesk
|
|
* is the desktop with which the thread is associated, not
|
|
* necessarily the desktop to which we are switching !
|
|
*
|
|
* It's acutally kind of bogus to fall back on PtiCurrent.
|
|
* It assumes apps don't deal with multiple desktops !!!
|
|
*/
|
|
|
|
if (!pDesktop)
|
|
pDesktop = PtiCurrent()->rpdesk;
|
|
|
|
TRACE_SWITCH(("\n DESKTOP is %08lx \n", pDesktop));
|
|
|
|
UserAssert(bExclusiveDevice == FALSE);
|
|
|
|
UserAssert(physdevinfo == pDesktop->pDispInfo->pDevInfo);
|
|
|
|
} else {
|
|
UserAssert(bExclusiveDevice == TRUE);
|
|
}
|
|
|
|
/*
|
|
* We only switch modes dynamically for the desktops that share
|
|
* the same hdev.
|
|
*
|
|
* A mode should only be set if it's a new mode being passed down,
|
|
* or if the CDS_RESET flag was specified. CDS_RESET forces the
|
|
* set mode.
|
|
*/
|
|
|
|
if ((bCurrentDevice) &&
|
|
(gpDispInfo->hDev == pDesktop->pDispInfo->hDev) &&
|
|
( (dwFlags & CDS_RESET) ||
|
|
( (pDesktop) && (pDesktop->bForceModeReset == TRUE)) ||
|
|
(!RtlEqualMemory(pCaptDevmode,
|
|
physdevinfo->pCurrentDevmode,
|
|
sizeof(DEVMODEW))))) {
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings - Switching modes - program\n"));
|
|
|
|
/*
|
|
* Turn off cursor while mucking with the
|
|
* the resolution changes.
|
|
*/
|
|
GreSetPointer(gpDispInfo->hDev, NULL, 0);
|
|
|
|
/*
|
|
* Free the spb's prior to calling the mode-change. This
|
|
* will make sure off-screen memory is cleaned up for
|
|
* gdi.
|
|
*/
|
|
FreeAllSpbs(NULL);
|
|
|
|
if (GreDynamicModeChange(gpDispInfo->hDev,
|
|
physdevinfo->pDeviceHandle,
|
|
pCaptDevmode)) {
|
|
|
|
/*
|
|
* Save the current mode of the device.
|
|
*
|
|
* If the user set the mode as CDS_FULLSCREEN, or the mode
|
|
* is being saved on an exclusive device, don't save it
|
|
* to the desktop.
|
|
*/
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings - Saving the mode\n"));
|
|
|
|
|
|
if ((!(dwFlags & CDS_FULLSCREEN)) &&
|
|
(!bExclusiveDevice)) {
|
|
UserSaveCurrentMode(pDesktop,
|
|
physdevinfo,
|
|
pCaptDevmode);
|
|
|
|
} else {
|
|
UserSaveCurrentMode(NULL,
|
|
physdevinfo,
|
|
pCaptDevmode);
|
|
}
|
|
|
|
/*
|
|
* For CDS_FULLSCREEN mark the desktop as requiring a
|
|
* recalc next time we switch to it, so we force a mode
|
|
* switch.
|
|
*/
|
|
|
|
if (pDesktop) {
|
|
if (dwFlags & CDS_FULLSCREEN) {
|
|
pDesktop->bForceModeReset = TRUE;
|
|
} else {
|
|
pDesktop->bForceModeReset = FALSE;
|
|
}
|
|
}
|
|
|
|
ResetDisplayDevice(pDesktop, gpDispInfo, dwFlags);
|
|
|
|
} else {
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings: GreDynamicModeChange failed \n"));
|
|
status = DISP_CHANGE_FAILED;
|
|
}
|
|
|
|
/*
|
|
* Inline so we can specify which desktop this should happen on.
|
|
* xxxRedrawScreen();
|
|
*/
|
|
if (pDesktop) {
|
|
xxxInternalInvalidate(pDesktop->pDeskInfo->spwnd,
|
|
MAXREGION, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
|
|
}
|
|
|
|
|
|
/*
|
|
* Bring back the cursor-shape.
|
|
*
|
|
#ifdef LATER
|
|
* NOTE: Post 4.0, we should look at changing this and
|
|
* the GreSetPointer() call above to use the
|
|
* UpdateCursorImage() calls. This way we can
|
|
* assure the global-user-vars are update
|
|
* correctly, as well as making sure that animated
|
|
* curor timers are killed through the change.
|
|
*
|
|
* ChrisWil: 19-Jul-1996
|
|
*
|
|
#endif
|
|
*/
|
|
if (gpqCursor &&
|
|
gpqCursor->spcurCurrent &&
|
|
(gpqCursor->iCursorLevel >= 0) &&
|
|
SYSMET(MOUSEPRESENT)) {
|
|
|
|
GreSetPointer(gpDispInfo->hDev,
|
|
(PCURSINFO)&(gpqCursor->spcurCurrent->xHotspot),
|
|
0);
|
|
}
|
|
|
|
} else {
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings - Switching modes - NO program\n"));
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
CDS_Exit:
|
|
|
|
if (pwnd)
|
|
ThreadUnlock(&tlpwnd);
|
|
|
|
CDS_Exit_Free_Device:
|
|
|
|
UserFreeDevice(physdevinfo);
|
|
|
|
CDS_Exit_NoUnlock:
|
|
|
|
if (!bKernelMode && strDevice.Buffer)
|
|
UserFreePool(strDevice.Buffer);
|
|
|
|
if (pCaptDevmode)
|
|
UserFreePool(pCaptDevmode);
|
|
|
|
TRACE_INIT(("ChangeDisplaySettings - Leaving, Status = %d\n", status));
|
|
|
|
return status;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* UserGetVgaHandle
|
|
*
|
|
* Returns the VGA handle back to GDI. May be NULL if there's no VGA.
|
|
*
|
|
* 03-Mar-1996 andrewgo Created
|
|
\***************************************************************************/
|
|
|
|
HANDLE UserGetVgaHandle(
|
|
VOID)
|
|
{
|
|
return(gphysDevInfo[0].pDeviceHandle);
|
|
}
|