mirror of
https://github.com/reactos/reactos.git
synced 2024-09-06 02:37:06 +00:00
![Hermès Bélusca-Maïto](/assets/img/avatar_default.png)
CORE-12451, CORE-17601, CORE-17803 Replaces PR #4281. When changing the console output code page, check whether the current font can support it. If not, try to find a suitable font for the new code page. If none can be found: - if we are creating a new console, forcefully switch to codepage 437 (OEM USA) and retry finding a font, falling back to "Terminal" if none could be found; - if we were just changing the current CP, just fail and keep the old code page and font. Rework the console font selection/creation functions for this new job (see CreateConsoleFontEx() and friends). Elements of implementation based from https://github.com/microsoft/terminal ; see code for more information. Silence the noisy IsValidConsoleFont2() diagnostic messages. Add Doxygen documentation. [CONSOLE.CPL] Only add "Terminal" to the enumerated list of faces + add a TODO implementation comment.
2636 lines
83 KiB
C
2636 lines
83 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 signal 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->IgnoreNextMouseSignal = 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->IgnoreNextMouseSignal)
|
|
{
|
|
if (msg != WM_LBUTTONDOWN &&
|
|
msg != WM_MBUTTONDOWN &&
|
|
msg != WM_RBUTTONDOWN &&
|
|
msg != WM_XBUTTONDOWN)
|
|
{
|
|
/*
|
|
* If this mouse signal is not a button-down action
|
|
* then this is the last one being ignored.
|
|
*/
|
|
GuiData->IgnoreNextMouseSignal = FALSE;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* This mouse signal 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 signal */
|
|
GuiData->IgnoreNextMouseSignal = 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 signal */
|
|
GuiData->IgnoreNextMouseSignal = 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;
|
|
}
|
|
}
|
|
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 depending on which VM ReactOS runs, the next mouse
|
|
* signal coming after a button-down action can be a mouse-move (e.g.
|
|
* on VBox, whereas on QEMU it is not the case). However it is NOT a
|
|
* rule, so that we cannot use the IgnoreNextMouseSignal flag to just
|
|
* "ignore" the next mouse event, thinking it would always be a mouse-
|
|
* move signal.
|
|
*
|
|
* To work around this problem (that should really be fixed in Win32k),
|
|
* we use a second flag to ignore this possible next mouse move signal.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
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 */
|