Files
terminal/patch.diff
2021-03-01 11:37:39 -06:00

1746 lines
68 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
From f33c69d8b4171ed43adc09113f2bd9975fdf4422 Mon Sep 17 00:00:00 2001
From: Mike Griese <migrie@microsoft.com>
Date: Fri, 17 Jul 2020 13:01:07 -0500
Subject: [PATCH] Add a scratch island project for testing
---
OpenConsole.sln | 38 ++
src/tools/ScratchIsland/AppHost.cpp | 176 +++++
src/tools/ScratchIsland/AppHost.h | 24 +
src/tools/ScratchIsland/BaseWindow.h | 228 +++++++
src/tools/ScratchIsland/IslandWindow.cpp | 604 ++++++++++++++++++
src/tools/ScratchIsland/IslandWindow.h | 77 +++
src/tools/ScratchIsland/ScratchIsland.def | 1 +
.../ScratchIsland/ScratchIsland.manifest | 24 +
src/tools/ScratchIsland/ScratchIsland.rc | 94 +++
src/tools/ScratchIsland/ScratchIsland.vcxproj | 164 +++++
src/tools/ScratchIsland/main.cpp | 54 ++
src/tools/ScratchIsland/packages.config | 8 +
src/tools/ScratchIsland/pch.cpp | 4 +
src/tools/ScratchIsland/pch.h | 74 +++
src/tools/ScratchIsland/resource.h | 24 +
15 files changed, 1594 insertions(+)
create mode 100644 src/tools/ScratchIsland/AppHost.cpp
create mode 100644 src/tools/ScratchIsland/AppHost.h
create mode 100644 src/tools/ScratchIsland/BaseWindow.h
create mode 100644 src/tools/ScratchIsland/IslandWindow.cpp
create mode 100644 src/tools/ScratchIsland/IslandWindow.h
create mode 100644 src/tools/ScratchIsland/ScratchIsland.def
create mode 100644 src/tools/ScratchIsland/ScratchIsland.manifest
create mode 100644 src/tools/ScratchIsland/ScratchIsland.rc
create mode 100644 src/tools/ScratchIsland/ScratchIsland.vcxproj
create mode 100644 src/tools/ScratchIsland/main.cpp
create mode 100644 src/tools/ScratchIsland/packages.config
create mode 100644 src/tools/ScratchIsland/pch.cpp
create mode 100644 src/tools/ScratchIsland/pch.h
create mode 100644 src/tools/ScratchIsland/resource.h
diff --git a/OpenConsole.sln b/OpenConsole.sln
index dcae33515..5099f457d 100644
--- a/OpenConsole.sln
+++ b/OpenConsole.sln
@@ -321,6 +321,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchWinRTClient", "src\t
{D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4}
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScratchIsland", "src\tools\ScratchIsland\ScratchIsland.vcxproj", "{23a1f736-cd19-4196-980f-84bcd50cf783}"
+ ProjectSection(ProjectDependencies) = postProject
+ {06382349-D62A-4C7D-A7D3-9CA817EAE092} = {06382349-D62A-4C7D-A7D3-9CA817EAE092}
+ {D46D9547-F085-4645-B8F7-E8CD21559AB4} = {D46D9547-F085-4645-B8F7-E8CD21559AB4}
+ EndProjectSection
+EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}"
EndProject
Global
@@ -2087,6 +2093,37 @@ Global
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32
+
+
+
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|Any CPU.ActiveCfg = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|Any CPU.Build.0 = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|ARM64.ActiveCfg = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|ARM64.Build.0 = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x64Test.ActiveCfg = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x64Test.Build.0 = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x86Test.ActiveCfg = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|DotNet_x86Test.Build.0 = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x64.ActiveCfg = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x64.Build.0 = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x86.ActiveCfg = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.AuditMode|x86.Build.0 = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|ARM64.ActiveCfg = Debug|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x64.ActiveCfg = Debug|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x64.Build.0 = Debug|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x86.ActiveCfg = Debug|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Debug|x86.Build.0 = Debug|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|Any CPU.ActiveCfg = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|ARM64.ActiveCfg = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x64.ActiveCfg = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x64.Build.0 = Release|x64
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x86.ActiveCfg = Release|Win32
+ {23a1f736-cd19-4196-980f-84bcd50cf783}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2169,6 +2206,7 @@ Global
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
{D46D9547-F085-4645-B8F7-E8CD21559AB4} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{06382349-D62A-4C7D-A7D3-9CA817EAE092} = {A10C4720-DCA4-4640-9749-67F4314F527C}
+ {23a1f736-cd19-4196-980f-84bcd50cf783} = {A10C4720-DCA4-4640-9749-67F4314F527C}
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/src/tools/ScratchIsland/AppHost.cpp b/src/tools/ScratchIsland/AppHost.cpp
new file mode 100644
index 000000000..d1ec7b004
--- /dev/null
+++ b/src/tools/ScratchIsland/AppHost.cpp
@@ -0,0 +1,176 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "AppHost.h"
+#include "../types/inc/Viewport.hpp"
+#include "../types/inc/utils.hpp"
+#include "../types/inc/User32Utils.hpp"
+
+#include "resource.h"
+
+using namespace winrt::Windows::UI;
+using namespace winrt::Windows::UI::Composition;
+using namespace winrt::Windows::UI::Xaml;
+using namespace winrt::Windows::UI::Xaml::Hosting;
+using namespace winrt::Windows::Foundation::Numerics;
+using namespace ::Microsoft::Console;
+using namespace ::Microsoft::Console::Types;
+
+AppHost::AppHost() noexcept :
+ _window{ nullptr }
+{
+ _window = std::make_unique<IslandWindow>();
+
+ // Tell the window to callback to us when it's about to handle a WM_CREATE
+ auto pfn = std::bind(&AppHost::_HandleCreateWindow,
+ this,
+ std::placeholders::_1,
+ std::placeholders::_2);
+ _window->SetCreateCallback(pfn);
+
+ _window->MakeWindow();
+}
+
+AppHost::~AppHost()
+{
+ // destruction order is important for proper teardown here
+ _window = nullptr;
+}
+
+// Method Description:
+// - Initializes the XAML island, creates the terminal app, and sets the
+// island's content to that of the terminal app's content. Also registers some
+// callbacks with TermApp.
+// !!! IMPORTANT!!!
+// This must be called *AFTER* WindowsXamlManager::InitializeForCurrentThread.
+// If it isn't, then we won't be able to create the XAML island.
+// Arguments:
+// - <none>
+// Return Value:
+// - <none>
+void AppHost::Initialize()
+{
+ _window->Initialize();
+
+ // Set up the content of the application. If the app has a custom titlebar,
+ // set that content as well.
+ winrt::Windows::UI::Xaml::Controls::Grid g;
+ // TODO: INITIALIZE THIS UI HERE
+ _window->SetContent(g);
+
+ _window->OnAppInitialized();
+}
+
+// Method Description:
+// - Resize the window we're about to create to the appropriate dimensions, as
+// specified in the settings. This will be called during the handling of
+// WM_CREATE. We'll load the settings for the app, then get the proposed size
+// of the terminal from the app. Using that proposed size, we'll resize the
+// window we're creating, so that it'll match the values in the settings.
+// Arguments:
+// - hwnd: The HWND of the window we're about to create.
+// - proposedRect: The location and size of the window that we're about to
+// create. We'll use this rect to determine which monitor the window is about
+// to appear on.
+// - launchMode: A LaunchMode enum reference that indicates the launch mode
+// Return Value:
+// - None
+void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect)
+{
+ // Acquire the actual initial position
+ winrt::Windows::Foundation::Point initialPosition{ (float)proposedRect.left, (float)proposedRect.top };
+ proposedRect.left = gsl::narrow_cast<long>(initialPosition.X);
+ proposedRect.top = gsl::narrow_cast<long>(initialPosition.Y);
+
+ long adjustedHeight = 0;
+ long adjustedWidth = 0;
+
+ // Find nearest monitor.
+ HMONITOR hmon = MonitorFromRect(&proposedRect, MONITOR_DEFAULTTONEAREST);
+
+ // Get nearest monitor information
+ MONITORINFO monitorInfo;
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(hmon, &monitorInfo);
+
+ // This API guarantees that dpix and dpiy will be equal, but neither is an
+ // optional parameter so give two UINTs.
+ UINT dpix = USER_DEFAULT_SCREEN_DPI;
+ UINT dpiy = USER_DEFAULT_SCREEN_DPI;
+ // If this fails, we'll use the default of 96.
+ GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy);
+
+ // We need to check if the top left point of the titlebar of the window is within any screen
+ RECT offScreenTestRect;
+ offScreenTestRect.left = proposedRect.left;
+ offScreenTestRect.top = proposedRect.top;
+ offScreenTestRect.right = offScreenTestRect.left + 1;
+ offScreenTestRect.bottom = offScreenTestRect.top + 1;
+
+ bool isTitlebarIntersectWithMonitors = false;
+ EnumDisplayMonitors(
+ nullptr, &offScreenTestRect, [](HMONITOR, HDC, LPRECT, LPARAM lParam) -> BOOL {
+ auto intersectWithMonitor = reinterpret_cast<bool*>(lParam);
+ *intersectWithMonitor = true;
+ // Continue the enumeration
+ return FALSE;
+ },
+ reinterpret_cast<LPARAM>(&isTitlebarIntersectWithMonitors));
+
+ if (!isTitlebarIntersectWithMonitors)
+ {
+ // If the title bar is out-of-screen, we set the initial position to
+ // the top left corner of the nearest monitor
+ proposedRect.left = monitorInfo.rcWork.left;
+ proposedRect.top = monitorInfo.rcWork.top;
+ }
+
+ winrt::Windows::Foundation::Size initialSize{ 800, 600 };
+
+ const short islandWidth = Utils::ClampToShortMax(
+ static_cast<long>(ceil(initialSize.Width)), 1);
+ const short islandHeight = Utils::ClampToShortMax(
+ static_cast<long>(ceil(initialSize.Height)), 1);
+
+ // Get the size of a window we'd need to host that client rect. This will
+ // add the titlebar space.
+ const auto nonClientSize = _window->GetTotalNonClientExclusiveSize(dpix);
+ adjustedWidth = islandWidth + nonClientSize.cx;
+ adjustedHeight = islandHeight + nonClientSize.cy;
+
+ const COORD origin{ gsl::narrow<short>(proposedRect.left),
+ gsl::narrow<short>(proposedRect.top) };
+ const COORD dimensions{ Utils::ClampToShortMax(adjustedWidth, 1),
+ Utils::ClampToShortMax(adjustedHeight, 1) };
+
+ const auto newPos = Viewport::FromDimensions(origin, dimensions);
+ bool succeeded = SetWindowPos(hwnd,
+ nullptr,
+ newPos.Left(),
+ newPos.Top(),
+ newPos.Width(),
+ newPos.Height(),
+ SWP_NOACTIVATE | SWP_NOZORDER);
+
+ // Refresh the dpi of HWND because the dpi where the window will launch may be different
+ // at this time
+ _window->RefreshCurrentDPI();
+
+ // If we can't resize the window, that's really okay. We can just go on with
+ // the originally proposed window size.
+ LOG_LAST_ERROR_IF(!succeeded);
+}
+
+// Method Description:
+// - Called when the app wants to change its theme. We'll forward this to the
+// IslandWindow, so it can update the root UI element of the entire XAML tree.
+// Arguments:
+// - sender: unused
+// - arg: the ElementTheme to use as the new theme for the UI
+// Return Value:
+// - <none>
+void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
+{
+ _window->OnApplicationThemeChanged(arg);
+}
diff --git a/src/tools/ScratchIsland/AppHost.h b/src/tools/ScratchIsland/AppHost.h
new file mode 100644
index 000000000..e4a957cd8
--- /dev/null
+++ b/src/tools/ScratchIsland/AppHost.h
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+
+#include "IslandWindow.h"
+
+class AppHost
+{
+public:
+ AppHost() noexcept;
+ virtual ~AppHost();
+
+ void Initialize();
+
+private:
+ bool _useNonClientArea{ false };
+
+ std::unique_ptr<IslandWindow> _window;
+
+ void _HandleCreateWindow(const HWND hwnd, RECT proposedRect);
+ void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&,
+ const winrt::Windows::UI::Xaml::ElementTheme& arg);
+};
diff --git a/src/tools/ScratchIsland/BaseWindow.h b/src/tools/ScratchIsland/BaseWindow.h
new file mode 100644
index 000000000..d1a8b86d7
--- /dev/null
+++ b/src/tools/ScratchIsland/BaseWindow.h
@@ -0,0 +1,228 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+// Custom window messages
+#define CM_UPDATE_TITLE (WM_USER)
+
+#include <wil/resource.h>
+
+template<typename T>
+class BaseWindow
+{
+public:
+ virtual ~BaseWindow() = 0;
+ static T* GetThisFromHandle(HWND const window) noexcept
+ {
+ return reinterpret_cast<T*>(GetWindowLongPtr(window, GWLP_USERDATA));
+ }
+
+ [[nodiscard]] static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
+ {
+ WINRT_ASSERT(window);
+
+ if (WM_NCCREATE == message)
+ {
+ auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
+ T* that = static_cast<T*>(cs->lpCreateParams);
+ WINRT_ASSERT(that);
+ WINRT_ASSERT(!that->_window);
+ that->_window = wil::unique_hwnd(window);
+
+ return that->_OnNcCreate(wparam, lparam);
+ }
+ else if (T* that = GetThisFromHandle(window))
+ {
+ return that->MessageHandler(message, wparam, lparam);
+ }
+
+ return DefWindowProc(window, message, wparam, lparam);
+ }
+
+ [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
+ {
+ switch (message)
+ {
+ case WM_DPICHANGED:
+ {
+ return HandleDpiChange(_window.get(), wparam, lparam);
+ }
+
+ case WM_DESTROY:
+ {
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ case WM_SIZE:
+ {
+ UINT width = LOWORD(lparam);
+ UINT height = HIWORD(lparam);
+
+ switch (wparam)
+ {
+ case SIZE_MAXIMIZED:
+ [[fallthrough]];
+ case SIZE_RESTORED:
+ if (_minimized)
+ {
+ _minimized = false;
+ OnRestore();
+ }
+
+ // We always need to fire the resize event, even when we're transitioning from minimized.
+ // We might be transitioning directly from minimized to maximized, and we'll need
+ // to trigger any size-related content changes.
+ OnResize(width, height);
+ break;
+ case SIZE_MINIMIZED:
+ if (!_minimized)
+ {
+ _minimized = true;
+ OnMinimize();
+ }
+ break;
+ default:
+ // do nothing.
+ break;
+ }
+ break;
+ }
+ case CM_UPDATE_TITLE:
+ {
+ SetWindowTextW(_window.get(), _title.c_str());
+ break;
+ }
+ }
+
+ return DefWindowProc(_window.get(), message, wparam, lparam);
+ }
+
+ // DPI Change handler. on WM_DPICHANGE resize the window
+ [[nodiscard]] LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam)
+ {
+ _inDpiChange = true;
+ const HWND hWndStatic = GetWindow(hWnd, GW_CHILD);
+ if (hWndStatic != nullptr)
+ {
+ const UINT uDpi = HIWORD(wParam);
+
+ // Resize the window
+ auto lprcNewScale = reinterpret_cast<RECT*>(lParam);
+
+ SetWindowPos(hWnd, nullptr, lprcNewScale->left, lprcNewScale->top, lprcNewScale->right - lprcNewScale->left, lprcNewScale->bottom - lprcNewScale->top, SWP_NOZORDER | SWP_NOACTIVATE);
+
+ _currentDpi = uDpi;
+ }
+ _inDpiChange = false;
+ return 0;
+ }
+
+ virtual void OnResize(const UINT width, const UINT height) = 0;
+ virtual void OnMinimize() = 0;
+ virtual void OnRestore() = 0;
+
+ RECT GetWindowRect() const noexcept
+ {
+ RECT rc = { 0 };
+ ::GetWindowRect(_window.get(), &rc);
+ return rc;
+ }
+
+ HWND GetHandle() const noexcept
+ {
+ return _window.get();
+ }
+
+ float GetCurrentDpiScale() const noexcept
+ {
+ const auto dpi = ::GetDpiForWindow(_window.get());
+ const auto scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
+ return scale;
+ }
+
+ //// Gets the physical size of the client area of the HWND in _window
+ SIZE GetPhysicalSize() const noexcept
+ {
+ RECT rect = {};
+ GetClientRect(_window.get(), &rect);
+ const auto windowsWidth = rect.right - rect.left;
+ const auto windowsHeight = rect.bottom - rect.top;
+ return SIZE{ windowsWidth, windowsHeight };
+ }
+
+ //// Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize
+ //// Remarks:
+ //// XAML coordinate system is always in Display Independent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons)
+ //// in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels.
+ //// The formula to transform is:
+ //// logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
+ //// See also:
+ //// https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels
+ //// https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness
+ winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept
+ {
+ const auto scale = GetCurrentDpiScale();
+ // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
+ const auto logicalWidth = (physicalSize.cx / scale) + 0.5f;
+ const auto logicalHeight = (physicalSize.cy / scale) + 0.5f;
+ return winrt::Windows::Foundation::Size(logicalWidth, logicalHeight);
+ }
+
+ winrt::Windows::Foundation::Size GetLogicalSize() const noexcept
+ {
+ return GetLogicalSize(GetPhysicalSize());
+ }
+
+ // Method Description:
+ // - Sends a message to our message loop to update the title of the window.
+ // Arguments:
+ // - newTitle: a string to use as the new title of the window.
+ // Return Value:
+ // - <none>
+ void UpdateTitle(std::wstring_view newTitle)
+ {
+ _title = newTitle;
+ PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast<LPARAM>(nullptr));
+ }
+
+ // Method Description:
+ // Reset the current dpi of the window. This method is only called after we change the
+ // initial launch position. This makes sure the dpi is consistent with the monitor on which
+ // the window will launch
+ void RefreshCurrentDPI()
+ {
+ _currentDpi = GetDpiForWindow(_window.get());
+ }
+
+protected:
+ using base_type = BaseWindow<T>;
+ wil::unique_hwnd _window;
+
+ unsigned int _currentDpi = 0;
+ bool _inDpiChange = false;
+
+ std::wstring _title = L"";
+
+ bool _minimized = false;
+
+ // Method Description:
+ // - This method is called when the window receives the WM_NCCREATE message.
+ // Return Value:
+ // - The value returned from the window proc.
+ virtual [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept
+ {
+ SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
+
+ EnableNonClientDpiScaling(_window.get());
+ _currentDpi = GetDpiForWindow(_window.get());
+
+ return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam);
+ };
+};
+
+template<typename T>
+inline BaseWindow<T>::~BaseWindow()
+{
+}
diff --git a/src/tools/ScratchIsland/IslandWindow.cpp b/src/tools/ScratchIsland/IslandWindow.cpp
new file mode 100644
index 000000000..3813c6e70
--- /dev/null
+++ b/src/tools/ScratchIsland/IslandWindow.cpp
@@ -0,0 +1,604 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "IslandWindow.h"
+#include "../types/inc/Viewport.hpp"
+#include "resource.h"
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+using namespace winrt::Windows::UI;
+using namespace winrt::Windows::UI::Composition;
+using namespace winrt::Windows::UI::Xaml;
+using namespace winrt::Windows::UI::Xaml::Hosting;
+using namespace winrt::Windows::Foundation::Numerics;
+using namespace ::Microsoft::Console::Types;
+
+#define XAML_HOSTING_WINDOW_CLASS_NAME L"SCRATCH_ISLAND_CLASS"
+
+IslandWindow::IslandWindow() noexcept :
+ _interopWindowHandle{ nullptr },
+ _rootGrid{ nullptr },
+ _source{ nullptr },
+ _pfnCreateCallback{ nullptr }
+{
+}
+
+IslandWindow::~IslandWindow()
+{
+ _source.Close();
+}
+
+// Method Description:
+// - Create the actual window that we'll use for the application.
+// Arguments:
+// - <none>
+// Return Value:
+// - <none>
+void IslandWindow::MakeWindow() noexcept
+{
+ WNDCLASS wc{};
+ wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ wc.hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
+ wc.lpszClassName = XAML_HOSTING_WINDOW_CLASS_NAME;
+ wc.style = CS_HREDRAW | CS_VREDRAW;
+ wc.lpfnWndProc = WndProc;
+ wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON));
+ RegisterClass(&wc);
+ WINRT_ASSERT(!_window);
+
+ // Create the window with the default size here - During the creation of the
+ // window, the system will give us a chance to set its size in WM_CREATE.
+ // WM_CREATE will be handled synchronously, before CreateWindow returns.
+ WINRT_VERIFY(CreateWindowEx(_alwaysOnTop ? WS_EX_TOPMOST : 0,
+ wc.lpszClassName,
+ L"Scratch Island",
+ WS_OVERLAPPEDWINDOW,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ CW_USEDEFAULT,
+ nullptr,
+ nullptr,
+ wc.hInstance,
+ this));
+
+ WINRT_ASSERT(_window);
+}
+
+// Method Description:
+// - Called when no tab is remaining to close the window.
+// Arguments:
+// - <none>
+// Return Value:
+// - <none>
+void IslandWindow::Close()
+{
+ PostQuitMessage(0);
+}
+
+// Method Description:
+// - Set a callback to be called when we process a WM_CREATE message. This gives
+// the AppHost a chance to resize the window to the proper size.
+// Arguments:
+// - pfn: a function to be called during the handling of WM_CREATE. It takes two
+// parameters:
+// * HWND: the HWND of the window that's being created.
+// * RECT: The position on the screen that the system has proposed for our
+// window.
+// Return Value:
+// - <none>
+void IslandWindow::SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept
+{
+ _pfnCreateCallback = pfn;
+}
+
+// Method Description:
+// - Handles a WM_CREATE message. Calls our create callback, if one's been set.
+// Arguments:
+// - wParam: unused
+// - lParam: the lParam of a WM_CREATE, which is a pointer to a CREATESTRUCTW
+// Return Value:
+// - <none>
+void IslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam) noexcept
+{
+ // Get proposed window rect from create structure
+ CREATESTRUCTW* pcs = reinterpret_cast<CREATESTRUCTW*>(lParam);
+ RECT rc;
+ rc.left = pcs->x;
+ rc.top = pcs->y;
+ rc.right = rc.left + pcs->cx;
+ rc.bottom = rc.top + pcs->cy;
+
+ if (_pfnCreateCallback)
+ {
+ _pfnCreateCallback(_window.get(), rc);
+ }
+
+ int nCmdShow = SW_SHOW;
+
+ ShowWindow(_window.get(), nCmdShow);
+
+ UpdateWindow(_window.get());
+}
+
+// Method Description:
+// - Handles a WM_SIZING message, which occurs when user drags a window border
+// or corner. It intercepts this resize action and applies 'snapping' i.e.
+// aligns the terminal's size to its cell grid. We're given the window size,
+// which we then adjust based on the terminal's properties (like font size).
+// Arguments:
+// - wParam: Specifies which edge of the window is being dragged.
+// - lParam: Pointer to the requested window rectangle (this is, the one that
+// originates from current drag action). It also acts as the return value
+// (it's a ref parameter).
+// Return Value:
+// - <none>
+LRESULT IslandWindow::_OnSizing(const WPARAM /*wParam*/, const LPARAM /*lParam*/)
+{
+ // If we haven't been given the callback that would adjust the dimension,
+ // then we can't do anything, so just bail out.
+ return FALSE;
+}
+
+void IslandWindow::Initialize()
+{
+ const bool initialized = (_interopWindowHandle != nullptr);
+
+ _source = DesktopWindowXamlSource{};
+
+ auto interop = _source.as<IDesktopWindowXamlSourceNative>();
+ winrt::check_hresult(interop->AttachToWindow(_window.get()));
+
+ // stash the child interop handle so we can resize it when the main hwnd is resized
+ interop->get_WindowHandle(&_interopWindowHandle);
+
+ _rootGrid = winrt::Windows::UI::Xaml::Controls::Grid();
+ _source.Content(_rootGrid);
+}
+
+void IslandWindow::OnSize(const UINT width, const UINT height)
+{
+ // update the interop window size
+ SetWindowPos(_interopWindowHandle, nullptr, 0, 0, width, height, SWP_SHOWWINDOW);
+
+ if (_rootGrid)
+ {
+ const auto size = GetLogicalSize();
+ _rootGrid.Width(size.Width);
+ _rootGrid.Height(size.Height);
+ }
+}
+
+[[nodiscard]] LRESULT IslandWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
+{
+ switch (message)
+ {
+ case WM_CREATE:
+ {
+ _HandleCreateWindow(wparam, lparam);
+ return 0;
+ }
+ case WM_SETFOCUS:
+ {
+ if (_interopWindowHandle != nullptr)
+ {
+ // send focus to the child window
+ SetFocus(_interopWindowHandle);
+ return 0; // eat the message
+ }
+ }
+
+ case WM_NCLBUTTONDOWN:
+ case WM_NCLBUTTONUP:
+ case WM_NCMBUTTONDOWN:
+ case WM_NCMBUTTONUP:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCRBUTTONUP:
+ case WM_NCXBUTTONDOWN:
+ case WM_NCXBUTTONUP:
+ {
+ // If we clicked in the titlebar, raise an event so the app host can
+ // dispatch an appropriate event.
+ _DragRegionClickedHandlers();
+ break;
+ }
+ case WM_MENUCHAR:
+ {
+ // GH#891: return this LRESULT here to prevent the app from making a
+ // bell when alt+key is pressed. A menu is active and the user presses a
+ // key that does not correspond to any mnemonic or accelerator key,
+ return MAKELRESULT(0, MNC_CLOSE);
+ }
+ case WM_SIZING:
+ {
+ return _OnSizing(wparam, lparam);
+ }
+ case WM_CLOSE:
+ {
+ // If the user wants to close the app by clicking 'X' button,
+ // we hand off the close experience to the app layer. If all the tabs
+ // are closed, the window will be closed as well.
+ _windowCloseButtonClickedHandler();
+ return 0;
+ }
+ case WM_MOUSEWHEEL:
+ try
+ {
+ // This whole handler is a hack for GH#979.
+ //
+ // On some laptops, their trackpads won't scroll inactive windows
+ // _ever_. With our entire window just being one giant XAML Island, the
+ // touchpad driver thinks our entire window is inactive, and won't
+ // scroll the XAML island. On those types of laptops, we'll get a
+ // WM_MOUSEWHEEL here, in our root window, when the trackpad scrolls.
+ // We're going to take that message and manually plumb it through to our
+ // TermControl's, or anything else that implements IMouseWheelListener.
+
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms645617(v=vs.85).aspx
+ // Important! Do not use the LOWORD or HIWORD macros to extract the x-
+ // and y- coordinates of the cursor position because these macros return
+ // incorrect results on systems with multiple monitors. Systems with
+ // multiple monitors can have negative x- and y- coordinates, and LOWORD
+ // and HIWORD treat the coordinates as unsigned quantities.
+ const til::point eventPoint{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
+ // This mouse event is relative to the display origin, not the window. Convert here.
+ const til::rectangle windowRect{ GetWindowRect() };
+ const auto origin = windowRect.origin();
+ const auto relative = eventPoint - origin;
+ // Convert to logical scaling before raising the event.
+ const auto real = relative / GetCurrentDpiScale();
+
+ const short wheelDelta = static_cast<short>(HIWORD(wparam));
+
+ // Raise an event, so any listeners can handle the mouse wheel event manually.
+ _MouseScrolledHandlers(real, wheelDelta);
+ return 0;
+ }
+ CATCH_LOG();
+ }
+
+ // TODO: handle messages here...
+ return base_type::MessageHandler(message, wparam, lparam);
+}
+
+// Method Description:
+// - Called when the window has been resized (or maximized)
+// Arguments:
+// - width: the new width of the window _in pixels_
+// - height: the new height of the window _in pixels_
+void IslandWindow::OnResize(const UINT width, const UINT height)
+{
+ if (_interopWindowHandle)
+ {
+ OnSize(width, height);
+ }
+}
+
+// Method Description:
+// - Called when the window is minimized to the taskbar.
+void IslandWindow::OnMinimize()
+{
+ // TODO GH#1989 Stop rendering island content when the app is minimized.
+}
+
+// Method Description:
+// - Called when the window is restored from having been minimized.
+void IslandWindow::OnRestore()
+{
+ // TODO GH#1989 Stop rendering island content when the app is minimized.
+}
+
+void IslandWindow::SetContent(winrt::Windows::UI::Xaml::UIElement content)
+{
+ _rootGrid.Children().Clear();
+ _rootGrid.Children().Append(content);
+}
+
+// Method Description:
+// - Gets the difference between window and client area size.
+// Arguments:
+// - dpi: dpi of a monitor on which the window is placed
+// Return Value
+// - The size difference
+SIZE IslandWindow::GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept
+{
+ const auto windowStyle = static_cast<DWORD>(GetWindowLong(_window.get(), GWL_STYLE));
+ RECT islandFrame{};
+
+ // If we failed to get the correct window size for whatever reason, log
+ // the error and go on. We'll use whatever the control proposed as the
+ // size of our window, which will be at least close.
+ LOG_IF_WIN32_BOOL_FALSE(AdjustWindowRectExForDpi(&islandFrame, windowStyle, false, 0, dpi));
+
+ return {
+ islandFrame.right - islandFrame.left,
+ islandFrame.bottom - islandFrame.top
+ };
+}
+
+void IslandWindow::OnAppInitialized()
+{
+ // Do a quick resize to force the island to paint
+ const auto size = GetPhysicalSize();
+ OnSize(size.cx, size.cy);
+}
+
+// Method Description:
+// - Called when the app wants to change its theme. We'll update the root UI
+// element of the entire XAML tree, so that all UI elements get the theme
+// applied.
+// Arguments:
+// - arg: the ElementTheme to use as the new theme for the UI
+// Return Value:
+// - <none>
+void IslandWindow::OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
+{
+ _rootGrid.RequestedTheme(requestedTheme);
+ // Invalidate the window rect, so that we'll repaint any elements we're
+ // drawing ourselves to match the new theme
+ ::InvalidateRect(_window.get(), nullptr, false);
+}
+
+// Method Description:
+// - Updates our focus mode state. See _SetIsBorderless for more details.
+// Arguments:
+// - <none>
+// Return Value:
+// - <none>
+void IslandWindow::FocusModeChanged(const bool focusMode)
+{
+ // Do nothing if the value was unchanged.
+ if (focusMode == _borderless)
+ {
+ return;
+ }
+
+ _SetIsBorderless(focusMode);
+}
+
+// Method Description:
+// - Updates our fullscreen state. See _SetIsFullscreen for more details.
+// Arguments:
+// - <none>
+// Return Value:
+// - <none>
+void IslandWindow::FullscreenChanged(const bool fullscreen)
+{
+ // Do nothing if the value was unchanged.
+ if (fullscreen == _fullscreen)
+ {
+ return;
+ }
+
+ _SetIsFullscreen(fullscreen);
+}
+
+// Method Description:
+// - Enter or exit the "always on top" state. Before the window is created, this
+// value will later be used when we create the window to create the window on
+// top of all others. After the window is created, it will either enter the
+// group of topmost windows, or exit the group of topmost windows.
+// Arguments:
+// - alwaysOnTop: whether we should be entering or exiting always on top mode.
+// Return Value:
+// - <none>
+void IslandWindow::SetAlwaysOnTop(const bool alwaysOnTop)
+{
+ _alwaysOnTop = alwaysOnTop;
+
+ const auto hwnd = GetHandle();
+ if (hwnd)
+ {
+ SetWindowPos(hwnd,
+ _alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
+ 0, // the window dimensions are unused, because we're passing SWP_NOSIZE
+ 0,
+ 0,
+ 0,
+ SWP_NOMOVE | SWP_NOSIZE);
+ }
+}
+
+// From GdiEngine::s_SetWindowLongWHelper
+void _SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept
+{
+ // SetWindowLong has strange error handling. On success, it returns the
+ // previous Window Long value and doesn't modify the Last Error state. To
+ // deal with this, we set the last error to 0/S_OK first, call it, and if
+ // the previous long was 0, we check if the error was non-zero before
+ // reporting. Otherwise, we'll get an "Error: The operation has completed
+ // successfully." and there will be another screenshot on the internet
+ // making fun of Windows. See:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx
+ SetLastError(0);
+ LONG const lResult = SetWindowLongW(hWnd, nIndex, dwNewLong);
+ if (0 == lResult)
+ {
+ LOG_LAST_ERROR_IF(0 != GetLastError());
+ }
+}
+
+// Method Description:
+// - This is a helper to figure out what the window styles should be, given the
+// current state of flags like borderless mode and fullscreen mode.
+// Arguments:
+// - <none>
+// Return Value:
+// - a LONG with the appropriate flags set for our current window mode, to be used with GWL_STYLE
+LONG IslandWindow::_getDesiredWindowStyle() const
+{
+ auto windowStyle = GetWindowLongW(GetHandle(), GWL_STYLE);
+
+ // If we're both fullscreen and borderless, fullscreen mode takes precedence.
+
+ if (_fullscreen)
+ {
+ // When moving to fullscreen, remove WS_OVERLAPPEDWINDOW, which specifies
+ // styles for non-fullscreen windows (e.g. caption bar), and add the
+ // WS_POPUP style to allow us to size ourselves to the monitor size.
+ // Do the reverse when restoring from fullscreen.
+ // Doing these modifications to that window will cause a vista-style
+ // window frame to briefly appear when entering and exiting fullscreen.
+ WI_ClearFlag(windowStyle, WS_BORDER);
+ WI_ClearFlag(windowStyle, WS_SIZEBOX);
+ WI_ClearAllFlags(windowStyle, WS_OVERLAPPEDWINDOW);
+
+ WI_SetFlag(windowStyle, WS_POPUP);
+ return windowStyle;
+ }
+ else if (_borderless)
+ {
+ // When moving to borderless, remove WS_OVERLAPPEDWINDOW, which
+ // specifies styles for non-fullscreen windows (e.g. caption bar), and
+ // add the WS_BORDER and WS_SIZEBOX styles. This allows us to still have
+ // a small resizing frame, but without a full titlebar, nor caption
+ // buttons.
+
+ WI_ClearAllFlags(windowStyle, WS_OVERLAPPEDWINDOW);
+ WI_ClearFlag(windowStyle, WS_POPUP);
+
+ WI_SetFlag(windowStyle, WS_BORDER);
+ WI_SetFlag(windowStyle, WS_SIZEBOX);
+ return windowStyle;
+ }
+
+ // Here, we're not in either fullscreen or borderless mode. Return to
+ // WS_OVERLAPPEDWINDOW.
+ WI_ClearFlag(windowStyle, WS_POPUP);
+ WI_ClearFlag(windowStyle, WS_BORDER);
+ WI_ClearFlag(windowStyle, WS_SIZEBOX);
+
+ WI_SetAllFlags(windowStyle, WS_OVERLAPPEDWINDOW);
+
+ return windowStyle;
+}
+
+// Method Description:
+// - Enable or disable focus mode. When entering focus mode, we'll
+// need to manually hide the entire titlebar.
+// - When we're entering focus we need to do some additional modification
+// of our window styles. However, the NonClientIslandWindow very explicitly
+// _doesn't_ need to do these steps.
+// Arguments:
+// - borderlessEnabled: If true, we're entering focus mode. If false, we're leaving.
+// Return Value:
+// - <none>
+void IslandWindow::_SetIsBorderless(const bool borderlessEnabled)
+{
+ _borderless = borderlessEnabled;
+
+ HWND const hWnd = GetHandle();
+
+ // First, modify regular window styles as appropriate
+ auto windowStyle = _getDesiredWindowStyle();
+ _SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle);
+
+ // Now modify extended window styles as appropriate
+ // When moving to fullscreen, remove the window edge style to avoid an
+ // ugly border when not focused.
+ auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE);
+ WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen);
+ _SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle);
+
+ // Resize the window, with SWP_FRAMECHANGED, to trigger user32 to
+ // recalculate the non/client areas
+ const til::rectangle windowPos{ GetWindowRect() };
+ SetWindowPos(GetHandle(),
+ HWND_TOP,
+ windowPos.left<int>(),
+ windowPos.top<int>(),
+ windowPos.width<int>(),
+ windowPos.height<int>(),
+ SWP_SHOWWINDOW | SWP_FRAMECHANGED);
+}
+
+// Method Description:
+// - Controls setting us into or out of fullscreen mode. Largely taken from
+// Window::SetIsFullscreen in conhost.
+// - When entering fullscreen mode, we'll save the current window size and
+// location, and expand to take the entire monitor size. When leaving, we'll
+// use that saved size to restore back to.
+// Arguments:
+// - fullscreenEnabled true if we should enable fullscreen mode, false to disable.
+// Return Value:
+// - <none>
+void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
+{
+ // It is possible to enter _SetIsFullscreen even if we're already in full
+ // screen. Use the old is in fullscreen flag to gate checks that rely on the
+ // current state.
+ const auto oldIsInFullscreen = _fullscreen;
+ _fullscreen = fullscreenEnabled;
+
+ HWND const hWnd = GetHandle();
+
+ // First, modify regular window styles as appropriate
+ auto windowStyle = _getDesiredWindowStyle();
+ _SetWindowLongWHelper(hWnd, GWL_STYLE, windowStyle);
+
+ // Now modify extended window styles as appropriate
+ // When moving to fullscreen, remove the window edge style to avoid an
+ // ugly border when not focused.
+ auto exWindowStyle = GetWindowLongW(hWnd, GWL_EXSTYLE);
+ WI_UpdateFlag(exWindowStyle, WS_EX_WINDOWEDGE, !_fullscreen);
+ _SetWindowLongWHelper(hWnd, GWL_EXSTYLE, exWindowStyle);
+
+ // When entering/exiting fullscreen mode, we also need to backup/restore the
+ // current window size, and resize the window to match the new state.
+ _BackupWindowSizes(oldIsInFullscreen);
+ _ApplyWindowSize();
+}
+
+// Method Description:
+// - Used in entering/exiting fullscreen mode. Saves the current window size,
+// and the full size of the monitor, for use in _ApplyWindowSize.
+// - Taken from conhost's Window::_BackupWindowSizes
+// Arguments:
+// - fCurrentIsInFullscreen: true if we're currently in fullscreen mode.
+// Return Value:
+// - <none>
+void IslandWindow::_BackupWindowSizes(const bool fCurrentIsInFullscreen)
+{
+ if (_fullscreen)
+ {
+ // Note: the current window size depends on the current state of the
+ // window. So don't back it up if we're already in full screen.
+ if (!fCurrentIsInFullscreen)
+ {
+ _nonFullscreenWindowSize = GetWindowRect();
+ }
+
+ // get and back up the current monitor's size
+ HMONITOR const hCurrentMonitor = MonitorFromWindow(GetHandle(), MONITOR_DEFAULTTONEAREST);
+ MONITORINFO currMonitorInfo;
+ currMonitorInfo.cbSize = sizeof(currMonitorInfo);
+ if (GetMonitorInfo(hCurrentMonitor, &currMonitorInfo))
+ {
+ _fullscreenWindowSize = currMonitorInfo.rcMonitor;
+ }
+ }
+}
+
+// Method Description:
+// - Applys the appropriate window size for transitioning to/from fullscreen mode.
+// - Taken from conhost's Window::_ApplyWindowSize
+// Arguments:
+// - <none>
+// Return Value:
+// - <none>
+void IslandWindow::_ApplyWindowSize()
+{
+ const auto newSize = _fullscreen ? _fullscreenWindowSize : _nonFullscreenWindowSize;
+ LOG_IF_WIN32_BOOL_FALSE(SetWindowPos(GetHandle(),
+ HWND_TOP,
+ newSize.left,
+ newSize.top,
+ newSize.right - newSize.left,
+ newSize.bottom - newSize.top,
+ SWP_FRAMECHANGED));
+}
+
+DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
+DEFINE_EVENT(IslandWindow, WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
diff --git a/src/tools/ScratchIsland/IslandWindow.h b/src/tools/ScratchIsland/IslandWindow.h
new file mode 100644
index 000000000..109f7c1ec
--- /dev/null
+++ b/src/tools/ScratchIsland/IslandWindow.h
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "BaseWindow.h"
+#include "../../cascadia/inc/cppwinrt_utils.h"
+
+class IslandWindow :
+ public BaseWindow<IslandWindow>
+{
+public:
+ IslandWindow() noexcept;
+ virtual ~IslandWindow() override;
+
+ virtual void MakeWindow() noexcept;
+ void Close();
+ virtual void OnSize(const UINT width, const UINT height);
+
+ [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
+ void OnResize(const UINT width, const UINT height) override;
+ void OnMinimize() override;
+ void OnRestore() override;
+ virtual void OnAppInitialized();
+ virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content);
+ virtual void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
+ virtual SIZE GetTotalNonClientExclusiveSize(const UINT dpi) const noexcept;
+
+ virtual void Initialize();
+
+ void SetCreateCallback(std::function<void(const HWND, const RECT)> pfn) noexcept;
+
+ void FocusModeChanged(const bool focusMode);
+ void FullscreenChanged(const bool fullscreen);
+ void SetAlwaysOnTop(const bool alwaysOnTop);
+
+#pragma endregion
+
+ DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>);
+ DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>);
+ WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
+
+protected:
+ void ForceResize()
+ {
+ // Do a quick resize to force the island to paint
+ const auto size = GetPhysicalSize();
+ OnSize(size.cx, size.cy);
+ }
+
+ HWND _interopWindowHandle;
+
+ winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _source;
+
+ winrt::Windows::UI::Xaml::Controls::Grid _rootGrid;
+
+ std::function<void(const HWND, const RECT)> _pfnCreateCallback;
+
+ void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept;
+ [[nodiscard]] LRESULT _OnSizing(const WPARAM wParam, const LPARAM lParam);
+
+ bool _borderless{ false };
+ bool _fullscreen{ false };
+ bool _alwaysOnTop{ false };
+ RECT _fullscreenWindowSize;
+ RECT _nonFullscreenWindowSize;
+
+ virtual void _SetIsBorderless(const bool borderlessEnabled);
+ virtual void _SetIsFullscreen(const bool fullscreenEnabled);
+ void _BackupWindowSizes(const bool currentIsInFullscreen);
+ void _ApplyWindowSize();
+
+ LONG _getDesiredWindowStyle() const;
+
+private:
+ // This minimum width allows for width the tabs fit
+ static constexpr long minimumWidth = 460L;
+};
diff --git a/src/tools/ScratchIsland/ScratchIsland.def b/src/tools/ScratchIsland/ScratchIsland.def
new file mode 100644
index 000000000..5f282702b
--- /dev/null
+++ b/src/tools/ScratchIsland/ScratchIsland.def
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/tools/ScratchIsland/ScratchIsland.manifest b/src/tools/ScratchIsland/ScratchIsland.manifest
new file mode 100644
index 000000000..a447bc8fd
--- /dev/null
+++ b/src/tools/ScratchIsland/ScratchIsland.manifest
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+
+ <!-- This file is copied into ut_app/TerminalApp.Unit.Tests.manifest as part
+ of the pre-build step for that project. Changes should only be made to the
+ WindowsTerminal version of the file. -->
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 1903 -->
+ <!-- See https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/xaml-islands -->
+ <!-- "maxversiontested" is CASE SENSITIVE. Do not change this.-->
+ <maxversiontested Id="10.0.18362.0"/>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+ </application>
+ </compatibility>
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ </windowsSettings>
+ </application>
+</assembly>
diff --git a/src/tools/ScratchIsland/ScratchIsland.rc b/src/tools/ScratchIsland/ScratchIsland.rc
new file mode 100644
index 000000000..78a784cf2
--- /dev/null
+++ b/src/tools/ScratchIsland/ScratchIsland.rc
@@ -0,0 +1,94 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APPICON ICON "..\\..\\..\\res\\terminal.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_ERROR_DIALOG_TITLE "Error"
+ IDS_HELP_DIALOG_TITLE "Help"
+ IDS_ERROR_ARCHITECTURE_FORMAT
+ "Windows Terminal is designed to run on your system's native architecture (%s).\nYou are currently using the %s version.\n\nPlease use the version of Windows Terminal that matches your system's native architecture."
+ IDS_X86_ARCHITECTURE "i386"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_AMD64_ARCHITECTURE "AMD64"
+ IDS_ARM64_ARCHITECTURE "ARM64"
+ IDS_ARM_ARCHITECTURE "ARM"
+ IDS_UNKNOWN_ARCHITECTURE "Unknown"
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/src/tools/ScratchIsland/ScratchIsland.vcxproj b/src/tools/ScratchIsland/ScratchIsland.vcxproj
new file mode 100644
index 000000000..d155853ab
--- /dev/null
+++ b/src/tools/ScratchIsland/ScratchIsland.vcxproj
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" />
+
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{23a1f736-cd19-4196-980f-84bcd50cf783}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>ScratchIsland</RootNamespace>
+ <ProjectName>ScratchIsland</ProjectName>
+ <TargetName>ScratchIsland</TargetName>
+ <ConfigurationType>Application</ConfigurationType>
+ <OpenConsoleUniversalApp>false</OpenConsoleUniversalApp>
+ <ApplicationType>Windows Store</ApplicationType>
+ <WindowsStoreApp>true</WindowsStoreApp>
+ <WindowsAppContainer>false</WindowsAppContainer>
+ <!-- IMPORTANT! Xaml Islands only works on >= 17709 -->
+ <!-- IMPORTANT! cppwinrt.pre.props specifies 17134 -->
+ <WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
+ <!-- DON'T REDIRECT OUR OUTPUT -->
+ <NoOutputRedirection>true</NoOutputRedirection>
+ </PropertyGroup>
+
+ <Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
+ <Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
+
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <SDLCheck>true</SDLCheck>
+ </ClCompile>
+ </ItemDefinitionGroup>
+
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>$(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
+ </ClCompile>
+ <ClCompile>
+ <!-- Manually include the generated TerminalCore header's path, because
+ adding a project reference will confuse msbuild, because TerminalCore
+ isn't a dll, it's a lib, and cppwinrt won't include a lib's header -->
+ <AdditionalIncludeDirectories>"$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <PropertyGroup>
+ <GenerateManifest>true</GenerateManifest>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <!-- Source Files -->
+ <ItemGroup>
+ <ClInclude Include="pch.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="AppHost.h" />
+ <ClInclude Include="BaseWindow.h" />
+ <ClInclude Include="IslandWindow.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="pch.cpp">
+ <PrecompiledHeader>Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="AppHost.cpp" />
+ <ClCompile Include="IslandWindow.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ScratchIsland.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <Manifest Include="ScratchIsland.manifest" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <!-- Dependencies -->
+ <ItemGroup>
+ <!-- Even though we do have proper recursive dependencies, we want to keep some of these here
+ so that the AppX Manifest contains their activatable classes. -->
+ <!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalSettings\TerminalSettings.vcxproj" /> -->
+ <!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalControl\TerminalControl.vcxproj" /> -->
+ <!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalConnection\TerminalConnection.vcxproj" /> -->
+
+ <!-- <ProjectReference Include="$(OpenConsoleDir)src\cascadia\TerminalApp\TerminalApp.vcxproj" /> -->
+
+ <ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
+ </ItemGroup>
+
+ <!--
+ This ItemGroup and the Globals PropertyGroup below it are required in order
+ to enable F5 debugging for the unpackaged application
+ -->
+ <ItemGroup>
+ <PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_general.xml" />
+ <PropertyPageSchema Include="$(VCTargetsPath)$(LangID)\debugger_local_windows.xml" />
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+ </PropertyGroup>
+
+ <Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
+
+ <Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
+ <Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
+ <Import Project="..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets" Condition="Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" />
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+ <PropertyGroup>
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+ </PropertyGroup>
+ <Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets'))" />
+ <Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.props'))" />
+ <Error Condition="!Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets'))" />
+ <Error Condition="!Exists('..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.VCRTForwarders.140.1.0.1-rc\build\native\Microsoft.VCRTForwarders.140.targets'))" />
+ <Error Condition="!Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets'))" />
+ </Target>
+
+ <!-- Override GetPackagingOutputs to roll up all our dependencies.
+ This ensures that when the WAP packaging project asks what files go into
+ the package, we tell it.
+ This is a heavily stripped version of the one in Microsoft.*.AppxPackage.targets.
+ -->
+ <PropertyGroup>
+ <_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true</_ContinueOnError>
+ <_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false</_ContinueOnError>
+ </PropertyGroup>
+ <Target Name="GetPackagingOutputs" Returns="@(PackagingOutputs)">
+ <MSBuild
+ Projects="@(ProjectReferenceWithConfiguration)"
+ Targets="GetPackagingOutputs"
+ BuildInParallel="$(BuildInParallel)"
+ Properties="%(ProjectReferenceWithConfiguration.SetConfiguration); %(ProjectReferenceWithConfiguration.SetPlatform)"
+ Condition="'@(ProjectReferenceWithConfiguration)' != ''
+ and '%(ProjectReferenceWithConfiguration.BuildReference)' == 'true'
+ and '%(ProjectReferenceWithConfiguration.ReferenceOutputAssembly)' == 'true'"
+ ContinueOnError="$(_ContinueOnError)">
+ <Output TaskParameter="TargetOutputs" ItemName="_PackagingOutputsFromOtherProjects"/>
+ </MSBuild>
+
+ <ItemGroup>
+ <PackagingOutputs Include="@(_PackagingOutputsFromOtherProjects)" />
+ </ItemGroup>
+
+ <!-- **BEGIN VC LIBS HACK** -->
+ <PropertyGroup>
+ <ReasonablePlatform Condition="'$(Platform)'=='Win32'">x86</ReasonablePlatform>
+ <ReasonablePlatform Condition="'$(ReasonablePlatform)'==''">$(Platform)</ReasonablePlatform>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(ScratchIslandOfficialBuild)'=='true'">
+ <!-- Add all the CRT libs as content; these must be inside a Target as they are wildcards. -->
+ <_OpenConsoleVCLibToCopy Include="$(VCToolsRedistInstallDir)\$(ReasonablePlatform)\Microsoft.VC142.CRT\*.dll" />
+
+ <PackagingOutputs Include="@(_OpenConsoleVCLibToCopy)">
+ <ProjectName>$(ProjectName)</ProjectName>
+ <OutputGroup>BuiltProjectOutputGroup</OutputGroup>
+ <TargetPath>%(Filename)%(Extension)</TargetPath>
+ </PackagingOutputs>
+ </ItemGroup>
+ <!-- **END VC LIBS HACK** -->
+ </Target>
+
+ <Import Project="$(OpenConsoleDir)\build\rules\GenerateSxsManifestsFromWinmds.targets" />
+ <Import Project="..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets" Condition="Exists('..\..\..\packages\Terminal.ThemeHelpers.0.2.200324001\build\native\Terminal.ThemeHelpers.targets')" />
+</Project>
+
diff --git a/src/tools/ScratchIsland/main.cpp b/src/tools/ScratchIsland/main.cpp
new file mode 100644
index 000000000..777e3ee9e
--- /dev/null
+++ b/src/tools/ScratchIsland/main.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+#include "AppHost.h"
+#include "resource.h"
+
+using namespace winrt;
+using namespace winrt::Windows::UI;
+using namespace winrt::Windows::UI::Composition;
+using namespace winrt::Windows::UI::Xaml::Hosting;
+using namespace winrt::Windows::Foundation::Numerics;
+
+int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
+{
+ // If Terminal is spawned by a shortcut that requests that it run in a new process group
+ // while attached to a console session, that request is nonsense. That request will, however,
+ // cause WT to start with Ctrl-C disabled. This wouldn't matter, because it's a Windows-subsystem
+ // application. Unfortunately, that state is heritable. In short, if you start WT using cmd in
+ // a weird way, ^C stops working _inside_ the terminal. Mad.
+ SetConsoleCtrlHandler(NULL, FALSE);
+
+ // Make sure to call this so we get WM_POINTER messages.
+ EnableMouseInPointer(true);
+
+ // !!! LOAD BEARING !!!
+ // We must initialize the main thread as a single-threaded apartment before
+ // constructing any Xaml objects. Failing to do so will cause some issues
+ // in accessibility somewhere down the line when a UIAutomation object will
+ // be queried on the wrong thread at the wrong time.
+ // We used to initialize as STA only _after_ initializing the application
+ // host, which loaded the settings. The settings needed to be loaded in MTA
+ // because we were using the Windows.Storage APIs. Since we're no longer
+ // doing that, we can safely init as STA before any WinRT dispatches.
+ winrt::init_apartment(winrt::apartment_type::single_threaded);
+
+ // Create the AppHost object, which will create both the window and the
+ // Terminal App. This MUST BE constructed before the Xaml manager as TermApp
+ // provides an implementation of Windows.UI.Xaml.Application.
+ AppHost host;
+
+ // Initialize the xaml content. This must be called AFTER the
+ // WindowsXamlManager is initialized.
+ host.Initialize();
+
+ MSG message;
+
+ while (GetMessage(&message, nullptr, 0, 0))
+ {
+ TranslateMessage(&message);
+ DispatchMessage(&message);
+ }
+ return 0;
+}
diff --git a/src/tools/ScratchIsland/packages.config b/src/tools/ScratchIsland/packages.config
new file mode 100644
index 000000000..166f8861f
--- /dev/null
+++ b/src/tools/ScratchIsland/packages.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
+ <package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.0.0" targetFramework="native" />
+ <package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.200609001" targetFramework="native" />
+ <package id="Microsoft.VCRTForwarders.140" version="1.0.1-rc" targetFramework="native" />
+ <package id="Terminal.ThemeHelpers" version="0.2.200324001" targetFramework="native" />
+</packages>
\ No newline at end of file
diff --git a/src/tools/ScratchIsland/pch.cpp b/src/tools/ScratchIsland/pch.cpp
new file mode 100644
index 000000000..398a99f66
--- /dev/null
+++ b/src/tools/ScratchIsland/pch.cpp
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
diff --git a/src/tools/ScratchIsland/pch.h b/src/tools/ScratchIsland/pch.h
new file mode 100644
index 000000000..40be55842
--- /dev/null
+++ b/src/tools/ScratchIsland/pch.h
@@ -0,0 +1,74 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- pch.h
+
+Abstract:
+- Contains external headers to include in the precompile phase of console build process.
+- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
+--*/
+
+#pragma once
+
+// Ignore checked iterators warning from VC compiler.
+#define _SCL_SECURE_NO_WARNINGS
+
+// Block minwindef.h min/max macros to prevent <algorithm> conflict
+#define NOMINMAX
+
+#define WIN32_LEAN_AND_MEAN
+#include <unknwn.h>
+
+#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
+
+#include <windows.h>
+#include <UIAutomation.h>
+#include <cstdlib>
+#include <cstring>
+#include <shellscalingapi.h>
+#include <windowsx.h>
+
+// Manually include til after we include Windows.Foundation to give it winrt superpowers
+#define BLOCK_TIL
+#include "../inc/LibraryIncludes.h"
+
+// This is inexplicable, but for whatever reason, cppwinrt conflicts with the
+// SDK definition of this function, so the only fix is to undef it.
+// from WinBase.h
+// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
+#include <wil/cppwinrt.h>
+
+// Needed just for XamlIslands to work at all:
+#include <winrt/Windows.system.h>
+#include <winrt/Windows.Foundation.Collections.h>
+#include <winrt/Windows.UI.Xaml.Hosting.h>
+#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
+
+// Additional headers for various xaml features. We need:
+// * Controls for grid
+// * Media for ScaleTransform
+#include <winrt/Windows.UI.Xaml.Controls.h>
+#include <winrt/Windows.ui.xaml.media.h>
+#include <windows.ui.xaml.media.dxinterop.h>
+
+#include <wil/resource.h>
+#include <wil/win32_helpers.h>
+
+// Including TraceLogging essentials for the binary
+#include <TraceLoggingProvider.h>
+#include <winmeta.h>
+TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider);
+#include <telemetry\ProjectTelemetry.h>
+#include <TraceLoggingActivity.h>
+
+// For commandline argument processing
+#include <shellapi.h>
+#include <processenv.h>
+
+#include "til.h"
diff --git a/src/tools/ScratchIsland/resource.h b/src/tools/ScratchIsland/resource.h
new file mode 100644
index 000000000..011413eae
--- /dev/null
+++ b/src/tools/ScratchIsland/resource.h
@@ -0,0 +1,24 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by WindowsTerminal.rc
+//
+#define IDI_APPICON 101
+#define IDS_ERROR_DIALOG_TITLE 105
+#define IDS_HELP_DIALOG_TITLE 106
+#define IDS_ERROR_ARCHITECTURE_FORMAT 110
+#define IDS_X86_ARCHITECTURE 111
+#define IDS_AMD64_ARCHITECTURE 112
+#define IDS_ARM64_ARCHITECTURE 113
+#define IDS_ARM_ARCHITECTURE 114
+#define IDS_UNKNOWN_ARCHITECTURE 115
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 104
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
--
2.20.1.windows.1