430 lines
17 KiB
C++
430 lines
17 KiB
C++
#include "pch.h"
|
|
#include "FlatC.h"
|
|
#include "winrt/Microsoft.Terminal.Control.h"
|
|
#include "winrt/Microsoft.Terminal.Core.h"
|
|
#include "../TerminalCore/ControlKeyStates.hpp"
|
|
#include "../types/inc/colorTable.hpp"
|
|
#include "../inc/DefaultSettings.h"
|
|
#include <windowsx.h>
|
|
|
|
#pragma warning(disable : 4100)
|
|
|
|
using namespace winrt::Microsoft::Terminal::Control;
|
|
using namespace winrt::Microsoft::Terminal::Core;
|
|
using CKS = ::Microsoft::Terminal::Core::ControlKeyStates;
|
|
|
|
static CKS getControlKeyState() noexcept
|
|
{
|
|
struct KeyModifier
|
|
{
|
|
int vkey;
|
|
CKS flags;
|
|
};
|
|
|
|
constexpr std::array<KeyModifier, 5> modifiers{ {
|
|
{ VK_RMENU, CKS::RightAltPressed },
|
|
{ VK_LMENU, CKS::LeftAltPressed },
|
|
{ VK_RCONTROL, CKS::RightCtrlPressed },
|
|
{ VK_LCONTROL, CKS::LeftCtrlPressed },
|
|
{ VK_SHIFT, CKS::ShiftPressed },
|
|
} };
|
|
|
|
CKS flags;
|
|
|
|
for (const auto& mod : modifiers)
|
|
{
|
|
const auto state = GetKeyState(mod.vkey);
|
|
const auto isDown = state < 0;
|
|
|
|
if (isDown)
|
|
{
|
|
flags |= mod.flags;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
static LPCWSTR term_window_class = L"HwndTerminalClass";
|
|
|
|
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
|
|
// "If the high-order bit is 1, the key is down; otherwise, it is up."
|
|
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
|
|
|
|
static constexpr bool _IsMouseMessage(UINT uMsg)
|
|
{
|
|
return uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK ||
|
|
uMsg == WM_MBUTTONDOWN || uMsg == WM_MBUTTONUP || uMsg == WM_MBUTTONDBLCLK ||
|
|
uMsg == WM_RBUTTONDOWN || uMsg == WM_RBUTTONUP || uMsg == WM_RBUTTONDBLCLK ||
|
|
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
|
|
}
|
|
|
|
#include "../inc/cppwinrt_utils.h"
|
|
|
|
struct NullConnection : public winrt::implements<NullConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection>
|
|
{
|
|
void Initialize(IInspectable x) {}
|
|
void Start() {}
|
|
void WriteInput(winrt::hstring d)
|
|
{
|
|
if (_pfnWriteCallback)
|
|
{
|
|
_pfnWriteCallback(d.data());
|
|
}
|
|
}
|
|
void Resize(uint32_t r, uint32_t c) {}
|
|
void Close() {}
|
|
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept { return winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::Closed; }
|
|
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
|
|
|
|
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Windows::Foundation::IInspectable);
|
|
|
|
public:
|
|
std::function<void(const wchar_t*)> _pfnWriteCallback{ nullptr };
|
|
void WireOutput(const wchar_t* data)
|
|
{
|
|
_TerminalOutputHandlers(winrt::to_hstring(data));
|
|
}
|
|
};
|
|
struct TerminalSettings : winrt::implements<TerminalSettings, IControlSettings, ICoreSettings, IControlAppearance, ICoreAppearance>
|
|
{
|
|
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
|
|
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
|
|
TerminalSettings()
|
|
{
|
|
const auto campbellSpan = Microsoft::Console::Utils::CampbellColorTable();
|
|
std::transform(campbellSpan.begin(), campbellSpan.end(), _ColorTable.begin(), [](auto&& color) {
|
|
return static_cast<winrt::Microsoft::Terminal::Core::Color>(til::color{ color });
|
|
});
|
|
}
|
|
~TerminalSettings() = default;
|
|
|
|
winrt::Microsoft::Terminal::Core::Color GetColorTableEntry(int32_t index) noexcept { return _ColorTable.at(index); }
|
|
|
|
WINRT_PROPERTY(til::color, DefaultForeground, DEFAULT_FOREGROUND);
|
|
WINRT_PROPERTY(til::color, DefaultBackground, DEFAULT_BACKGROUND);
|
|
WINRT_PROPERTY(til::color, SelectionBackground, DEFAULT_FOREGROUND);
|
|
WINRT_PROPERTY(int32_t, HistorySize, DEFAULT_HISTORY_SIZE);
|
|
WINRT_PROPERTY(int32_t, InitialRows, 30);
|
|
WINRT_PROPERTY(int32_t, InitialCols, 80);
|
|
WINRT_PROPERTY(bool, SnapOnInput, true);
|
|
WINRT_PROPERTY(bool, AltGrAliasing, true);
|
|
WINRT_PROPERTY(til::color, CursorColor, DEFAULT_CURSOR_COLOR);
|
|
WINRT_PROPERTY(winrt::Microsoft::Terminal::Core::CursorStyle, CursorShape, winrt::Microsoft::Terminal::Core::CursorStyle::Vintage);
|
|
WINRT_PROPERTY(uint32_t, CursorHeight, DEFAULT_CURSOR_HEIGHT);
|
|
WINRT_PROPERTY(winrt::hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS);
|
|
WINRT_PROPERTY(bool, CopyOnSelect, false);
|
|
WINRT_PROPERTY(bool, InputServiceWarning, true);
|
|
WINRT_PROPERTY(bool, FocusFollowMouse, false);
|
|
WINRT_PROPERTY(bool, TrimBlockSelection, false);
|
|
WINRT_PROPERTY(bool, DetectURLs, true);
|
|
WINRT_PROPERTY(winrt::Windows::Foundation::IReference<winrt::Microsoft::Terminal::Core::Color>, TabColor, nullptr);
|
|
WINRT_PROPERTY(winrt::Windows::Foundation::IReference<winrt::Microsoft::Terminal::Core::Color>, StartingTabColor, nullptr);
|
|
WINRT_PROPERTY(bool, IntenseIsBright);
|
|
WINRT_PROPERTY(winrt::hstring, ProfileName);
|
|
WINRT_PROPERTY(bool, UseAcrylic, false);
|
|
WINRT_PROPERTY(double, TintOpacity, 0.5);
|
|
WINRT_PROPERTY(winrt::hstring, Padding, DEFAULT_PADDING);
|
|
WINRT_PROPERTY(winrt::hstring, FontFace, DEFAULT_FONT_FACE);
|
|
WINRT_PROPERTY(int32_t, FontSize, DEFAULT_FONT_SIZE);
|
|
WINRT_PROPERTY(winrt::Windows::UI::Text::FontWeight, FontWeight, winrt::Windows::UI::Text::FontWeight{ 400 });
|
|
WINRT_PROPERTY(IFontAxesMap, FontAxes);
|
|
WINRT_PROPERTY(IFontFeatureMap, FontFeatures);
|
|
WINRT_PROPERTY(winrt::hstring, BackgroundImage);
|
|
WINRT_PROPERTY(double, BackgroundImageOpacity, 1.0);
|
|
WINRT_PROPERTY(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill);
|
|
WINRT_PROPERTY(winrt::Windows::UI::Xaml::HorizontalAlignment, BackgroundImageHorizontalAlignment, winrt::Windows::UI::Xaml::HorizontalAlignment::Center);
|
|
WINRT_PROPERTY(winrt::Windows::UI::Xaml::VerticalAlignment, BackgroundImageVerticalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment::Center);
|
|
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr);
|
|
WINRT_PROPERTY(winrt::hstring, Commandline);
|
|
WINRT_PROPERTY(winrt::hstring, StartingDirectory);
|
|
WINRT_PROPERTY(winrt::hstring, StartingTitle);
|
|
WINRT_PROPERTY(bool, SuppressApplicationTitle);
|
|
WINRT_PROPERTY(winrt::hstring, EnvironmentVariables);
|
|
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::ScrollbarState, ScrollState, winrt::Microsoft::Terminal::Control::ScrollbarState::Visible);
|
|
WINRT_PROPERTY(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale);
|
|
WINRT_PROPERTY(bool, RetroTerminalEffect, false);
|
|
WINRT_PROPERTY(bool, ForceFullRepaintRendering, false);
|
|
WINRT_PROPERTY(bool, SoftwareRendering, false);
|
|
WINRT_PROPERTY(bool, ForceVTInput, false);
|
|
WINRT_PROPERTY(winrt::hstring, PixelShaderPath);
|
|
WINRT_PROPERTY(bool, IntenseIsBold);
|
|
|
|
private:
|
|
std::array<winrt::Microsoft::Terminal::Core::Color, 16> _ColorTable;
|
|
};
|
|
|
|
struct HwndTerminal
|
|
{
|
|
static LRESULT CALLBACK HwndTerminalWndProc(
|
|
HWND hwnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam) noexcept
|
|
try
|
|
{
|
|
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
|
|
HwndTerminal* terminal = reinterpret_cast<HwndTerminal*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
|
if (terminal)
|
|
{
|
|
return terminal->WindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_CAUGHT_EXCEPTION();
|
|
return 0;
|
|
}
|
|
|
|
static bool RegisterTermClass(HINSTANCE hInstance) noexcept
|
|
{
|
|
WNDCLASSW wc;
|
|
if (GetClassInfoW(hInstance, term_window_class, &wc))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
wc.style = 0;
|
|
wc.lpfnWndProc = HwndTerminal::HwndTerminalWndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.hInstance = hInstance;
|
|
wc.hIcon = nullptr;
|
|
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wc.hbrBackground = nullptr;
|
|
wc.lpszMenuName = nullptr;
|
|
wc.lpszClassName = term_window_class;
|
|
|
|
return RegisterClassW(&wc) != 0;
|
|
}
|
|
|
|
static MouseButtonState MouseButtonStateFromWParam(WPARAM wParam)
|
|
{
|
|
MouseButtonState state{};
|
|
WI_UpdateFlag(state, MouseButtonState::IsLeftButtonDown, WI_IsFlagSet(wParam, MK_LBUTTON));
|
|
WI_UpdateFlag(state, MouseButtonState::IsMiddleButtonDown, WI_IsFlagSet(wParam, MK_MBUTTON));
|
|
WI_UpdateFlag(state, MouseButtonState::IsRightButtonDown, WI_IsFlagSet(wParam, MK_RBUTTON));
|
|
return state;
|
|
}
|
|
|
|
static Point PointFromLParam(LPARAM lParam)
|
|
{
|
|
return { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
|
}
|
|
|
|
LRESULT WindowProc(
|
|
HWND hwnd,
|
|
UINT uMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam) noexcept
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
SetCapture(_hwnd.get());
|
|
_interactivity.PointerPressed(
|
|
MouseButtonStateFromWParam(wParam),
|
|
uMsg,
|
|
std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count(),
|
|
getControlKeyState(),
|
|
PointFromLParam(lParam));
|
|
return 0;
|
|
case WM_MOUSEMOVE:
|
|
_interactivity.PointerMoved(
|
|
MouseButtonStateFromWParam(wParam),
|
|
WM_MOUSEMOVE,
|
|
getControlKeyState(),
|
|
true,
|
|
PointFromLParam(lParam),
|
|
true);
|
|
return 0;
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
ReleaseCapture();
|
|
_interactivity.PointerReleased(
|
|
MouseButtonStateFromWParam(wParam),
|
|
uMsg,
|
|
getControlKeyState(),
|
|
PointFromLParam(lParam));
|
|
return 0;
|
|
case WM_MOUSEWHEEL:
|
|
if (_interactivity.MouseWheel(getControlKeyState(), GET_WHEEL_DELTA_WPARAM(wParam), PointFromLParam(lParam), MouseButtonStateFromWParam(wParam)))
|
|
{
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_SETFOCUS:
|
|
_interactivity.GotFocus();
|
|
break;
|
|
case WM_KILLFOCUS:
|
|
_interactivity.LostFocus();
|
|
break;
|
|
}
|
|
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
wil::unique_hwnd _hwnd;
|
|
|
|
HwndTerminal(HWND parentHwnd)
|
|
{
|
|
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
|
|
|
|
if (RegisterTermClass(hInstance))
|
|
{
|
|
_hwnd.reset(CreateWindowExW(
|
|
0,
|
|
term_window_class,
|
|
nullptr,
|
|
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
parentHwnd,
|
|
nullptr,
|
|
hInstance,
|
|
nullptr));
|
|
|
|
#pragma warning(suppress : 26490) // Win32 APIs can only store void*, have to use reinterpret_cast
|
|
SetWindowLongPtr(_hwnd.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
|
}
|
|
|
|
_connection = winrt::make_self<NullConnection>();
|
|
_interactivity = ControlInteractivity{ winrt::make<TerminalSettings>(), *_connection };
|
|
_core = _interactivity.Core();
|
|
}
|
|
|
|
winrt::com_ptr<NullConnection> _connection;
|
|
ControlInteractivity _interactivity{ nullptr };
|
|
ControlCore _core{ nullptr };
|
|
|
|
HRESULT SendOutput(LPCWSTR data)
|
|
try
|
|
{
|
|
_connection->WireOutput(data);
|
|
return S_OK;
|
|
}
|
|
CATCH_RETURN()
|
|
bool _initialized{ false };
|
|
|
|
HRESULT RegisterScrollCallback(PSCROLLCB callback) { return S_OK; }
|
|
HRESULT TriggerResize(_In_ short width, _In_ short height, _Out_ COORD* dimensions)
|
|
{
|
|
if (!_initialized)
|
|
return S_FALSE;
|
|
SetWindowPos(_hwnd.get(), nullptr, 0, 0, width, height, 0);
|
|
_core.SizeChanged(width, height);
|
|
return S_OK;
|
|
}
|
|
HRESULT TriggerResizeWithDimension(_In_ COORD dimensions, _Out_ SIZE* dimensionsInPixels) { return S_OK; }
|
|
HRESULT CalculateResize(_In_ short width, _In_ short height, _Out_ COORD* dimensions) { return S_OK; }
|
|
HRESULT DpiChanged(int newDpi)
|
|
{
|
|
_core.ScaleChanged((double)newDpi / 96.0);
|
|
return S_OK;
|
|
}
|
|
HRESULT UserScroll(int viewTop) { return S_OK; }
|
|
HRESULT ClearSelection() { return S_OK; }
|
|
HRESULT GetSelection(const wchar_t** out) { return S_OK; }
|
|
HRESULT IsSelectionActive(bool* out) { return S_OK; }
|
|
HRESULT SetTheme(TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi) { return S_OK; }
|
|
HRESULT RegisterWriteCallback(PWRITECB callback)
|
|
{
|
|
_connection->_pfnWriteCallback = callback;
|
|
return S_OK;
|
|
}
|
|
HRESULT SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown)
|
|
{
|
|
_core.TrySendKeyEvent(vkey, scanCode, getControlKeyState(), keyDown);
|
|
return S_OK;
|
|
}
|
|
HRESULT SendCharEvent(wchar_t ch, WORD flags, WORD scanCode)
|
|
{
|
|
_core.SendCharEvent(ch, scanCode, getControlKeyState());
|
|
return S_OK;
|
|
}
|
|
HRESULT BlinkCursor()
|
|
{
|
|
_core.CursorOn(!_core.CursorOn());
|
|
return S_OK;
|
|
}
|
|
HRESULT SetCursorVisible(const bool visible)
|
|
{
|
|
_core.CursorOn(visible);
|
|
return S_OK;
|
|
}
|
|
|
|
void Initialize()
|
|
{
|
|
RECT windowRect;
|
|
GetWindowRect(_hwnd.get(), &windowRect);
|
|
// BODGY: the +/-1 is because ControlCore will ignore an Initialize with zero size (oops)
|
|
// becuase in the old days, TermControl would accidentally try to resize the Swap Chain to 0x0 (oops)
|
|
// and therefore resize the connection to 0x0 (oops)
|
|
_core.InitializeWithHwnd(windowRect.right - windowRect.left + 1, windowRect.bottom - windowRect.top + 1, 1.0, reinterpret_cast<uint64_t>(_hwnd.get()));
|
|
_interactivity.Initialize();
|
|
_core.EnablePainting();
|
|
_initialized = true;
|
|
}
|
|
|
|
private:
|
|
};
|
|
|
|
__declspec(dllexport) HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ PTERM* terminal)
|
|
{
|
|
auto inner = new HwndTerminal{ parentHwnd };
|
|
*terminal = inner;
|
|
*hwnd = inner->_hwnd.get();
|
|
inner->Initialize();
|
|
return S_OK;
|
|
}
|
|
|
|
__declspec(dllexport) void _stdcall DestroyTerminal(PTERM terminal)
|
|
{
|
|
delete (HwndTerminal*)terminal;
|
|
}
|
|
|
|
// Generate all of the C->C++ bridge functions.
|
|
#define API_NAME(name) Terminal##name
|
|
#define GENERATOR_0(name) \
|
|
__declspec(dllexport) HRESULT _stdcall API_NAME(name)(PTERM terminal) \
|
|
{ \
|
|
return ((HwndTerminal*)(terminal))->name(); \
|
|
}
|
|
#define GENERATOR_1(name, t1, a1) \
|
|
__declspec(dllexport) HRESULT _stdcall API_NAME(name)(PTERM terminal, t1 a1) \
|
|
{ \
|
|
return ((HwndTerminal*)(terminal))->name(a1); \
|
|
}
|
|
#define GENERATOR_2(name, t1, a1, t2, a2) \
|
|
__declspec(dllexport) HRESULT _stdcall API_NAME(name)(PTERM terminal, t1 a1, t2 a2) \
|
|
{ \
|
|
return ((HwndTerminal*)(terminal))->name(a1, a2); \
|
|
}
|
|
#define GENERATOR_3(name, t1, a1, t2, a2, t3, a3) \
|
|
__declspec(dllexport) HRESULT _stdcall API_NAME(name)(PTERM terminal, t1 a1, t2 a2, t3 a3) \
|
|
{ \
|
|
return ((HwndTerminal*)(terminal))->name(a1, a2, a3); \
|
|
}
|
|
#define GENERATOR_4(name, t1, a1, t2, a2, t3, a3, t4, a4) \
|
|
__declspec(dllexport) HRESULT _stdcall API_NAME(name)(PTERM terminal, t1 a1, t2 a2, t3 a3, t4 a4) \
|
|
{ \
|
|
return ((HwndTerminal*)(terminal))->name(a1, a2, a3, a4); \
|
|
}
|
|
#define GENERATOR_N(name, t1, a1, t2, a2, t3, a3, t4, a4, MACRO, ...) MACRO
|
|
#define GENERATOR(...) \
|
|
GENERATOR_N(__VA_ARGS__, GENERATOR_4, GENERATOR_4, GENERATOR_3, GENERATOR_3, GENERATOR_2, GENERATOR_2, GENERATOR_1, GENERATOR_1, GENERATOR_0) \
|
|
(__VA_ARGS__)
|
|
TERMINAL_API_TABLE(GENERATOR)
|
|
#undef GENERATOR_0
|
|
#undef GENERATOR_1
|
|
#undef GENERATOR_2
|
|
#undef GENERATOR_3
|
|
#undef GENERATOR_4
|
|
#undef GENERATOR_N
|
|
#undef API_NAME
|