// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "precomp.h" #include "TerminalPropsheetPage.h" #include "OptionsPage.h" // For InitializeCursorSize #include "ColorControl.h" #include #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& list, const DelegationConfig::DelegationPackage& selected) { const HWND hCombo = GetDlgItem(hDlg, dlgItem); ComboBox_ResetContent(hCombo); DWORD selectedIndex = 0; for (DWORD i = 0; i < gsl::narrow(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 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(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; }