reactos/win32ss/user/winsrv/consrv/frontends/gui/conwnd.c
Hermès Bélusca-Maïto 97277b4deb
[CONSRV] Work-around buggy WM_MOUSEMOVE events received when VBox Mouse Integration is enabled. (#5441)
CORE-8394

These caused the "Ignore-next-mouse-event" mechanism of the console
(used e.g. in QuickEdit mode for not triggering the appearance of the
context menu, etc.) to not work.

Please note that these buggy events, that arise when testing ReactOS
in VirtualBox with Mouse Integration is enabled, do not show up when
running instead Windows (2003, ...) in the same configured VM.

Addendum to commits ac51557 (r63792) and 33d8a4b (r67218).

Improved fix for the one reported in PR #5406 by contributor 'whindsaks',
as it keeps the separation between the flag that manages the working-around
of the bug, and the other flag that is used for ignoring the genuine next
mouse event that follows mouse-button-down events.
2023-07-16 19:13:14 +02:00

2663 lines
84 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS Console Server DLL
* FILE: win32ss/user/winsrv/consrv/frontends/gui/conwnd.c
* PURPOSE: GUI Console Window Class
* PROGRAMMERS: Gé van Geldorp
* Johannes Anderwald
* Jeffrey Morlan
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
* Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
*/
/* INCLUDES *******************************************************************/
#include <consrv.h>
#include <intrin.h>
#include <windowsx.h>
#include <shellapi.h>
#define NDEBUG
#include <debug.h>
#include "concfg/font.h"
#include "guiterm.h"
#include "resource.h"
/* GLOBALS ********************************************************************/
// #define PM_CREATE_CONSOLE (WM_APP + 1)
// #define PM_DESTROY_CONSOLE (WM_APP + 2)
// See guiterm.c
#define CONGUI_MIN_WIDTH 10
#define CONGUI_MIN_HEIGHT 10
#define CONGUI_UPDATE_TIME 0
#define CONGUI_UPDATE_TIMER 1
#define CURSOR_BLINK_TIME 500
/**************************************************************\
\** Define the Console Leader Process for the console window **/
#define GWLP_CONWND_ALLOC (2 * sizeof(LONG_PTR))
#define GWLP_CONSOLE_LEADER_PID 0
#define GWLP_CONSOLE_LEADER_TID 4
VOID
SetConWndConsoleLeaderCID(IN PGUI_CONSOLE_DATA GuiData)
{
PCONSOLE_PROCESS_DATA ProcessData;
ProcessData = ConSrvGetConsoleLeaderProcess(GuiData->Console);
ASSERT(ProcessData != NULL);
DPRINT("ProcessData: %p, ProcessData->Process %p.\n", ProcessData, ProcessData->Process);
if (ProcessData->Process)
{
CLIENT_ID ConsoleLeaderCID = ProcessData->Process->ClientId;
SetWindowLongPtrW(GuiData->hWindow, GWLP_CONSOLE_LEADER_PID,
(LONG_PTR)(ConsoleLeaderCID.UniqueProcess));
SetWindowLongPtrW(GuiData->hWindow, GWLP_CONSOLE_LEADER_TID,
(LONG_PTR)(ConsoleLeaderCID.UniqueThread));
}
else
{
SetWindowLongPtrW(GuiData->hWindow, GWLP_CONSOLE_LEADER_PID, 0);
SetWindowLongPtrW(GuiData->hWindow, GWLP_CONSOLE_LEADER_TID, 0);
}
}
/**************************************************************/
HICON ghDefaultIcon = NULL;
HICON ghDefaultIconSm = NULL;
HCURSOR ghDefaultCursor = NULL;
typedef struct _GUICONSOLE_MENUITEM
{
UINT uID;
const struct _GUICONSOLE_MENUITEM *SubMenu;
WORD wCmdID;
} GUICONSOLE_MENUITEM, *PGUICONSOLE_MENUITEM;
static const GUICONSOLE_MENUITEM GuiConsoleEditMenuItems[] =
{
{ IDS_MARK, NULL, ID_SYSTEM_EDIT_MARK },
{ IDS_COPY, NULL, ID_SYSTEM_EDIT_COPY },
{ IDS_PASTE, NULL, ID_SYSTEM_EDIT_PASTE },
{ IDS_SELECTALL, NULL, ID_SYSTEM_EDIT_SELECTALL },
{ IDS_SCROLL, NULL, ID_SYSTEM_EDIT_SCROLL },
{ IDS_FIND, NULL, ID_SYSTEM_EDIT_FIND },
{ 0, NULL, 0 } /* End of list */
};
static const GUICONSOLE_MENUITEM GuiConsoleMainMenuItems[] =
{
{ IDS_EDIT, GuiConsoleEditMenuItems, 0 },
{ IDS_DEFAULTS, NULL, ID_SYSTEM_DEFAULTS },
{ IDS_PROPERTIES, NULL, ID_SYSTEM_PROPERTIES },
{ 0, NULL, 0 } /* End of list */
};
/*
* Default 16-color palette for foreground and background
* (corresponding flags in comments).
*/
const COLORREF s_Colors[16] =
{
RGB(0, 0, 0), // (Black)
RGB(0, 0, 128), // BLUE
RGB(0, 128, 0), // GREEN
RGB(0, 128, 128), // BLUE | GREEN
RGB(128, 0, 0), // RED
RGB(128, 0, 128), // BLUE | RED
RGB(128, 128, 0), // GREEN | RED
RGB(192, 192, 192), // BLUE | GREEN | RED
RGB(128, 128, 128), // (Grey) INTENSITY
RGB(0, 0, 255), // BLUE | INTENSITY
RGB(0, 255, 0), // GREEN | INTENSITY
RGB(0, 255, 255), // BLUE | GREEN | INTENSITY
RGB(255, 0, 0), // RED | INTENSITY
RGB(255, 0, 255), // BLUE | RED | INTENSITY
RGB(255, 255, 0), // GREEN | RED | INTENSITY
RGB(255, 255, 255) // BLUE | GREEN | RED | INTENSITY
};
/* FUNCTIONS ******************************************************************/
static LRESULT CALLBACK
ConWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOLEAN
RegisterConWndClass(IN HINSTANCE hInstance)
{
WNDCLASSEXW WndClass;
ATOM WndClassAtom;
ghDefaultIcon = LoadImageW(hInstance,
MAKEINTRESOURCEW(IDI_TERMINAL),
IMAGE_ICON,
GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON),
LR_SHARED);
ghDefaultIconSm = LoadImageW(hInstance,
MAKEINTRESOURCEW(IDI_TERMINAL),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_SHARED);
ghDefaultCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_ARROW));
WndClass.cbSize = sizeof(WNDCLASSEXW);
WndClass.lpszClassName = GUI_CONWND_CLASS;
WndClass.lpfnWndProc = ConWndProc;
WndClass.style = CS_DBLCLKS /* | CS_HREDRAW | CS_VREDRAW */;
WndClass.hInstance = hInstance;
WndClass.hIcon = ghDefaultIcon;
WndClass.hIconSm = ghDefaultIconSm;
WndClass.hCursor = ghDefaultCursor;
WndClass.hbrBackground = NULL;
WndClass.lpszMenuName = NULL;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = GWLP_CONWND_ALLOC;
WndClassAtom = RegisterClassExW(&WndClass);
if (WndClassAtom == 0)
{
DPRINT1("Failed to register GUI console class\n");
}
else
{
NtUserConsoleControl(GuiConsoleWndClassAtom, &WndClassAtom, sizeof(ATOM));
}
return (WndClassAtom != 0);
}
BOOLEAN
UnRegisterConWndClass(HINSTANCE hInstance)
{
return !!UnregisterClassW(GUI_CONWND_CLASS, hInstance);
}
static VOID
AppendMenuItems(HMENU hMenu,
const GUICONSOLE_MENUITEM *Items)
{
UINT i = 0;
WCHAR szMenuString[255];
HMENU hSubMenu;
do
{
if (Items[i].uID != (UINT)-1)
{
if (LoadStringW(ConSrvDllInstance,
Items[i].uID,
szMenuString,
ARRAYSIZE(szMenuString)) > 0)
{
if (Items[i].SubMenu != NULL)
{
hSubMenu = CreatePopupMenu();
if (hSubMenu != NULL)
{
AppendMenuItems(hSubMenu, Items[i].SubMenu);
if (!AppendMenuW(hMenu,
MF_STRING | MF_POPUP,
(UINT_PTR)hSubMenu,
szMenuString))
{
DestroyMenu(hSubMenu);
}
}
}
else
{
AppendMenuW(hMenu,
MF_STRING,
Items[i].wCmdID,
szMenuString);
}
}
}
else
{
AppendMenuW(hMenu,
MF_SEPARATOR,
0,
NULL);
}
i++;
} while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
}
//static
VOID
CreateSysMenu(HWND hWnd)
{
MENUITEMINFOW mii;
HMENU hMenu;
PWCHAR ptrTab;
WCHAR szMenuStringBack[255];
hMenu = GetSystemMenu(hWnd, FALSE);
if (hMenu == NULL)
return;
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_STRING;
mii.dwTypeData = szMenuStringBack;
mii.cch = ARRAYSIZE(szMenuStringBack);
GetMenuItemInfoW(hMenu, SC_CLOSE, FALSE, &mii);
ptrTab = wcschr(szMenuStringBack, L'\t');
if (ptrTab)
{
*ptrTab = L'\0';
mii.cch = (UINT)wcslen(szMenuStringBack);
SetMenuItemInfoW(hMenu, SC_CLOSE, FALSE, &mii);
}
AppendMenuItems(hMenu, GuiConsoleMainMenuItems);
DrawMenuBar(hWnd);
}
static VOID
SendMenuEvent(PCONSRV_CONSOLE Console, UINT CmdId)
{
INPUT_RECORD er;
DPRINT("Menu item ID: %d\n", CmdId);
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
/* Send a menu event */
er.EventType = MENU_EVENT;
er.Event.MenuEvent.dwCommandId = CmdId;
ConioProcessInputEvent(Console, &er);
LeaveCriticalSection(&Console->Lock);
}
static VOID
Copy(PGUI_CONSOLE_DATA GuiData);
static VOID
Paste(PGUI_CONSOLE_DATA GuiData);
static VOID
UpdateSelection(PGUI_CONSOLE_DATA GuiData,
PCOORD SelectionAnchor OPTIONAL,
PCOORD coord);
static VOID
Mark(PGUI_CONSOLE_DATA GuiData)
{
PCONSOLE_SCREEN_BUFFER ActiveBuffer = GuiData->ActiveBuffer;
/* Clear the old selection */
GuiData->Selection.dwFlags = CONSOLE_NO_SELECTION;
/* Restart a new selection */
GuiData->dwSelectionCursor = ActiveBuffer->ViewOrigin;
UpdateSelection(GuiData,
&GuiData->dwSelectionCursor,
&GuiData->dwSelectionCursor);
}
static VOID
SelectAll(PGUI_CONSOLE_DATA GuiData)
{
PCONSOLE_SCREEN_BUFFER ActiveBuffer = GuiData->ActiveBuffer;
COORD SelectionAnchor;
/* Clear the old selection */
GuiData->Selection.dwFlags = CONSOLE_NO_SELECTION;
/*
* The selection area extends to the whole screen buffer's width.
*/
SelectionAnchor.X = SelectionAnchor.Y = 0;
GuiData->dwSelectionCursor.X = ActiveBuffer->ScreenBufferSize.X - 1;
/*
* Determine whether the selection must extend to just some part
* (for text-mode screen buffers) or to all of the screen buffer's
* height (for graphics ones).
*/
if (GetType(ActiveBuffer) == TEXTMODE_BUFFER)
{
/*
* We select all the characters from the first line
* to the line where the cursor is positioned.
*/
GuiData->dwSelectionCursor.Y = ActiveBuffer->CursorPosition.Y;
}
else /* if (GetType(ActiveBuffer) == GRAPHICS_BUFFER) */
{
/*
* We select all the screen buffer area.
*/
GuiData->dwSelectionCursor.Y = ActiveBuffer->ScreenBufferSize.Y - 1;
}
/* Restart a new selection */
GuiData->Selection.dwFlags |= CONSOLE_MOUSE_SELECTION;
UpdateSelection(GuiData, &SelectionAnchor, &GuiData->dwSelectionCursor);
}
static LRESULT
OnCommand(PGUI_CONSOLE_DATA GuiData, WPARAM wParam, LPARAM lParam)
{
LRESULT Ret = TRUE;
PCONSRV_CONSOLE Console = GuiData->Console;
/*
* In case the selected menu item belongs to the user-reserved menu id range,
* send to him a menu event and return directly. The user must handle those
* reserved menu commands...
*/
if (GuiData->CmdIdLow <= (UINT)wParam && (UINT)wParam <= GuiData->CmdIdHigh)
{
SendMenuEvent(Console, (UINT)wParam);
goto Quit;
}
/* ... otherwise, perform actions. */
switch (wParam)
{
case ID_SYSTEM_EDIT_MARK:
Mark(GuiData);
break;
case ID_SYSTEM_EDIT_COPY:
Copy(GuiData);
break;
case ID_SYSTEM_EDIT_PASTE:
Paste(GuiData);
break;
case ID_SYSTEM_EDIT_SELECTALL:
SelectAll(GuiData);
break;
case ID_SYSTEM_EDIT_SCROLL:
DPRINT1("Scrolling is not handled yet\n");
break;
case ID_SYSTEM_EDIT_FIND:
DPRINT1("Finding is not handled yet\n");
break;
case ID_SYSTEM_DEFAULTS:
GuiConsoleShowConsoleProperties(GuiData, TRUE);
break;
case ID_SYSTEM_PROPERTIES:
GuiConsoleShowConsoleProperties(GuiData, FALSE);
break;
default:
Ret = FALSE;
break;
}
Quit:
if (!Ret)
Ret = DefWindowProcW(GuiData->hWindow, WM_SYSCOMMAND, wParam, lParam);
return Ret;
}
static PGUI_CONSOLE_DATA
GuiGetGuiData(HWND hWnd)
{
/* This function ensures that the console pointer is not NULL */
PGUI_CONSOLE_DATA GuiData = (PGUI_CONSOLE_DATA)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
return ( ((GuiData == NULL) || (GuiData->hWindow == hWnd && GuiData->Console != NULL)) ? GuiData : NULL );
}
static VOID
ResizeConWnd(PGUI_CONSOLE_DATA GuiData, DWORD WidthUnit, DWORD HeightUnit)
{
PCONSOLE_SCREEN_BUFFER Buff = GuiData->ActiveBuffer;
SCROLLINFO sInfo;
DWORD Width, Height;
Width = Buff->ViewSize.X * WidthUnit +
2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE));
Height = Buff->ViewSize.Y * HeightUnit +
2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION);
/* Set scrollbar sizes */
sInfo.cbSize = sizeof(sInfo);
sInfo.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
sInfo.nMin = 0;
if (Buff->ScreenBufferSize.Y > Buff->ViewSize.Y)
{
sInfo.nMax = Buff->ScreenBufferSize.Y - 1;
sInfo.nPage = Buff->ViewSize.Y;
sInfo.nPos = Buff->ViewOrigin.Y;
SetScrollInfo(GuiData->hWindow, SB_VERT, &sInfo, TRUE);
Width += GetSystemMetrics(SM_CXVSCROLL);
ShowScrollBar(GuiData->hWindow, SB_VERT, TRUE);
}
else
{
ShowScrollBar(GuiData->hWindow, SB_VERT, FALSE);
}
if (Buff->ScreenBufferSize.X > Buff->ViewSize.X)
{
sInfo.nMax = Buff->ScreenBufferSize.X - 1;
sInfo.nPage = Buff->ViewSize.X;
sInfo.nPos = Buff->ViewOrigin.X;
SetScrollInfo(GuiData->hWindow, SB_HORZ, &sInfo, TRUE);
Height += GetSystemMetrics(SM_CYHSCROLL);
ShowScrollBar(GuiData->hWindow, SB_HORZ, TRUE);
}
else
{
ShowScrollBar(GuiData->hWindow, SB_HORZ, FALSE);
}
/* Resize the window */
SetWindowPos(GuiData->hWindow, NULL, 0, 0, Width, Height,
SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS);
// NOTE: The SWP_NOCOPYBITS flag can be replaced by a subsequent call
// to: InvalidateRect(GuiData->hWindow, NULL, TRUE);
}
VOID
DeleteFonts(PGUI_CONSOLE_DATA GuiData)
{
ULONG i;
for (i = 0; i < ARRAYSIZE(GuiData->Font); ++i)
{
if (GuiData->Font[i] != NULL) DeleteObject(GuiData->Font[i]);
GuiData->Font[i] = NULL;
}
}
static HFONT
CreateDerivedFont(HFONT OrgFont,
// COORD FontSize,
ULONG FontWeight,
// BOOLEAN bItalic,
BOOLEAN bUnderline,
BOOLEAN bStrikeOut)
{
LOGFONTW lf;
/* Initialize the LOGFONT structure */
RtlZeroMemory(&lf, sizeof(lf));
/* Retrieve the details of the current font */
if (GetObjectW(OrgFont, sizeof(lf), &lf) == 0)
return NULL;
/* Change the font attributes */
// lf.lfHeight = FontSize.Y;
// lf.lfWidth = FontSize.X;
lf.lfWeight = FontWeight;
// lf.lfItalic = bItalic;
lf.lfUnderline = bUnderline;
lf.lfStrikeOut = bStrikeOut;
/* Build a new font */
return CreateFontIndirectW(&lf);
}
BOOL
InitFonts(
_Inout_ PGUI_CONSOLE_DATA GuiData,
_In_reads_or_z_(LF_FACESIZE)
PCWSTR FaceName,
_In_ ULONG FontWeight,
_In_ ULONG FontFamily,
_In_ COORD FontSize,
_In_opt_ UINT CodePage,
_In_ BOOL UseDefaultFallback)
{
HDC hDC;
HFONT hFont;
FONT_DATA FontData;
UINT OldCharWidth = GuiData->CharWidth;
UINT OldCharHeight = GuiData->CharHeight;
COORD OldFontSize = GuiData->GuiInfo.FontSize;
WCHAR NewFaceName[LF_FACESIZE];
/* Default to current code page if none has been provided */
if (!CodePage)
CodePage = GuiData->Console->OutputCodePage;
/*
* Initialize a new NORMAL font.
*/
/* Copy the requested face name into the local buffer.
* It will be modified in output by CreateConsoleFontEx()
* to hold a possible fallback font face name. */
StringCchCopyNW(NewFaceName, ARRAYSIZE(NewFaceName),
FaceName, LF_FACESIZE);
/* NOTE: FontSize is always in cell height/width units (pixels) */
hFont = CreateConsoleFontEx((LONG)(ULONG)FontSize.Y,
(LONG)(ULONG)FontSize.X,
NewFaceName,
FontWeight,
FontFamily,
CodePage,
UseDefaultFallback,
&FontData);
if (!hFont)
{
DPRINT1("InitFonts: CreateConsoleFontEx('%S') failed\n", NewFaceName);
return FALSE;
}
/* Retrieve its character cell size */
hDC = GetDC(GuiData->hWindow);
if (!GetFontCellSize(hDC, hFont, &GuiData->CharHeight, &GuiData->CharWidth))
{
DPRINT1("InitFonts: GetFontCellSize failed\n");
ReleaseDC(GuiData->hWindow, hDC);
DeleteObject(hFont);
return FALSE;
}
ReleaseDC(GuiData->hWindow, hDC);
/*
* Initialization succeeded.
*/
// Delete all the old fonts first.
DeleteFonts(GuiData);
GuiData->Font[FONT_NORMAL] = hFont;
/*
* Now build the optional fonts (bold, underlined, mixed).
* Do not error in case they fail to be created.
*/
GuiData->Font[FONT_BOLD] =
CreateDerivedFont(GuiData->Font[FONT_NORMAL],
max(FW_BOLD, FontData.Weight),
FALSE,
FALSE);
GuiData->Font[FONT_UNDERLINE] =
CreateDerivedFont(GuiData->Font[FONT_NORMAL],
FontData.Weight,
TRUE,
FALSE);
GuiData->Font[FONT_BOLD | FONT_UNDERLINE] =
CreateDerivedFont(GuiData->Font[FONT_NORMAL],
max(FW_BOLD, FontData.Weight),
TRUE,
FALSE);
/*
* Save the new font characteristics.
*/
StringCchCopyNW(GuiData->GuiInfo.FaceName,
ARRAYSIZE(GuiData->GuiInfo.FaceName),
NewFaceName, ARRAYSIZE(NewFaceName));
GuiData->GuiInfo.FontWeight = FontData.Weight;
GuiData->GuiInfo.FontFamily = FontData.Family;
GuiData->GuiInfo.FontSize = FontData.Size;
/* Resize the terminal, in case the new font has a different size */
if ((OldCharWidth != GuiData->CharWidth) ||
(OldCharHeight != GuiData->CharHeight) ||
(OldFontSize.X != FontData.Size.X ||
OldFontSize.Y != FontData.Size.Y))
{
TermResizeTerminal(GuiData->Console);
}
return TRUE;
}
static BOOL
OnNcCreate(HWND hWnd, LPCREATESTRUCTW Create)
{
PGUI_CONSOLE_DATA GuiData = (PGUI_CONSOLE_DATA)Create->lpCreateParams;
PCONSRV_CONSOLE Console;
if (GuiData == NULL)
{
DPRINT1("GuiConsoleNcCreate: No GUI data\n");
return FALSE;
}
Console = GuiData->Console;
GuiData->hWindow = hWnd;
GuiData->hSysMenu = GetSystemMenu(hWnd, FALSE);
/* Initialize the fonts */
if (!InitFonts(GuiData,
GuiData->GuiInfo.FaceName,
GuiData->GuiInfo.FontWeight,
GuiData->GuiInfo.FontFamily,
GuiData->GuiInfo.FontSize,
0, FALSE))
{
/* Reset only the output code page if we don't have a suitable
* font for it, possibly falling back to "United States (OEM)". */
UINT AltCodePage = GetOEMCP();
if (AltCodePage == Console->OutputCodePage)
AltCodePage = CP_USA;
DPRINT1("Could not initialize font '%S' for code page %d - Resetting CP to %d\n",
GuiData->GuiInfo.FaceName, Console->OutputCodePage, AltCodePage);
CON_SET_OUTPUT_CP(Console, AltCodePage);
/* We will use a fallback font if we cannot find
* anything for this replacement code page. */
if (!InitFonts(GuiData,
GuiData->GuiInfo.FaceName,
GuiData->GuiInfo.FontWeight,
GuiData->GuiInfo.FontFamily,
GuiData->GuiInfo.FontSize,
0, TRUE))
{
DPRINT1("Failed to initialize font '%S' for code page %d\n",
GuiData->GuiInfo.FaceName, Console->OutputCodePage);
DPRINT1("GuiConsoleNcCreate: InitFonts failed\n");
GuiData->hWindow = NULL;
NtSetEvent(GuiData->hGuiInitEvent, NULL);
return FALSE;
}
}
/* Initialize the terminal framebuffer */
GuiData->hMemDC = CreateCompatibleDC(NULL);
GuiData->hBitmap = NULL;
GuiData->hSysPalette = NULL; /* Original system palette */
/* Update the icons of the window */
if (GuiData->hIcon != ghDefaultIcon)
{
DefWindowProcW(GuiData->hWindow, WM_SETICON, ICON_BIG , (LPARAM)GuiData->hIcon );
DefWindowProcW(GuiData->hWindow, WM_SETICON, ICON_SMALL, (LPARAM)GuiData->hIconSm);
}
// FIXME: Keep these instructions here ? ///////////////////////////////////
Console->ActiveBuffer->CursorBlinkOn = TRUE;
Console->ActiveBuffer->ForceCursorOff = FALSE;
////////////////////////////////////////////////////////////////////////////
SetWindowLongPtrW(GuiData->hWindow, GWLP_USERDATA, (DWORD_PTR)GuiData);
if (GuiData->IsWindowVisible)
{
SetTimer(GuiData->hWindow, CONGUI_UPDATE_TIMER, CONGUI_UPDATE_TIME, NULL);
}
// FIXME: HACK: Potential HACK for CORE-8129; see revision 63595.
//CreateSysMenu(GuiData->hWindow);
DPRINT("OnNcCreate - setting start event\n");
NtSetEvent(GuiData->hGuiInitEvent, NULL);
/* We accept dropped files */
DragAcceptFiles(GuiData->hWindow, TRUE);
return (BOOL)DefWindowProcW(GuiData->hWindow, WM_NCCREATE, 0, (LPARAM)Create);
}
static VOID
OnActivate(PGUI_CONSOLE_DATA GuiData, WPARAM wParam)
{
WORD ActivationState = LOWORD(wParam);
DPRINT("WM_ACTIVATE - ActivationState = %d\n", ActivationState);
if ( ActivationState == WA_ACTIVE ||
ActivationState == WA_CLICKACTIVE )
{
if (GuiData->GuiInfo.FullScreen)
{
EnterFullScreen(GuiData);
// // PostMessageW(GuiData->hWindow, WM_SYSCOMMAND, SC_RESTORE, 0);
// SendMessageW(GuiData->hWindow, WM_SYSCOMMAND, SC_RESTORE, 0);
}
}
else // if (ActivationState == WA_INACTIVE)
{
if (GuiData->GuiInfo.FullScreen)
{
SendMessageW(GuiData->hWindow, WM_SYSCOMMAND, SC_MINIMIZE, 0);
LeaveFullScreen(GuiData);
// // PostMessageW(GuiData->hWindow, WM_SYSCOMMAND, SC_MINIMIZE, 0);
// SendMessageW(GuiData->hWindow, WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
}
/*
* Ignore the next mouse event when we are going to be enabled again via
* the mouse, in order to prevent, e.g. when we are in Edit mode, erroneous
* mouse actions from the user that could spoil text selection or copy/pastes.
*/
if (ActivationState == WA_CLICKACTIVE)
GuiData->IgnoreNextMouseEvent = TRUE;
}
static VOID
OnFocus(PGUI_CONSOLE_DATA GuiData, BOOL SetFocus)
{
PCONSRV_CONSOLE Console = GuiData->Console;
INPUT_RECORD er;
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
/* Set console focus state */
Console->HasFocus = SetFocus;
/*
* Set the priority of the processes of this console
* in accordance with the console focus state.
*/
ConSrvSetConsoleProcessFocus(Console, SetFocus);
/* Send a focus event */
er.EventType = FOCUS_EVENT;
er.Event.FocusEvent.bSetFocus = SetFocus;
ConioProcessInputEvent(Console, &er);
LeaveCriticalSection(&Console->Lock);
if (SetFocus)
DPRINT("TODO: Create console caret\n");
else
DPRINT("TODO: Destroy console caret\n");
}
VOID
GetSelectionBeginEnd(PCOORD Begin, PCOORD End,
PCOORD SelectionAnchor,
PSMALL_RECT SmallRect)
{
if (Begin == NULL || End == NULL) return;
*Begin = *SelectionAnchor;
End->X = (SelectionAnchor->X == SmallRect->Left) ? SmallRect->Right
/* Case X != Left, must be == Right */ : SmallRect->Left;
End->Y = (SelectionAnchor->Y == SmallRect->Top ) ? SmallRect->Bottom
/* Case Y != Top, must be == Bottom */ : SmallRect->Top;
/* Exchange Begin / End if Begin > End lexicographically */
if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X))
{
End->X = _InterlockedExchange16(&Begin->X, End->X);
End->Y = _InterlockedExchange16(&Begin->Y, End->Y);
}
}
static HRGN
CreateSelectionRgn(PGUI_CONSOLE_DATA GuiData,
BOOL LineSelection,
PCOORD SelectionAnchor,
PSMALL_RECT SmallRect)
{
if (!LineSelection)
{
RECT rect;
SmallRectToRect(GuiData, &rect, SmallRect);
return CreateRectRgnIndirect(&rect);
}
else
{
HRGN SelRgn;
COORD Begin, End;
GetSelectionBeginEnd(&Begin, &End, SelectionAnchor, SmallRect);
if (Begin.Y == End.Y)
{
SMALL_RECT sr;
RECT r ;
sr.Left = Begin.X;
sr.Top = Begin.Y;
sr.Right = End.X;
sr.Bottom = End.Y;
// Debug thingie to see whether I can put this corner case
// together with the previous one.
if (SmallRect->Left != sr.Left ||
SmallRect->Top != sr.Top ||
SmallRect->Right != sr.Right ||
SmallRect->Bottom != sr.Bottom)
{
DPRINT1("\n"
"SmallRect = (%d, %d, %d, %d)\n"
"sr = (%d, %d, %d, %d)\n"
"\n",
SmallRect->Left, SmallRect->Top, SmallRect->Right, SmallRect->Bottom,
sr.Left, sr.Top, sr.Right, sr.Bottom);
}
SmallRectToRect(GuiData, &r, &sr);
SelRgn = CreateRectRgnIndirect(&r);
}
else
{
PCONSOLE_SCREEN_BUFFER ActiveBuffer = GuiData->ActiveBuffer;
HRGN rg1, rg2, rg3;
SMALL_RECT sr1, sr2, sr3;
RECT r1 , r2 , r3 ;
sr1.Left = Begin.X;
sr1.Top = Begin.Y;
sr1.Right = ActiveBuffer->ScreenBufferSize.X - 1;
sr1.Bottom = Begin.Y;
sr2.Left = 0;
sr2.Top = Begin.Y + 1;
sr2.Right = ActiveBuffer->ScreenBufferSize.X - 1;
sr2.Bottom = End.Y - 1;
sr3.Left = 0;
sr3.Top = End.Y;
sr3.Right = End.X;
sr3.Bottom = End.Y;
SmallRectToRect(GuiData, &r1, &sr1);
SmallRectToRect(GuiData, &r2, &sr2);
SmallRectToRect(GuiData, &r3, &sr3);
rg1 = CreateRectRgnIndirect(&r1);
rg2 = CreateRectRgnIndirect(&r2);
rg3 = CreateRectRgnIndirect(&r3);
CombineRgn(rg1, rg1, rg2, RGN_XOR);
CombineRgn(rg1, rg1, rg3, RGN_XOR);
DeleteObject(rg3);
DeleteObject(rg2);
SelRgn = rg1;
}
return SelRgn;
}
}
static VOID
PaintSelectionRect(PGUI_CONSOLE_DATA GuiData, PPAINTSTRUCT pps)
{
HRGN rgnPaint = CreateRectRgnIndirect(&pps->rcPaint);
HRGN rgnSel = CreateSelectionRgn(GuiData, GuiData->LineSelection,
&GuiData->Selection.dwSelectionAnchor,
&GuiData->Selection.srSelection);
/* Invert the selection */
int ErrorCode = CombineRgn(rgnPaint, rgnPaint, rgnSel, RGN_AND);
if (ErrorCode != ERROR && ErrorCode != NULLREGION)
{
InvertRgn(pps->hdc, rgnPaint);
}
DeleteObject(rgnSel);
DeleteObject(rgnPaint);
}
static VOID
UpdateSelection(PGUI_CONSOLE_DATA GuiData,
PCOORD SelectionAnchor OPTIONAL,
PCOORD coord)
{
PCONSRV_CONSOLE Console = GuiData->Console;
HRGN oldRgn = CreateSelectionRgn(GuiData, GuiData->LineSelection,
&GuiData->Selection.dwSelectionAnchor,
&GuiData->Selection.srSelection);
/* Update the anchor if needed (use the old one if NULL) */
if (SelectionAnchor)
GuiData->Selection.dwSelectionAnchor = *SelectionAnchor;
// TODO: Scroll buffer to bring 'coord' into view
if (coord != NULL)
{
SMALL_RECT rc;
HRGN newRgn;
/*
* Pressing the Control key while selecting text, allows us to enter
* into line-selection mode, the selection mode of *nix terminals.
*/
BOOL OldLineSel = GuiData->LineSelection;
GuiData->LineSelection = !!(GetKeyState(VK_CONTROL) & KEY_PRESSED);
/* Exchange left/top with right/bottom if required */
rc.Left = min(GuiData->Selection.dwSelectionAnchor.X, coord->X);
rc.Top = min(GuiData->Selection.dwSelectionAnchor.Y, coord->Y);
rc.Right = max(GuiData->Selection.dwSelectionAnchor.X, coord->X);
rc.Bottom = max(GuiData->Selection.dwSelectionAnchor.Y, coord->Y);
newRgn = CreateSelectionRgn(GuiData, GuiData->LineSelection,
&GuiData->Selection.dwSelectionAnchor,
&rc);
if (GuiData->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
{
if (OldLineSel != GuiData->LineSelection ||
memcmp(&rc, &GuiData->Selection.srSelection, sizeof(SMALL_RECT)) != 0)
{
/* Calculate the region that needs to be updated */
if (oldRgn && newRgn && CombineRgn(newRgn, newRgn, oldRgn, RGN_XOR) != ERROR)
{
InvalidateRgn(GuiData->hWindow, newRgn, FALSE);
}
}
}
else
{
InvalidateRgn(GuiData->hWindow, newRgn, FALSE);
}
DeleteObject(newRgn);
GuiData->Selection.dwFlags |= CONSOLE_SELECTION_NOT_EMPTY;
GuiData->Selection.srSelection = rc;
GuiData->dwSelectionCursor = *coord;
if ((GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) == 0)
{
LPWSTR SelTypeStr = NULL , WindowTitle = NULL;
SIZE_T SelTypeStrLength = 0, Length = 0;
/* Clear the old selection */
if (GuiData->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
{
InvalidateRgn(GuiData->hWindow, oldRgn, FALSE);
}
/*
* When passing a zero-length buffer size, LoadString(...) returns
* a read-only pointer buffer to the program's resource string.
*/
SelTypeStrLength =
LoadStringW(ConSrvDllInstance,
(GuiData->Selection.dwFlags & CONSOLE_MOUSE_SELECTION)
? IDS_SELECT_TITLE : IDS_MARK_TITLE,
(LPWSTR)&SelTypeStr, 0);
/*
* Prepend the selection type string to the current console title
* if we succeeded in retrieving a valid localized string.
*/
if (SelTypeStr)
{
// 3 for " - " and 1 for NULL
Length = Console->Title.Length + (SelTypeStrLength + 3 + 1) * sizeof(WCHAR);
WindowTitle = ConsoleAllocHeap(0, Length);
wcsncpy(WindowTitle, SelTypeStr, SelTypeStrLength);
WindowTitle[SelTypeStrLength] = UNICODE_NULL;
wcscat(WindowTitle, L" - ");
wcscat(WindowTitle, Console->Title.Buffer);
SetWindowTextW(GuiData->hWindow, WindowTitle);
ConsoleFreeHeap(WindowTitle);
}
GuiData->Selection.dwFlags |= CONSOLE_SELECTION_IN_PROGRESS;
ConioPause(Console, PAUSED_FROM_SELECTION);
}
}
else
{
/* Clear the selection */
if (GuiData->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
{
InvalidateRgn(GuiData->hWindow, oldRgn, FALSE);
}
GuiData->Selection.dwFlags = CONSOLE_NO_SELECTION;
ConioUnpause(Console, PAUSED_FROM_SELECTION);
/* Restore the console title */
SetWindowTextW(GuiData->hWindow, Console->Title.Buffer);
}
DeleteObject(oldRgn);
}
static VOID
OnPaint(PGUI_CONSOLE_DATA GuiData)
{
PCONSOLE_SCREEN_BUFFER ActiveBuffer = GuiData->ActiveBuffer;
PAINTSTRUCT ps;
RECT rcPaint;
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible) return;
BeginPaint(GuiData->hWindow, &ps);
if (ps.hdc != NULL &&
ps.rcPaint.left < ps.rcPaint.right &&
ps.rcPaint.top < ps.rcPaint.bottom)
{
EnterCriticalSection(&GuiData->Lock);
/* Compose the current screen-buffer on-memory */
if (GetType(ActiveBuffer) == TEXTMODE_BUFFER)
{
GuiPaintTextModeBuffer((PTEXTMODE_SCREEN_BUFFER)ActiveBuffer,
GuiData, &ps.rcPaint, &rcPaint);
}
else /* if (GetType(ActiveBuffer) == GRAPHICS_BUFFER) */
{
GuiPaintGraphicsBuffer((PGRAPHICS_SCREEN_BUFFER)ActiveBuffer,
GuiData, &ps.rcPaint, &rcPaint);
}
/* Send it to screen */
BitBlt(ps.hdc,
ps.rcPaint.left,
ps.rcPaint.top,
rcPaint.right - rcPaint.left,
rcPaint.bottom - rcPaint.top,
GuiData->hMemDC,
rcPaint.left,
rcPaint.top,
SRCCOPY);
/* Draw the selection region if needed */
if (GuiData->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY)
{
PaintSelectionRect(GuiData, &ps);
}
// TODO: Move cursor display here!
LeaveCriticalSection(&GuiData->Lock);
}
EndPaint(GuiData->hWindow, &ps);
return;
}
static VOID
OnPaletteChanged(PGUI_CONSOLE_DATA GuiData)
{
PCONSOLE_SCREEN_BUFFER ActiveBuffer = GuiData->ActiveBuffer;
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible) return;
// See WM_PALETTECHANGED message
// if ((HWND)wParam == hWnd) break;
// if (GetType(ActiveBuffer) == GRAPHICS_BUFFER)
if (ActiveBuffer->PaletteHandle)
{
DPRINT("WM_PALETTECHANGED changing palette\n");
/* Specify the use of the system palette for the framebuffer */
SetSystemPaletteUse(GuiData->hMemDC, ActiveBuffer->PaletteUsage);
/* Realize the (logical) palette */
RealizePalette(GuiData->hMemDC);
}
}
static BOOL
IsSystemKey(WORD VirtualKeyCode)
{
switch (VirtualKeyCode)
{
/* From MSDN, "Virtual-Key Codes" */
case VK_RETURN:
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
case VK_PAUSE:
case VK_CAPITAL:
case VK_ESCAPE:
case VK_LWIN:
case VK_RWIN:
case VK_NUMLOCK:
case VK_SCROLL:
return TRUE;
default:
return FALSE;
}
}
static VOID
OnKey(PGUI_CONSOLE_DATA GuiData, UINT msg, WPARAM wParam, LPARAM lParam)
{
PCONSRV_CONSOLE Console = GuiData->Console;
PCONSOLE_SCREEN_BUFFER ActiveBuffer;
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
ActiveBuffer = GuiData->ActiveBuffer;
if (GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS)
{
WORD VirtualKeyCode = LOWORD(wParam);
if (msg != WM_KEYDOWN) goto Quit;
if (VirtualKeyCode == VK_RETURN)
{
/* Copy (and clear) selection if ENTER is pressed */
Copy(GuiData);
goto Quit;
}
else if ( VirtualKeyCode == VK_ESCAPE ||
(VirtualKeyCode == 'C' && (GetKeyState(VK_CONTROL) & KEY_PRESSED)) )
{
/* Cancel selection if ESC or Ctrl-C are pressed */
UpdateSelection(GuiData, NULL, NULL);
goto Quit;
}
if ((GuiData->Selection.dwFlags & CONSOLE_MOUSE_SELECTION) == 0)
{
/* Keyboard selection mode */
BOOL Interpreted = FALSE;
BOOL MajPressed = !!(GetKeyState(VK_SHIFT) & KEY_PRESSED);
switch (VirtualKeyCode)
{
case VK_LEFT:
{
Interpreted = TRUE;
if (GuiData->dwSelectionCursor.X > 0)
GuiData->dwSelectionCursor.X--;
break;
}
case VK_RIGHT:
{
Interpreted = TRUE;
if (GuiData->dwSelectionCursor.X < ActiveBuffer->ScreenBufferSize.X - 1)
GuiData->dwSelectionCursor.X++;
break;
}
case VK_UP:
{
Interpreted = TRUE;
if (GuiData->dwSelectionCursor.Y > 0)
GuiData->dwSelectionCursor.Y--;
break;
}
case VK_DOWN:
{
Interpreted = TRUE;
if (GuiData->dwSelectionCursor.Y < ActiveBuffer->ScreenBufferSize.Y - 1)
GuiData->dwSelectionCursor.Y++;
break;
}
case VK_HOME:
{
Interpreted = TRUE;
GuiData->dwSelectionCursor.X = 0;
GuiData->dwSelectionCursor.Y = 0;
break;
}
case VK_END:
{
Interpreted = TRUE;
GuiData->dwSelectionCursor.Y = ActiveBuffer->ScreenBufferSize.Y - 1;
break;
}
case VK_PRIOR:
{
Interpreted = TRUE;
GuiData->dwSelectionCursor.Y -= ActiveBuffer->ViewSize.Y;
if (GuiData->dwSelectionCursor.Y < 0)
GuiData->dwSelectionCursor.Y = 0;
break;
}
case VK_NEXT:
{
Interpreted = TRUE;
GuiData->dwSelectionCursor.Y += ActiveBuffer->ViewSize.Y;
if (GuiData->dwSelectionCursor.Y >= ActiveBuffer->ScreenBufferSize.Y)
GuiData->dwSelectionCursor.Y = ActiveBuffer->ScreenBufferSize.Y - 1;
break;
}
default:
break;
}
if (Interpreted)
{
UpdateSelection(GuiData,
!MajPressed ? &GuiData->dwSelectionCursor : NULL,
&GuiData->dwSelectionCursor);
}
else if (!IsSystemKey(VirtualKeyCode))
{
/* Emit an error beep sound */
SendNotifyMessage(GuiData->hWindow, PM_CONSOLE_BEEP, 0, 0);
}
goto Quit;
}
else
{
/* Mouse selection mode */
if (!IsSystemKey(VirtualKeyCode))
{
/* Clear the selection and send the key into the input buffer */
UpdateSelection(GuiData, NULL, NULL);
}
else
{
goto Quit;
}
}
}
if ((GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) == 0)
{
MSG Message;
Message.hwnd = GuiData->hWindow;
Message.message = msg;
Message.wParam = wParam;
Message.lParam = lParam;
ConioProcessKey(Console, &Message);
}
Quit:
LeaveCriticalSection(&Console->Lock);
}
// FIXME: Remove after fixing OnTimer
VOID
InvalidateCell(PGUI_CONSOLE_DATA GuiData,
SHORT x, SHORT y);
static VOID
OnTimer(PGUI_CONSOLE_DATA GuiData)
{
PCONSRV_CONSOLE Console = GuiData->Console;
PCONSOLE_SCREEN_BUFFER Buff;
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible) return;
SetTimer(GuiData->hWindow, CONGUI_UPDATE_TIMER, CURSOR_BLINK_TIME, NULL);
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
Buff = GuiData->ActiveBuffer;
if (GetType(Buff) == TEXTMODE_BUFFER)
{
/* Repaint the caret */
InvalidateCell(GuiData, Buff->CursorPosition.X, Buff->CursorPosition.Y);
Buff->CursorBlinkOn = !Buff->CursorBlinkOn;
if ((GuiData->OldCursor.x != Buff->CursorPosition.X) ||
(GuiData->OldCursor.y != Buff->CursorPosition.Y))
{
SCROLLINFO sInfo;
int OldScrollX = -1, OldScrollY = -1;
int NewScrollX = -1, NewScrollY = -1;
sInfo.cbSize = sizeof(sInfo);
sInfo.fMask = SIF_POS;
// Capture the original position of the scroll bars and save them.
if (GetScrollInfo(GuiData->hWindow, SB_HORZ, &sInfo)) OldScrollX = sInfo.nPos;
if (GetScrollInfo(GuiData->hWindow, SB_VERT, &sInfo)) OldScrollY = sInfo.nPos;
// If we successfully got the info for the horizontal scrollbar
if (OldScrollX >= 0)
{
if ((Buff->CursorPosition.X < Buff->ViewOrigin.X) ||
(Buff->CursorPosition.X >= (Buff->ViewOrigin.X + Buff->ViewSize.X)))
{
// Handle the horizontal scroll bar
if (Buff->CursorPosition.X >= Buff->ViewSize.X)
NewScrollX = Buff->CursorPosition.X - Buff->ViewSize.X + 1;
else
NewScrollX = 0;
}
else
{
NewScrollX = OldScrollX;
}
}
// If we successfully got the info for the vertical scrollbar
if (OldScrollY >= 0)
{
if ((Buff->CursorPosition.Y < Buff->ViewOrigin.Y) ||
(Buff->CursorPosition.Y >= (Buff->ViewOrigin.Y + Buff->ViewSize.Y)))
{
// Handle the vertical scroll bar
if (Buff->CursorPosition.Y >= Buff->ViewSize.Y)
NewScrollY = Buff->CursorPosition.Y - Buff->ViewSize.Y + 1;
else
NewScrollY = 0;
}
else
{
NewScrollY = OldScrollY;
}
}
// Adjust scroll bars and refresh the window if the cursor has moved outside the visible area
// NOTE: OldScroll# and NewScroll# will both be -1 (initial value) if the info for the respective scrollbar
// was not obtained successfully in the previous steps. This means their difference is 0 (no scrolling)
// and their associated scrollbar is left alone.
if ((OldScrollX != NewScrollX) || (OldScrollY != NewScrollY))
{
Buff->ViewOrigin.X = NewScrollX;
Buff->ViewOrigin.Y = NewScrollY;
ScrollWindowEx(GuiData->hWindow,
(OldScrollX - NewScrollX) * GuiData->CharWidth,
(OldScrollY - NewScrollY) * GuiData->CharHeight,
NULL,
NULL,
NULL,
NULL,
SW_INVALIDATE);
if (NewScrollX >= 0)
{
sInfo.nPos = NewScrollX;
SetScrollInfo(GuiData->hWindow, SB_HORZ, &sInfo, TRUE);
}
if (NewScrollY >= 0)
{
sInfo.nPos = NewScrollY;
SetScrollInfo(GuiData->hWindow, SB_VERT, &sInfo, TRUE);
}
UpdateWindow(GuiData->hWindow);
// InvalidateRect(GuiData->hWindow, NULL, FALSE);
GuiData->OldCursor.x = Buff->CursorPosition.X;
GuiData->OldCursor.y = Buff->CursorPosition.Y;
}
}
}
else /* if (GetType(Buff) == GRAPHICS_BUFFER) */
{
}
LeaveCriticalSection(&Console->Lock);
}
static BOOL
OnClose(PGUI_CONSOLE_DATA GuiData)
{
PCONSRV_CONSOLE Console = GuiData->Console;
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE))
return TRUE;
// TODO: Prompt for termination ? (Warn the user about possible apps running in this console)
/*
* FIXME: Windows will wait up to 5 seconds for the thread to exit.
* We shouldn't wait here, though, since the console lock is entered.
* A copy of the thread list probably needs to be made.
*/
ConSrvConsoleProcessCtrlEvent(Console, 0, CTRL_CLOSE_EVENT);
LeaveCriticalSection(&Console->Lock);
return FALSE;
}
static LRESULT
OnNcDestroy(HWND hWnd)
{
PGUI_CONSOLE_DATA GuiData = GuiGetGuiData(hWnd);
/* Free the GuiData registration */
SetWindowLongPtrW(hWnd, GWLP_USERDATA, (DWORD_PTR)NULL);
/* Reset the system menu back to default and destroy the previous menu */
GetSystemMenu(hWnd, TRUE);
if (GuiData)
{
if (GuiData->IsWindowVisible)
KillTimer(hWnd, CONGUI_UPDATE_TIMER);
/* Free the terminal framebuffer */
if (GuiData->hMemDC ) DeleteDC(GuiData->hMemDC);
if (GuiData->hBitmap) DeleteObject(GuiData->hBitmap);
// if (GuiData->hSysPalette) DeleteObject(GuiData->hSysPalette);
DeleteFonts(GuiData);
}
return DefWindowProcW(hWnd, WM_NCDESTROY, 0, 0);
}
static VOID
OnScroll(PGUI_CONSOLE_DATA GuiData, INT nBar, WORD sbCode)
{
PCONSRV_CONSOLE Console = GuiData->Console;
PCONSOLE_SCREEN_BUFFER Buff;
SCROLLINFO sInfo;
INT oldPos, Maximum;
PSHORT pOriginXY;
ASSERT(nBar == SB_HORZ || nBar == SB_VERT);
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
Buff = GuiData->ActiveBuffer;
if (nBar == SB_HORZ)
{
Maximum = Buff->ScreenBufferSize.X - Buff->ViewSize.X;
pOriginXY = &Buff->ViewOrigin.X;
}
else // if (nBar == SB_VERT)
{
Maximum = Buff->ScreenBufferSize.Y - Buff->ViewSize.Y;
pOriginXY = &Buff->ViewOrigin.Y;
}
/* Set scrollbar sizes */
sInfo.cbSize = sizeof(sInfo);
sInfo.fMask = SIF_RANGE | SIF_POS | SIF_PAGE | SIF_TRACKPOS;
if (!GetScrollInfo(GuiData->hWindow, nBar, &sInfo)) goto Quit;
oldPos = sInfo.nPos;
switch (sbCode)
{
case SB_LINEUP: // SB_LINELEFT:
sInfo.nPos--;
break;
case SB_LINEDOWN: // SB_LINERIGHT:
sInfo.nPos++;
break;
case SB_PAGEUP: // SB_PAGELEFT:
sInfo.nPos -= sInfo.nPage;
break;
case SB_PAGEDOWN: // SB_PAGERIGHT:
sInfo.nPos += sInfo.nPage;
break;
case SB_THUMBTRACK:
sInfo.nPos = sInfo.nTrackPos;
ConioPause(Console, PAUSED_FROM_SCROLLBAR);
break;
case SB_THUMBPOSITION:
sInfo.nPos = sInfo.nTrackPos;
ConioUnpause(Console, PAUSED_FROM_SCROLLBAR);
break;
case SB_TOP: // SB_LEFT:
sInfo.nPos = sInfo.nMin;
break;
case SB_BOTTOM: // SB_RIGHT:
sInfo.nPos = sInfo.nMax;
break;
default:
break;
}
sInfo.nPos = min(max(sInfo.nPos, 0), Maximum);
if (oldPos != sInfo.nPos)
{
USHORT OldX = Buff->ViewOrigin.X;
USHORT OldY = Buff->ViewOrigin.Y;
UINT WidthUnit, HeightUnit;
/* We now modify Buff->ViewOrigin */
*pOriginXY = sInfo.nPos;
GetScreenBufferSizeUnits(Buff, GuiData, &WidthUnit, &HeightUnit);
ScrollWindowEx(GuiData->hWindow,
(OldX - Buff->ViewOrigin.X) * WidthUnit ,
(OldY - Buff->ViewOrigin.Y) * HeightUnit,
NULL,
NULL,
NULL,
NULL,
SW_INVALIDATE);
sInfo.fMask = SIF_POS;
SetScrollInfo(GuiData->hWindow, nBar, &sInfo, TRUE);
UpdateWindow(GuiData->hWindow);
// InvalidateRect(GuiData->hWindow, NULL, FALSE);
}
Quit:
LeaveCriticalSection(&Console->Lock);
return;
}
static COORD
PointToCoord(PGUI_CONSOLE_DATA GuiData, LPARAM lParam)
{
PCONSOLE_SCREEN_BUFFER Buffer = GuiData->ActiveBuffer;
COORD Coord;
UINT WidthUnit, HeightUnit;
GetScreenBufferSizeUnits(Buffer, GuiData, &WidthUnit, &HeightUnit);
Coord.X = Buffer->ViewOrigin.X + ((SHORT)LOWORD(lParam) / (int)WidthUnit );
Coord.Y = Buffer->ViewOrigin.Y + ((SHORT)HIWORD(lParam) / (int)HeightUnit);
/* Clip coordinate to ensure it's inside buffer */
if (Coord.X < 0)
Coord.X = 0;
else if (Coord.X >= Buffer->ScreenBufferSize.X)
Coord.X = Buffer->ScreenBufferSize.X - 1;
if (Coord.Y < 0)
Coord.Y = 0;
else if (Coord.Y >= Buffer->ScreenBufferSize.Y)
Coord.Y = Buffer->ScreenBufferSize.Y - 1;
return Coord;
}
static LRESULT
OnMouse(PGUI_CONSOLE_DATA GuiData, UINT msg, WPARAM wParam, LPARAM lParam)
{
BOOL DoDefault = FALSE;
PCONSRV_CONSOLE Console = GuiData->Console;
/*
* HACK FOR CORE-8394 (Part 2):
*
* Check whether we should ignore the next mouse move event.
* In either case we reset the HACK flag.
*
* See Part 1 of this hack below.
*/
if (GuiData->HackCORE8394IgnoreNextMove && msg == WM_MOUSEMOVE)
{
GuiData->HackCORE8394IgnoreNextMove = FALSE;
goto Quit;
}
GuiData->HackCORE8394IgnoreNextMove = FALSE;
// FIXME: It's here that we need to check whether we have focus or not
// and whether we are or not in edit mode, in order to know if we need
// to deal with the mouse.
if (GuiData->IgnoreNextMouseEvent)
{
if (msg != WM_LBUTTONDOWN &&
msg != WM_MBUTTONDOWN &&
msg != WM_RBUTTONDOWN &&
msg != WM_XBUTTONDOWN)
{
/*
* If this mouse event is not a button-down action
* then this is the last one being ignored.
*/
GuiData->IgnoreNextMouseEvent = FALSE;
}
else
{
/*
* This mouse event is a button-down action.
* Ignore it and perform default action.
*/
DoDefault = TRUE;
}
goto Quit;
}
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE))
{
DoDefault = TRUE;
goto Quit;
}
if ( (GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) ||
(Console->QuickEdit) )
{
switch (msg)
{
case WM_LBUTTONDOWN:
{
/* Check for selection state */
if ( (GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) &&
(GuiData->Selection.dwFlags & CONSOLE_MOUSE_SELECTION) &&
(GetKeyState(VK_SHIFT) & KEY_PRESSED) )
{
/*
* A mouse selection is currently in progress and the user
* has pressed the SHIFT key and clicked somewhere, update
* the selection.
*/
GuiData->dwSelectionCursor = PointToCoord(GuiData, lParam);
UpdateSelection(GuiData, NULL, &GuiData->dwSelectionCursor);
}
else
{
/* Clear the old selection */
GuiData->Selection.dwFlags = CONSOLE_NO_SELECTION;
/* Restart a new selection */
GuiData->dwSelectionCursor = PointToCoord(GuiData, lParam);
SetCapture(GuiData->hWindow);
GuiData->Selection.dwFlags |= CONSOLE_MOUSE_SELECTION | CONSOLE_MOUSE_DOWN;
UpdateSelection(GuiData,
&GuiData->dwSelectionCursor,
&GuiData->dwSelectionCursor);
}
break;
}
case WM_LBUTTONUP:
{
if (!(GuiData->Selection.dwFlags & CONSOLE_MOUSE_DOWN)) break;
// GuiData->dwSelectionCursor = PointToCoord(GuiData, lParam);
GuiData->Selection.dwFlags &= ~CONSOLE_MOUSE_DOWN;
// UpdateSelection(GuiData, NULL, &GuiData->dwSelectionCursor);
ReleaseCapture();
break;
}
case WM_LBUTTONDBLCLK:
{
PCONSOLE_SCREEN_BUFFER Buffer = GuiData->ActiveBuffer;
if (GetType(Buffer) == TEXTMODE_BUFFER)
{
#define IS_WORD_SEP(c) \
((c) == L'\0' || (c) == L' ' || (c) == L'\t' || (c) == L'\r' || (c) == L'\n')
PTEXTMODE_SCREEN_BUFFER TextBuffer = (PTEXTMODE_SCREEN_BUFFER)Buffer;
COORD cL, cR;
PCHAR_INFO ptrL, ptrR;
/* Starting point */
cL = cR = PointToCoord(GuiData, lParam);
ptrL = ptrR = ConioCoordToPointer(TextBuffer, cL.X, cL.Y);
/* Enlarge the selection by checking for whitespace */
while ((0 < cL.X) && !IS_WORD_SEP(ptrL->Char.UnicodeChar)
&& !IS_WORD_SEP((ptrL-1)->Char.UnicodeChar))
{
--cL.X;
--ptrL;
}
while ((cR.X < TextBuffer->ScreenBufferSize.X - 1) &&
!IS_WORD_SEP(ptrR->Char.UnicodeChar) &&
!IS_WORD_SEP((ptrR+1)->Char.UnicodeChar))
{
++cR.X;
++ptrR;
}
/*
* Update the selection started with the single
* left-click that preceded this double-click.
*/
GuiData->Selection.dwFlags |= CONSOLE_MOUSE_SELECTION | CONSOLE_MOUSE_DOWN;
UpdateSelection(GuiData, &cL, &cR);
/* Ignore the next mouse move event */
GuiData->IgnoreNextMouseEvent = TRUE;
#undef IS_WORD_SEP
}
break;
}
case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
{
if (!(GuiData->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY))
{
Paste(GuiData);
}
else
{
Copy(GuiData);
}
/* Ignore the next mouse move event */
GuiData->IgnoreNextMouseEvent = TRUE;
break;
}
case WM_MOUSEMOVE:
{
if (!(GET_KEYSTATE_WPARAM(wParam) & MK_LBUTTON)) break;
if (!(GuiData->Selection.dwFlags & CONSOLE_MOUSE_DOWN)) break;
GuiData->dwSelectionCursor = PointToCoord(GuiData, lParam);
UpdateSelection(GuiData, NULL, &GuiData->dwSelectionCursor);
break;
}
default:
DoDefault = TRUE; // FALSE;
break;
}
/*
* HACK FOR CORE-8394 (Part 1):
*
* It appears that when running ReactOS on VBox with Mouse Integration
* enabled, the next mouse event coming after a button-down action is
* a mouse-move. However it is NOT always a rule, so that we cannot use
* the IgnoreNextMouseEvent flag to just "ignore" the next mouse event,
* thinking it would always be a mouse-move event.
*
* To work around this problem (that should really be fixed in Win32k),
* we use a second flag to ignore this possible next mouse move event.
*/
switch (msg)
{
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_XBUTTONDOWN:
GuiData->HackCORE8394IgnoreNextMove = TRUE;
default:
break;
}
}
else if (GetConsoleInputBufferMode(Console) & ENABLE_MOUSE_INPUT)
{
INPUT_RECORD er;
WORD wKeyState = GET_KEYSTATE_WPARAM(wParam);
DWORD dwButtonState = 0;
DWORD dwControlKeyState = 0;
DWORD dwEventFlags = 0;
switch (msg)
{
case WM_LBUTTONDOWN:
SetCapture(GuiData->hWindow);
dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
dwEventFlags = 0;
break;
case WM_MBUTTONDOWN:
SetCapture(GuiData->hWindow);
dwButtonState = FROM_LEFT_2ND_BUTTON_PRESSED;
dwEventFlags = 0;
break;
case WM_RBUTTONDOWN:
SetCapture(GuiData->hWindow);
dwButtonState = RIGHTMOST_BUTTON_PRESSED;
dwEventFlags = 0;
break;
case WM_XBUTTONDOWN:
{
/* Get which X-button was pressed */
WORD wButton = GET_XBUTTON_WPARAM(wParam);
/* Check for X-button validity */
if (wButton & ~(XBUTTON1 | XBUTTON2))
{
DPRINT1("X-button 0x%04x invalid\n", wButton);
DoDefault = TRUE;
break;
}
SetCapture(GuiData->hWindow);
dwButtonState = (wButton == XBUTTON1 ? FROM_LEFT_3RD_BUTTON_PRESSED
: FROM_LEFT_4TH_BUTTON_PRESSED);
dwEventFlags = 0;
break;
}
case WM_LBUTTONUP:
ReleaseCapture();
dwButtonState = 0;
dwEventFlags = 0;
break;
case WM_MBUTTONUP:
ReleaseCapture();
dwButtonState = 0;
dwEventFlags = 0;
break;
case WM_RBUTTONUP:
ReleaseCapture();
dwButtonState = 0;
dwEventFlags = 0;
break;
case WM_XBUTTONUP:
{
/* Get which X-button was released */
WORD wButton = GET_XBUTTON_WPARAM(wParam);
/* Check for X-button validity */
if (wButton & ~(XBUTTON1 | XBUTTON2))
{
DPRINT1("X-button 0x%04x invalid\n", wButton);
/* Ok, just release the button anyway... */
}
ReleaseCapture();
dwButtonState = 0;
dwEventFlags = 0;
break;
}
case WM_LBUTTONDBLCLK:
dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
dwEventFlags = DOUBLE_CLICK;
break;
case WM_MBUTTONDBLCLK:
dwButtonState = FROM_LEFT_2ND_BUTTON_PRESSED;
dwEventFlags = DOUBLE_CLICK;
break;
case WM_RBUTTONDBLCLK:
dwButtonState = RIGHTMOST_BUTTON_PRESSED;
dwEventFlags = DOUBLE_CLICK;
break;
case WM_XBUTTONDBLCLK:
{
/* Get which X-button was double-clicked */
WORD wButton = GET_XBUTTON_WPARAM(wParam);
/* Check for X-button validity */
if (wButton & ~(XBUTTON1 | XBUTTON2))
{
DPRINT1("X-button 0x%04x invalid\n", wButton);
DoDefault = TRUE;
break;
}
dwButtonState = (wButton == XBUTTON1 ? FROM_LEFT_3RD_BUTTON_PRESSED
: FROM_LEFT_4TH_BUTTON_PRESSED);
dwEventFlags = DOUBLE_CLICK;
break;
}
case WM_MOUSEMOVE:
dwButtonState = 0;
dwEventFlags = MOUSE_MOVED;
break;
case WM_MOUSEWHEEL:
dwButtonState = GET_WHEEL_DELTA_WPARAM(wParam) << 16;
dwEventFlags = MOUSE_WHEELED;
break;
case WM_MOUSEHWHEEL:
dwButtonState = GET_WHEEL_DELTA_WPARAM(wParam) << 16;
dwEventFlags = MOUSE_HWHEELED;
break;
default:
DoDefault = TRUE;
break;
}
/*
* HACK FOR CORE-8394 (Part 1):
*
* It appears that when running ReactOS on VBox with Mouse Integration
* enabled, the next mouse event coming after a button-down action is
* a mouse-move. However it is NOT always a rule, so that we cannot use
* the IgnoreNextMouseEvent flag to just "ignore" the next mouse event,
* thinking it would always be a mouse-move event.
*
* To work around this problem (that should really be fixed in Win32k),
* we use a second flag to ignore this possible next mouse move event.
*/
switch (msg)
{
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_XBUTTONDOWN:
GuiData->HackCORE8394IgnoreNextMove = TRUE;
default:
break;
}
if (!DoDefault)
{
if (wKeyState & MK_LBUTTON)
dwButtonState |= FROM_LEFT_1ST_BUTTON_PRESSED;
if (wKeyState & MK_MBUTTON)
dwButtonState |= FROM_LEFT_2ND_BUTTON_PRESSED;
if (wKeyState & MK_RBUTTON)
dwButtonState |= RIGHTMOST_BUTTON_PRESSED;
if (wKeyState & MK_XBUTTON1)
dwButtonState |= FROM_LEFT_3RD_BUTTON_PRESSED;
if (wKeyState & MK_XBUTTON2)
dwButtonState |= FROM_LEFT_4TH_BUTTON_PRESSED;
if (GetKeyState(VK_RMENU) & KEY_PRESSED)
dwControlKeyState |= RIGHT_ALT_PRESSED;
if (GetKeyState(VK_LMENU) & KEY_PRESSED)
dwControlKeyState |= LEFT_ALT_PRESSED;
if (GetKeyState(VK_RCONTROL) & KEY_PRESSED)
dwControlKeyState |= RIGHT_CTRL_PRESSED;
if (GetKeyState(VK_LCONTROL) & KEY_PRESSED)
dwControlKeyState |= LEFT_CTRL_PRESSED;
if (GetKeyState(VK_SHIFT) & KEY_PRESSED)
dwControlKeyState |= SHIFT_PRESSED;
if (GetKeyState(VK_NUMLOCK) & KEY_TOGGLED)
dwControlKeyState |= NUMLOCK_ON;
if (GetKeyState(VK_SCROLL) & KEY_TOGGLED)
dwControlKeyState |= SCROLLLOCK_ON;
if (GetKeyState(VK_CAPITAL) & KEY_TOGGLED)
dwControlKeyState |= CAPSLOCK_ON;
/* See WM_CHAR MSDN documentation for instance */
if (HIWORD(lParam) & KF_EXTENDED)
dwControlKeyState |= ENHANCED_KEY;
/* Send a mouse event */
er.EventType = MOUSE_EVENT;
er.Event.MouseEvent.dwMousePosition = PointToCoord(GuiData, lParam);
er.Event.MouseEvent.dwButtonState = dwButtonState;
er.Event.MouseEvent.dwControlKeyState = dwControlKeyState;
er.Event.MouseEvent.dwEventFlags = dwEventFlags;
ConioProcessInputEvent(Console, &er);
}
}
else
{
DoDefault = TRUE;
}
LeaveCriticalSection(&Console->Lock);
Quit:
if (!DoDefault)
return 0;
if (msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL)
{
INT nBar;
WORD sbCode;
// WORD wKeyState = GET_KEYSTATE_WPARAM(wParam);
SHORT wDelta = GET_WHEEL_DELTA_WPARAM(wParam);
if (msg == WM_MOUSEWHEEL)
nBar = SB_VERT;
else // if (msg == WM_MOUSEHWHEEL)
nBar = SB_HORZ;
// NOTE: We currently do not support zooming...
// if (wKeyState & MK_CONTROL)
// FIXME: For some reason our win32k does not set the key states
// when sending WM_MOUSEWHEEL or WM_MOUSEHWHEEL ...
// if (wKeyState & MK_SHIFT)
if (GetKeyState(VK_SHIFT) & KEY_PRESSED)
sbCode = (wDelta >= 0 ? SB_PAGEUP : SB_PAGEDOWN);
else
sbCode = (wDelta >= 0 ? SB_LINEUP : SB_LINEDOWN);
OnScroll(GuiData, nBar, sbCode);
}
return DefWindowProcW(GuiData->hWindow, msg, wParam, lParam);
}
static VOID
Copy(PGUI_CONSOLE_DATA GuiData)
{
if (OpenClipboard(GuiData->hWindow))
{
PCONSOLE_SCREEN_BUFFER Buffer = GuiData->ActiveBuffer;
if (GetType(Buffer) == TEXTMODE_BUFFER)
{
GuiCopyFromTextModeBuffer((PTEXTMODE_SCREEN_BUFFER)Buffer, GuiData);
}
else /* if (GetType(Buffer) == GRAPHICS_BUFFER) */
{
GuiCopyFromGraphicsBuffer((PGRAPHICS_SCREEN_BUFFER)Buffer, GuiData);
}
CloseClipboard();
}
/* Clear the selection */
UpdateSelection(GuiData, NULL, NULL);
}
static VOID
Paste(PGUI_CONSOLE_DATA GuiData)
{
if (OpenClipboard(GuiData->hWindow))
{
PCONSOLE_SCREEN_BUFFER Buffer = GuiData->ActiveBuffer;
if (GetType(Buffer) == TEXTMODE_BUFFER)
{
GuiPasteToTextModeBuffer((PTEXTMODE_SCREEN_BUFFER)Buffer, GuiData);
}
else /* if (GetType(Buffer) == GRAPHICS_BUFFER) */
{
GuiPasteToGraphicsBuffer((PGRAPHICS_SCREEN_BUFFER)Buffer, GuiData);
}
CloseClipboard();
}
}
static VOID
OnGetMinMaxInfo(PGUI_CONSOLE_DATA GuiData, PMINMAXINFO minMaxInfo)
{
PCONSRV_CONSOLE Console = GuiData->Console;
PCONSOLE_SCREEN_BUFFER ActiveBuffer;
DWORD windx, windy;
UINT WidthUnit, HeightUnit;
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
ActiveBuffer = GuiData->ActiveBuffer;
GetScreenBufferSizeUnits(ActiveBuffer, GuiData, &WidthUnit, &HeightUnit);
windx = CONGUI_MIN_WIDTH * WidthUnit + 2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE));
windy = CONGUI_MIN_HEIGHT * HeightUnit + 2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION);
minMaxInfo->ptMinTrackSize.x = windx;
minMaxInfo->ptMinTrackSize.y = windy;
windx = (ActiveBuffer->ScreenBufferSize.X) * WidthUnit + 2 * (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXEDGE));
windy = (ActiveBuffer->ScreenBufferSize.Y) * HeightUnit + 2 * (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE)) + GetSystemMetrics(SM_CYCAPTION);
if (ActiveBuffer->ViewSize.X < ActiveBuffer->ScreenBufferSize.X) windy += GetSystemMetrics(SM_CYHSCROLL); // Window currently has a horizontal scrollbar
if (ActiveBuffer->ViewSize.Y < ActiveBuffer->ScreenBufferSize.Y) windx += GetSystemMetrics(SM_CXVSCROLL); // Window currently has a vertical scrollbar
minMaxInfo->ptMaxTrackSize.x = windx;
minMaxInfo->ptMaxTrackSize.y = windy;
LeaveCriticalSection(&Console->Lock);
}
static VOID
OnSize(PGUI_CONSOLE_DATA GuiData, WPARAM wParam, LPARAM lParam)
{
PCONSRV_CONSOLE Console = GuiData->Console;
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible || IsIconic(GuiData->hWindow)) return;
if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE)) return;
if (!GuiData->WindowSizeLock &&
(wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED || wParam == SIZE_MINIMIZED))
{
PCONSOLE_SCREEN_BUFFER Buff = GuiData->ActiveBuffer;
DWORD windx, windy, charx, chary;
UINT WidthUnit, HeightUnit;
GetScreenBufferSizeUnits(Buff, GuiData, &WidthUnit, &HeightUnit);
GuiData->WindowSizeLock = TRUE;
windx = LOWORD(lParam);
windy = HIWORD(lParam);
/* Compensate for existing scroll bars (because lParam values do not accommodate scroll bar) */
if (Buff->ViewSize.X < Buff->ScreenBufferSize.X) windy += GetSystemMetrics(SM_CYHSCROLL); // Window currently has a horizontal scrollbar
if (Buff->ViewSize.Y < Buff->ScreenBufferSize.Y) windx += GetSystemMetrics(SM_CXVSCROLL); // Window currently has a vertical scrollbar
charx = windx / (int)WidthUnit ;
chary = windy / (int)HeightUnit;
/* Character alignment (round size up or down) */
if ((windx % WidthUnit ) >= (WidthUnit / 2)) ++charx;
if ((windy % HeightUnit) >= (HeightUnit / 2)) ++chary;
/* Compensate for added scroll bars in window */
if (Buff->ViewSize.X < Buff->ScreenBufferSize.X) windy -= GetSystemMetrics(SM_CYHSCROLL); // Window will have a horizontal scroll bar
if (Buff->ViewSize.Y < Buff->ScreenBufferSize.Y) windx -= GetSystemMetrics(SM_CXVSCROLL); // Window will have a vertical scroll bar
charx = windx / (int)WidthUnit ;
chary = windy / (int)HeightUnit;
/* Character alignment (round size up or down) */
if ((windx % WidthUnit ) >= (WidthUnit / 2)) ++charx;
if ((windy % HeightUnit) >= (HeightUnit / 2)) ++chary;
/* Resize window */
if ((charx != Buff->ViewSize.X) || (chary != Buff->ViewSize.Y))
{
Buff->ViewSize.X = (charx <= (DWORD)Buff->ScreenBufferSize.X) ? charx : Buff->ScreenBufferSize.X;
Buff->ViewSize.Y = (chary <= (DWORD)Buff->ScreenBufferSize.Y) ? chary : Buff->ScreenBufferSize.Y;
}
ResizeConWnd(GuiData, WidthUnit, HeightUnit);
/* Adjust the start of the visible area if we are attempting to show nonexistent areas */
if ((Buff->ScreenBufferSize.X - Buff->ViewOrigin.X) < Buff->ViewSize.X) Buff->ViewOrigin.X = Buff->ScreenBufferSize.X - Buff->ViewSize.X;
if ((Buff->ScreenBufferSize.Y - Buff->ViewOrigin.Y) < Buff->ViewSize.Y) Buff->ViewOrigin.Y = Buff->ScreenBufferSize.Y - Buff->ViewSize.Y;
InvalidateRect(GuiData->hWindow, NULL, TRUE);
GuiData->WindowSizeLock = FALSE;
}
LeaveCriticalSection(&Console->Lock);
}
static VOID
OnMove(PGUI_CONSOLE_DATA GuiData)
{
RECT rcWnd;
// TODO: Simplify the code.
// See: GuiConsoleNotifyWndProc() PM_CREATE_CONSOLE.
/* Retrieve our real position */
GetWindowRect(GuiData->hWindow, &rcWnd);
GuiData->GuiInfo.WindowOrigin.x = rcWnd.left;
GuiData->GuiInfo.WindowOrigin.y = rcWnd.top;
}
static VOID
OnDropFiles(PCONSRV_CONSOLE Console, HDROP hDrop)
{
LPWSTR pszPath;
WCHAR szPath[MAX_PATH + 2];
szPath[0] = L'"';
DragQueryFileW(hDrop, 0, &szPath[1], ARRAYSIZE(szPath) - 1);
DragFinish(hDrop);
if (wcschr(&szPath[1], L' ') != NULL)
{
StringCchCatW(szPath, ARRAYSIZE(szPath), L"\"");
pszPath = szPath;
}
else
{
pszPath = &szPath[1];
}
PasteText(Console, pszPath, wcslen(pszPath));
}
/*
// HACK: This functionality is standard for general scrollbars. Don't add it by hand.
VOID
GuiConsoleHandleScrollbarMenu(VOID)
{
HMENU hMenu;
hMenu = CreatePopupMenu();
if (hMenu == NULL)
{
DPRINT("CreatePopupMenu failed\n");
return;
}
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLHERE);
//InsertItem(hMenu, MFT_SEPARATOR, MIIM_FTYPE, 0, NULL, -1);
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLTOP);
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLBOTTOM);
//InsertItem(hMenu, MFT_SEPARATOR, MIIM_FTYPE, 0, NULL, -1);
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLPAGE_UP);
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLPAGE_DOWN);
//InsertItem(hMenu, MFT_SEPARATOR, MIIM_FTYPE, 0, NULL, -1);
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLUP);
//InsertItem(hMenu, MIIM_STRING, MIIM_ID | MIIM_FTYPE | MIIM_STRING, 0, NULL, IDS_SCROLLDOWN);
}
*/
HBITMAP
CreateFrameBufferBitmap(HDC hDC, int width, int height)
{
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = GetDeviceCaps(hDC, BITSPIXEL);
bmi.bmiHeader.biCompression = BI_RGB;
return CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, NULL, NULL, 0);
}
static LRESULT CALLBACK
ConWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
LRESULT Result = 0;
PGUI_CONSOLE_DATA GuiData = NULL;
PCONSRV_CONSOLE Console = NULL;
/*
* - If it's the first time we create a window for the terminal,
* just initialize it and return.
*
* - If we are destroying the window, just do it and return.
*/
if (msg == WM_NCCREATE)
{
return (LRESULT)OnNcCreate(hWnd, (LPCREATESTRUCTW)lParam);
}
else if (msg == WM_NCDESTROY)
{
return OnNcDestroy(hWnd);
}
/*
* Now the terminal window is initialized.
* Get the terminal data via the window's data.
* If there is no data, just go away.
*/
GuiData = GuiGetGuiData(hWnd);
if (GuiData == NULL) return DefWindowProcW(hWnd, msg, wParam, lParam);
// TEMPORARY HACK until all of the functions can deal with a NULL GuiData->ActiveBuffer ...
if (GuiData->ActiveBuffer == NULL) return DefWindowProcW(hWnd, msg, wParam, lParam);
/*
* Just retrieve a pointer to the console in case somebody needs it.
* It is not NULL because it was checked in GuiGetGuiData.
* Each helper function which needs the console has to validate and lock it.
*/
Console = GuiData->Console;
/* We have a console, start message dispatching */
switch (msg)
{
case WM_ACTIVATE:
OnActivate(GuiData, wParam);
break;
case WM_CLOSE:
if (OnClose(GuiData)) goto Default;
break;
case WM_ERASEBKGND:
return TRUE;
case WM_PAINT:
OnPaint(GuiData);
break;
case WM_TIMER:
OnTimer(GuiData);
break;
case WM_PALETTECHANGED:
{
DPRINT("WM_PALETTECHANGED called\n");
/*
* Protects against infinite loops:
* "... A window that receives this message must not realize
* its palette, unless it determines that wParam does not contain
* its own window handle." (WM_PALETTECHANGED description - MSDN)
*
* This message is sent to all windows, including the one that
* changed the system palette and caused this message to be sent.
* The wParam of this message contains the handle of the window
* that caused the system palette to change. To avoid an infinite
* loop, care must be taken to check that the wParam of this message
* does not match the window's handle.
*/
if ((HWND)wParam == hWnd) break;
DPRINT("WM_PALETTECHANGED ok\n");
OnPaletteChanged(GuiData);
DPRINT("WM_PALETTECHANGED quit\n");
break;
}
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
case WM_DEADCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
{
/* Detect Alt-Enter presses and switch back and forth to fullscreen mode */
if (msg == WM_SYSKEYDOWN && (HIWORD(lParam) & KF_ALTDOWN) && wParam == VK_RETURN)
{
/* Switch only at first Alt-Enter press, and ignore subsequent key repetitions */
if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT)
GuiConsoleSwitchFullScreen(GuiData);
break;
}
/* Detect Alt-Esc/Space/Tab presses defer to DefWindowProc */
if ( (HIWORD(lParam) & KF_ALTDOWN) && (wParam == VK_ESCAPE || wParam == VK_SPACE || wParam == VK_TAB))
{
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
/* Detect Alt+Shift */
if (wParam == VK_SHIFT && (msg == WM_SYSKEYDOWN || msg == WM_SYSKEYUP))
{
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
OnKey(GuiData, msg, wParam, lParam);
break;
}
case WM_SETCURSOR:
{
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible) goto Default;
/*
* The message was sent because we are manually triggering a change.
* Check whether the mouse is indeed present on this console window
* and take appropriate decisions.
*/
if (wParam == -1 && lParam == -1)
{
POINT mouseCoords;
HWND hWndHit;
/* Get the placement of the mouse */
GetCursorPos(&mouseCoords);
/* On which window is placed the mouse ? */
hWndHit = WindowFromPoint(mouseCoords);
/* It's our window. Perform the hit-test to be used later on. */
if (hWndHit == hWnd)
{
wParam = (WPARAM)hWnd;
lParam = DefWindowProcW(hWndHit, WM_NCHITTEST, 0,
MAKELPARAM(mouseCoords.x, mouseCoords.y));
}
}
/* Set the mouse cursor only when we are in the client area */
if ((HWND)wParam == hWnd && LOWORD(lParam) == HTCLIENT)
{
if (GuiData->MouseCursorRefCount >= 0)
{
/* Show the cursor */
SetCursor(GuiData->hCursor);
}
else
{
/* Hide the cursor if the reference count is negative */
SetCursor(NULL);
}
return TRUE;
}
else
{
goto Default;
}
}
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_XBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_XBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_XBUTTONDBLCLK:
case WM_MOUSEMOVE:
case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
{
Result = OnMouse(GuiData, msg, wParam, lParam);
break;
}
case WM_HSCROLL:
OnScroll(GuiData, SB_HORZ, LOWORD(wParam));
break;
case WM_VSCROLL:
OnScroll(GuiData, SB_VERT, LOWORD(wParam));
break;
case WM_CONTEXTMENU:
{
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible) break;
if (DefWindowProcW(hWnd /*GuiData->hWindow*/, WM_NCHITTEST, 0, lParam) == HTCLIENT)
{
HMENU hMenu = CreatePopupMenu();
if (hMenu != NULL)
{
AppendMenuItems(hMenu, GuiConsoleEditMenuItems);
TrackPopupMenuEx(hMenu,
TPM_RIGHTBUTTON,
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
hWnd,
NULL);
DestroyMenu(hMenu);
}
break;
}
else
{
goto Default;
}
}
case WM_INITMENU:
{
HMENU hMenu = (HMENU)wParam;
if (hMenu != NULL)
{
/* Enable or disable the Close menu item */
EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND |
(GuiData->IsCloseButtonEnabled ? MF_ENABLED : MF_GRAYED));
/* Enable or disable the Copy and Paste items */
EnableMenuItem(hMenu, ID_SYSTEM_EDIT_COPY , MF_BYCOMMAND |
((GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) &&
(GuiData->Selection.dwFlags & CONSOLE_SELECTION_NOT_EMPTY) ? MF_ENABLED : MF_GRAYED));
// FIXME: Following whether the active screen buffer is text-mode
// or graphics-mode, search for CF_UNICODETEXT or CF_BITMAP formats.
EnableMenuItem(hMenu, ID_SYSTEM_EDIT_PASTE, MF_BYCOMMAND |
(!(GuiData->Selection.dwFlags & CONSOLE_SELECTION_IN_PROGRESS) &&
IsClipboardFormatAvailable(CF_UNICODETEXT) ? MF_ENABLED : MF_GRAYED));
}
SendMenuEvent(Console, WM_INITMENU);
break;
}
case WM_MENUSELECT:
{
if (HIWORD(wParam) == 0xFFFF) // Allow all the menu flags
{
SendMenuEvent(Console, WM_MENUSELECT);
}
break;
}
case WM_COMMAND:
case WM_SYSCOMMAND:
{
Result = OnCommand(GuiData, wParam, lParam);
break;
}
case WM_DROPFILES:
OnDropFiles(Console, (HDROP)wParam);
break;
case WM_SETFOCUS:
case WM_KILLFOCUS:
OnFocus(GuiData, (msg == WM_SETFOCUS));
break;
case WM_GETMINMAXINFO:
OnGetMinMaxInfo(GuiData, (PMINMAXINFO)lParam);
break;
case WM_MOVE:
OnMove(GuiData);
break;
#if 0 // This code is here to prepare & control dynamic console SB resizing.
case WM_SIZING:
{
PRECT dragRect = (PRECT)lParam;
switch (wParam)
{
case WMSZ_LEFT:
DPRINT1("WMSZ_LEFT\n");
break;
case WMSZ_RIGHT:
DPRINT1("WMSZ_RIGHT\n");
break;
case WMSZ_TOP:
DPRINT1("WMSZ_TOP\n");
break;
case WMSZ_TOPLEFT:
DPRINT1("WMSZ_TOPLEFT\n");
break;
case WMSZ_TOPRIGHT:
DPRINT1("WMSZ_TOPRIGHT\n");
break;
case WMSZ_BOTTOM:
DPRINT1("WMSZ_BOTTOM\n");
break;
case WMSZ_BOTTOMLEFT:
DPRINT1("WMSZ_BOTTOMLEFT\n");
break;
case WMSZ_BOTTOMRIGHT:
DPRINT1("WMSZ_BOTTOMRIGHT\n");
break;
default:
DPRINT1("wParam = %d\n", wParam);
break;
}
DPRINT1("dragRect = {.left = %d ; .top = %d ; .right = %d ; .bottom = %d}\n",
dragRect->left, dragRect->top, dragRect->right, dragRect->bottom);
break;
}
#endif
case WM_SIZE:
OnSize(GuiData, wParam, lParam);
break;
case PM_RESIZE_TERMINAL:
{
PCONSOLE_SCREEN_BUFFER Buff = GuiData->ActiveBuffer;
HDC hDC;
HBITMAP hnew, hold;
DWORD Width, Height;
UINT WidthUnit, HeightUnit;
/* Do nothing if the window is hidden */
if (!GuiData->IsWindowVisible) break;
GetScreenBufferSizeUnits(Buff, GuiData, &WidthUnit, &HeightUnit);
Width = Buff->ScreenBufferSize.X * WidthUnit ;
Height = Buff->ScreenBufferSize.Y * HeightUnit;
/* Recreate the framebuffer */
hDC = GetDC(GuiData->hWindow);
hnew = CreateFrameBufferBitmap(hDC, Width, Height);
ReleaseDC(GuiData->hWindow, hDC);
hold = SelectObject(GuiData->hMemDC, hnew);
if (GuiData->hBitmap)
{
if (hold == GuiData->hBitmap) DeleteObject(GuiData->hBitmap);
}
GuiData->hBitmap = hnew;
/* Resize the window to the user's values */
GuiData->WindowSizeLock = TRUE;
ResizeConWnd(GuiData, WidthUnit, HeightUnit);
GuiData->WindowSizeLock = FALSE;
break;
}
/*
* Undocumented message sent by Windows' console.dll for applying console info.
* See http://www.catch22.net/sites/default/source/files/setconsoleinfo.c
* and http://www.scn.rain.com/~neighorn/PDF/MSBugPaper.pdf
* for more information.
*/
case WM_SETCONSOLEINFO:
{
GuiApplyUserSettings(GuiData, (HANDLE)wParam);
break;
}
case PM_CONSOLE_BEEP:
DPRINT1("Beep\n");
Beep(800, 200);
break;
case PM_CONSOLE_SET_TITLE:
SetWindowTextW(GuiData->hWindow, GuiData->Console->Title.Buffer);
break;
default: Default:
Result = DefWindowProcW(hWnd, msg, wParam, lParam);
break;
}
return Result;
}
/* EOF */