From f33c69d8b4171ed43adc09113f2bd9975fdf4422 Mon Sep 17 00:00:00 2001 From: Mike Griese 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(); + + // 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: +// - +// Return Value: +// - +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(initialPosition.X); + proposedRect.top = gsl::narrow_cast(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(lParam); + *intersectWithMonitor = true; + // Continue the enumeration + return FALSE; + }, + reinterpret_cast(&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(ceil(initialSize.Width)), 1); + const short islandHeight = Utils::ClampToShortMax( + static_cast(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(proposedRect.left), + gsl::narrow(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: +// - +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 _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 + +template +class BaseWindow +{ +public: + virtual ~BaseWindow() = 0; + static T* GetThisFromHandle(HWND const window) noexcept + { + return reinterpret_cast(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(lparam); + T* that = static_cast(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(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(dpi) / static_cast(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: + // - + void UpdateTitle(std::wstring_view newTitle) + { + _title = newTitle; + PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast(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; + 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(this)); + + EnableNonClientDpiScaling(_window.get()); + _currentDpi = GetDpiForWindow(_window.get()); + + return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam); + }; +}; + +template +inline BaseWindow::~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: +// - +// Return Value: +// - +void IslandWindow::MakeWindow() noexcept +{ + WNDCLASS wc{}; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hInstance = reinterpret_cast(&__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: +// - +// Return Value: +// - +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: +// - +void IslandWindow::SetCreateCallback(std::function 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: +// - +void IslandWindow::_HandleCreateWindow(const WPARAM, const LPARAM lParam) noexcept +{ + // Get proposed window rect from create structure + CREATESTRUCTW* pcs = reinterpret_cast(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: +// - +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(); + 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(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(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: +// - +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: +// - +// Return Value: +// - +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: +// - +// Return Value: +// - +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: +// - +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: +// - +// 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: +// - +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(), + windowPos.top(), + windowPos.width(), + windowPos.height(), + 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: +// - +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: +// - +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: +// - +// Return Value: +// - +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 +{ +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 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); + +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 _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 @@ + + + + + + + + + + + + + + + + + + PerMonitorV2 + true + + + 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 @@ + + + + + + {23a1f736-cd19-4196-980f-84bcd50cf783} + Win32Proj + ScratchIsland + ScratchIsland + ScratchIsland + Application + false + Windows Store + true + false + + + 10.0.18362.0 + + true + + + + + + + + true + + + + + + $(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories); + + + + "$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";%(AdditionalIncludeDirectories); + + + gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies) + + + + true + true + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WindowsLocalDebugger + + + + + + + + + + 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}. + + + + + + + + + + + <_ContinueOnError Condition="'$(BuildingProject)' == 'true'">true + <_ContinueOnError Condition="'$(BuildingProject)' != 'true'">false + + + + + + + + + + + + + x86 + $(Platform) + + + + + <_OpenConsoleVCLibToCopy Include="$(VCToolsRedistInstallDir)\$(ReasonablePlatform)\Microsoft.VC142.CRT\*.dll" /> + + + $(ProjectName) + BuiltProjectOutputGroup + %(Filename)%(Extension) + + + + + + + + + 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 @@ + + + + + + + + \ 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 conflict +#define NOMINMAX + +#define WIN32_LEAN_AND_MEAN +#include + +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) + +#include +#include +#include +#include +#include +#include + +// 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 + +// Needed just for XamlIslands to work at all: +#include +#include +#include +#include + +// Additional headers for various xaml features. We need: +// * Controls for grid +// * Media for ScaleTransform +#include +#include +#include + +#include +#include + +// Including TraceLogging essentials for the binary +#include +#include +TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider); +#include +#include + +// For commandline argument processing +#include +#include + +#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