terminal/src/propsheet/TerminalPropsheetPage.cpp
Dustin Howett 3909cc103a Merged PR 5770253: [Git2Git] Merged PR 5760120: Add propsheet chooser to Windows
[Git2Git] Merged PR 5760120: Add propsheet chooser to Windows

Now the inbox console propsheet can choose which terminal is default

Related work items: MSFT-32007202 #492

Retrieved from https://microsoft.visualstudio.com os.2020 OS official/rs_wdx_dxp_windev d80f506858bd990c267de6cefae7ff55707b3a57
2021-03-10 01:19:04 +00:00

504 lines
18 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "TerminalPropsheetPage.h"
#include "OptionsPage.h" // For InitializeCursorSize
#include "ColorControl.h"
#include <functional>
#include "../propslib/DelegationConfig.hpp"
#include "../types/inc/User32Utils.hpp"
// From conattrs.h
const COLORREF INVALID_COLOR = 0xffffffff;
const int COLOR_MAX = 255;
void _UseForeground(const HWND hDlg, const bool useFg) noexcept
{
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_FG_REDSCROLL), useFg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_FG_GREENSCROLL), useFg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_FG_BLUESCROLL), useFg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_FG_RED), useFg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_FG_GREEN), useFg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_FG_BLUE), useFg);
if (useFg)
{
const auto r = GetDlgItemInt(hDlg, IDD_TERMINAL_FG_RED, nullptr, FALSE);
const auto g = GetDlgItemInt(hDlg, IDD_TERMINAL_FG_GREEN, nullptr, FALSE);
const auto b = GetDlgItemInt(hDlg, IDD_TERMINAL_FG_BLUE, nullptr, FALSE);
gpStateInfo->DefaultForeground = RGB(r, g, b);
}
else
{
gpStateInfo->DefaultForeground = INVALID_COLOR;
}
}
void _UseBackground(const HWND hDlg, const bool useBg) noexcept
{
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_BG_REDSCROLL), useBg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_BG_GREENSCROLL), useBg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_BG_BLUESCROLL), useBg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_BG_RED), useBg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_BG_GREEN), useBg);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_BG_BLUE), useBg);
if (useBg)
{
auto r = GetDlgItemInt(hDlg, IDD_TERMINAL_BG_RED, nullptr, FALSE);
auto g = GetDlgItemInt(hDlg, IDD_TERMINAL_BG_GREEN, nullptr, FALSE);
auto b = GetDlgItemInt(hDlg, IDD_TERMINAL_BG_BLUE, nullptr, FALSE);
gpStateInfo->DefaultBackground = RGB(r, g, b);
}
else
{
gpStateInfo->DefaultBackground = INVALID_COLOR;
}
}
void _UseCursorColor(const HWND hDlg, const bool useColor) noexcept
{
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_REDSCROLL), useColor);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_GREENSCROLL), useColor);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_BLUESCROLL), useColor);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_RED), useColor);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_GREEN), useColor);
EnableWindow(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_BLUE), useColor);
if (useColor)
{
const auto r = GetDlgItemInt(hDlg, IDD_TERMINAL_CURSOR_RED, nullptr, FALSE);
const auto g = GetDlgItemInt(hDlg, IDD_TERMINAL_CURSOR_GREEN, nullptr, FALSE);
const auto b = GetDlgItemInt(hDlg, IDD_TERMINAL_CURSOR_BLUE, nullptr, FALSE);
gpStateInfo->CursorColor = RGB(r, g, b);
}
else
{
gpStateInfo->CursorColor = INVALID_COLOR;
}
}
void _UpdateTextAndScroll(const HWND hDlg,
const SHORT value,
const WORD textItem,
const WORD scrollItem) noexcept
{
UpdateItem(hDlg, textItem, value);
SendDlgItemMessage(hDlg, scrollItem, UDM_SETPOS, 0, MAKELONG(value, 0));
}
void _PrepDefAppCombo(const HWND hDlg,
const int dlgItem,
const std::vector<DelegationConfig::DelegationPackage>& list,
const DelegationConfig::DelegationPackage& selected)
{
const HWND hCombo = GetDlgItem(hDlg, dlgItem);
ComboBox_ResetContent(hCombo);
DWORD selectedIndex = 0;
for (DWORD i = 0; i < gsl::narrow<DWORD>(list.size()); ++i)
{
auto& item = list[i];
// An empty CLSID is a sentinel for the inbox console.
if (item.terminal.clsid == CLSID{ 0 })
{
const auto name = GetStringResource(IDS_TERMINAL_DEF_INBOX);
ComboBox_AddString(hCombo, name.c_str());
}
else
{
ComboBox_AddString(hCombo, item.terminal.name.c_str());
}
ComboBox_SetItemData(hCombo, i, &item);
if (selected == item)
{
selectedIndex = i;
}
}
ComboBox_SetCurSel(hCombo, selectedIndex);
ComboBox_Enable(hCombo, TRUE);
}
bool InitTerminalDialog(const HWND hDlg) noexcept
{
// Initialize the global handle to this dialog
g_hTerminalDlg = hDlg;
// Group radios
CheckRadioButton(hDlg, IDD_TERMINAL_INVERSE_CURSOR, IDD_TERMINAL_CURSOR_USECOLOR, IDD_TERMINAL_INVERSE_CURSOR);
// initialize size of edit controls
SendDlgItemMessage(hDlg, IDD_TERMINAL_FG_RED, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_FG_GREEN, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_FG_BLUE, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_BG_RED, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_BG_GREEN, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_BG_BLUE, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_CURSOR_RED, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_CURSOR_GREEN, EM_LIMITTEXT, 3, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_CURSOR_BLUE, EM_LIMITTEXT, 3, 0);
// Cap the color inputs to 255
const auto colorRange = MAKELONG(COLOR_MAX, 0);
SendDlgItemMessage(hDlg, IDD_TERMINAL_FG_REDSCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_FG_GREENSCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_FG_BLUESCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_BG_REDSCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_BG_GREENSCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_BG_BLUESCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_CURSOR_REDSCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_CURSOR_GREENSCROLL, UDM_SETRANGE, 0, colorRange);
SendDlgItemMessage(hDlg, IDD_TERMINAL_CURSOR_BLUESCROLL, UDM_SETRANGE, 0, colorRange);
const bool initialTerminalFG = gpStateInfo->DefaultForeground != INVALID_COLOR;
const bool initialTerminalBG = gpStateInfo->DefaultBackground != INVALID_COLOR;
const bool initialCursorLegacy = gpStateInfo->CursorColor == INVALID_COLOR;
if (initialTerminalFG)
{
g_fakeForegroundColor = gpStateInfo->DefaultForeground;
}
if (initialTerminalBG)
{
g_fakeBackgroundColor = gpStateInfo->DefaultBackground;
}
if (!initialCursorLegacy)
{
g_fakeCursorColor = gpStateInfo->CursorColor;
}
CheckDlgButton(hDlg, IDD_USE_TERMINAL_FG, initialTerminalFG);
CheckDlgButton(hDlg, IDD_USE_TERMINAL_BG, initialTerminalBG);
CheckRadioButton(hDlg,
IDD_TERMINAL_INVERSE_CURSOR,
IDD_TERMINAL_CURSOR_USECOLOR,
initialCursorLegacy ? IDD_TERMINAL_INVERSE_CURSOR : IDD_TERMINAL_CURSOR_USECOLOR);
// Set the initial values in the edit boxes and scroll controls
const auto fgRed = GetRValue(g_fakeForegroundColor);
const auto fgGreen = GetGValue(g_fakeForegroundColor);
const auto fgBlue = GetBValue(g_fakeForegroundColor);
const auto bgRed = GetRValue(g_fakeBackgroundColor);
const auto bgGreen = GetGValue(g_fakeBackgroundColor);
const auto bgBlue = GetBValue(g_fakeBackgroundColor);
const auto cursorRed = GetRValue(g_fakeCursorColor);
const auto cursorGreen = GetGValue(g_fakeCursorColor);
const auto cursorBlue = GetBValue(g_fakeCursorColor);
_UpdateTextAndScroll(hDlg, fgRed, IDD_TERMINAL_FG_RED, IDD_TERMINAL_FG_REDSCROLL);
_UpdateTextAndScroll(hDlg, fgGreen, IDD_TERMINAL_FG_GREEN, IDD_TERMINAL_FG_GREENSCROLL);
_UpdateTextAndScroll(hDlg, fgBlue, IDD_TERMINAL_FG_BLUE, IDD_TERMINAL_FG_BLUESCROLL);
_UpdateTextAndScroll(hDlg, bgRed, IDD_TERMINAL_BG_RED, IDD_TERMINAL_BG_REDSCROLL);
_UpdateTextAndScroll(hDlg, bgGreen, IDD_TERMINAL_BG_GREEN, IDD_TERMINAL_BG_GREENSCROLL);
_UpdateTextAndScroll(hDlg, bgBlue, IDD_TERMINAL_BG_BLUE, IDD_TERMINAL_BG_BLUESCROLL);
_UpdateTextAndScroll(hDlg, cursorRed, IDD_TERMINAL_CURSOR_RED, IDD_TERMINAL_CURSOR_REDSCROLL);
_UpdateTextAndScroll(hDlg, cursorGreen, IDD_TERMINAL_CURSOR_GREEN, IDD_TERMINAL_CURSOR_GREENSCROLL);
_UpdateTextAndScroll(hDlg, cursorBlue, IDD_TERMINAL_CURSOR_BLUE, IDD_TERMINAL_CURSOR_BLUESCROLL);
_UseForeground(hDlg, initialTerminalFG);
_UseBackground(hDlg, initialTerminalBG);
_UseCursorColor(hDlg, !initialCursorLegacy);
InvalidateRect(GetDlgItem(hDlg, IDD_TERMINAL_FGCOLOR), nullptr, FALSE);
InvalidateRect(GetDlgItem(hDlg, IDD_TERMINAL_BGCOLOR), nullptr, FALSE);
InvalidateRect(GetDlgItem(hDlg, IDD_TERMINAL_CURSOR_COLOR), nullptr, FALSE);
CheckRadioButton(hDlg,
IDD_TERMINAL_LEGACY_CURSOR,
IDD_TERMINAL_SOLIDBOX,
IDD_TERMINAL_LEGACY_CURSOR + gpStateInfo->CursorType);
CheckDlgButton(hDlg, IDD_DISABLE_SCROLLFORWARD, gpStateInfo->TerminalScrolling);
_PrepDefAppCombo(hDlg,
IDD_TERMINAL_COMBO_DEFTERM,
g_availablePackages,
g_selectedPackage);
return true;
}
void _ChangeColorControl(const HWND hDlg,
const WORD item,
const WORD redControl,
const WORD greenControl,
const WORD blueControl,
const WORD colorControl,
DWORD& setting) noexcept
{
BOOL bOK = FALSE;
int newValue = GetDlgItemInt(hDlg, item, &bOK, TRUE);
int r = GetRValue(setting);
int g = GetGValue(setting);
int b = GetBValue(setting);
if (bOK)
{
if (newValue > COLOR_MAX)
{
UpdateItem(hDlg, item, COLOR_MAX);
newValue = COLOR_MAX;
}
if (item == redControl)
{
r = newValue;
}
else if (item == greenControl)
{
g = newValue;
}
else if (item == blueControl)
{
b = newValue;
}
setting = RGB(r, g, b);
}
InvalidateRect(GetDlgItem(hDlg, colorControl), nullptr, FALSE);
}
void _ChangeForegroundRGB(const HWND hDlg, const WORD item) noexcept
{
_ChangeColorControl(hDlg,
item,
IDD_TERMINAL_FG_RED,
IDD_TERMINAL_FG_GREEN,
IDD_TERMINAL_FG_BLUE,
IDD_TERMINAL_FGCOLOR,
gpStateInfo->DefaultForeground);
g_fakeForegroundColor = gpStateInfo->DefaultForeground;
}
void _ChangeBackgroundRGB(const HWND hDlg, const WORD item) noexcept
{
_ChangeColorControl(hDlg,
item,
IDD_TERMINAL_BG_RED,
IDD_TERMINAL_BG_GREEN,
IDD_TERMINAL_BG_BLUE,
IDD_TERMINAL_BGCOLOR,
gpStateInfo->DefaultBackground);
g_fakeBackgroundColor = gpStateInfo->DefaultBackground;
}
void _ChangeCursorRGB(const HWND hDlg, const WORD item) noexcept
{
_ChangeColorControl(hDlg,
item,
IDD_TERMINAL_CURSOR_RED,
IDD_TERMINAL_CURSOR_GREEN,
IDD_TERMINAL_CURSOR_BLUE,
IDD_TERMINAL_CURSOR_COLOR,
gpStateInfo->CursorColor);
g_fakeCursorColor = gpStateInfo->CursorColor;
}
bool _CommandColorInput(const HWND hDlg,
const WORD item,
const WORD command,
const std::function<void(HWND, WORD)> changeFunction) noexcept
{
bool handled = false;
switch (command)
{
case EN_UPDATE:
case EN_CHANGE:
changeFunction(hDlg, item);
handled = true;
UpdateApplyButton(hDlg);
break;
}
return handled;
}
bool TerminalDlgCommand(const HWND hDlg, const WORD item, const WORD command) noexcept
{
bool handled = false;
switch (item)
{
case IDD_TERMINAL_CURSOR_USECOLOR:
case IDD_TERMINAL_INVERSE_CURSOR:
_UseCursorColor(hDlg, IsDlgButtonChecked(hDlg, IDD_TERMINAL_CURSOR_USECOLOR));
handled = true;
UpdateApplyButton(hDlg);
break;
case IDD_USE_TERMINAL_FG:
_UseForeground(hDlg, IsDlgButtonChecked(hDlg, IDD_USE_TERMINAL_FG));
handled = true;
UpdateApplyButton(hDlg);
break;
case IDD_USE_TERMINAL_BG:
_UseBackground(hDlg, IsDlgButtonChecked(hDlg, IDD_USE_TERMINAL_BG));
handled = true;
UpdateApplyButton(hDlg);
break;
case IDD_TERMINAL_FG_RED:
case IDD_TERMINAL_FG_GREEN:
case IDD_TERMINAL_FG_BLUE:
handled = _CommandColorInput(hDlg, item, command, _ChangeForegroundRGB);
break;
case IDD_TERMINAL_BG_RED:
case IDD_TERMINAL_BG_GREEN:
case IDD_TERMINAL_BG_BLUE:
handled = _CommandColorInput(hDlg, item, command, _ChangeBackgroundRGB);
break;
case IDD_TERMINAL_CURSOR_RED:
case IDD_TERMINAL_CURSOR_GREEN:
case IDD_TERMINAL_CURSOR_BLUE:
handled = _CommandColorInput(hDlg, item, command, _ChangeCursorRGB);
break;
case IDD_TERMINAL_LEGACY_CURSOR:
case IDD_TERMINAL_VERTBAR:
case IDD_TERMINAL_UNDERSCORE:
case IDD_TERMINAL_EMPTYBOX:
case IDD_TERMINAL_SOLIDBOX:
{
gpStateInfo->CursorType = item - IDD_TERMINAL_LEGACY_CURSOR;
UpdateApplyButton(hDlg);
// See GH#1219 - When the cursor state is something other than legacy,
// we need to manually check the "IDD_CURSOR_ADVANCED" radio button on
// the Options page. This will prevent the Options page from manually
// resetting the cursor to legacy.
if (g_hOptionsDlg != INVALID_HANDLE_VALUE)
{
InitializeCursorSize(g_hOptionsDlg);
}
handled = true;
break;
}
case IDD_DISABLE_SCROLLFORWARD:
{
gpStateInfo->TerminalScrolling = IsDlgButtonChecked(hDlg, IDD_DISABLE_SCROLLFORWARD);
UpdateApplyButton(hDlg);
handled = true;
break;
}
case IDD_TERMINAL_COMBO_DEFTERM:
{
if (CBN_SELCHANGE == command)
{
const HWND hCombo = GetDlgItem(hDlg, IDD_TERMINAL_COMBO_DEFTERM);
const DWORD comboItem = ComboBox_GetCurSel(hCombo);
if (CB_ERR != comboItem)
{
const auto pPackage = reinterpret_cast<const DelegationConfig::DelegationPackage* const>(ComboBox_GetItemData(hCombo, comboItem));
g_selectedPackage = *pPackage;
}
}
break;
}
}
return handled;
}
INT_PTR WINAPI TerminalDlgProc(const HWND hDlg, const UINT wMsg, const WPARAM wParam, const LPARAM lParam)
{
static bool fHaveInitialized = false;
switch (wMsg)
{
case WM_INITDIALOG:
fHaveInitialized = true;
return InitTerminalDialog(hDlg);
case WM_COMMAND:
if (!fHaveInitialized)
{
return false;
}
return TerminalDlgCommand(hDlg, LOWORD(wParam), HIWORD(wParam));
case WM_NOTIFY:
{
if (lParam && (wParam == IDD_HELP_TERMINAL))
{
// handle hyperlink click or keyboard activation
switch (((LPNMHDR)lParam)->code)
{
case NM_CLICK:
case NM_RETURN:
{
PNMLINK pnmLink = (PNMLINK)lParam;
if (0 == pnmLink->item.iLink)
{
ShellExecute(nullptr,
L"open",
pnmLink->item.szUrl,
nullptr,
nullptr,
SW_SHOW);
}
break;
}
}
}
else
{
const PSHNOTIFY* const pshn = (LPPSHNOTIFY)lParam;
if (lParam)
{
switch (pshn->hdr.code)
{
case PSN_APPLY:
EndDlgPage(hDlg, !pshn->lParam);
return TRUE;
case PSN_KILLACTIVE:
{
// Fake the dialog proc into thinking the edit control just
// lost focus so it'll update properly
int item = GetDlgCtrlID(GetFocus());
if (item)
{
SendMessage(hDlg, WM_COMMAND, MAKELONG(item, EN_KILLFOCUS), 0);
}
return TRUE;
}
}
}
}
}
case WM_VSCROLL:
// Fake the dialog proc into thinking the edit control just
// lost focus so it'll update properly
SendMessage(hDlg, WM_COMMAND, MAKELONG((GetDlgCtrlID((HWND)lParam) - 1), EN_KILLFOCUS), 0);
return TRUE;
case WM_DESTROY:
// MSFT:20740368
// When the propsheet is opened straight from explorer, NOT from
// conhost itself, then explorer will load console.dll once,
// and re-use it for subsequent launches. This means that on
// the first launch of the propsheet, our fHaveInitialized will
// be false until we actually do the init work, but on
// subsequent launches, fHaveInitialized will be re-used, and
// found to be true, and we'll zero out the values of the
// colors. This is because the message loop decides to update
// the values of the textboxes before we get a chance to put
// the current values into them. When the textboxes update,
// they'll overwrite the current color components with whatever
// they currently have, which is 0.
// To avoid this madness, make sure to reset our initialization
// state when the dialog is closed.
fHaveInitialized = false;
break;
}
return false;
}