From dc6631355f668e85e1e8575709fb8b5ba9109052 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Mon, 26 Apr 2021 14:36:23 -0500 Subject: [PATCH] Make the window name `_quake` special (#9785) ## Summary of the Pull Request This PR adds some special behavior to the window named "\_quake". * When creating the quake window, it ignores "initialRows" and "initialCols" and opens on the top half of the monitor. - It uses `initialPosition` to determine which monitor this is * It cannot be moved * It can only be vertically resized on the bottom border. * It's always in focus mode. - We should probably have an issue tracking "Allow showing tabs in focus mode"? Maybe? - This one element is maybe the one I'm least attached to When renaming a window to "\_quake", it adopts all those behaviors as well. It does not exit focus mode when leaving QM, nor does it resize back. That seemed unnecessary. ## References * As spec'ed in #9274 * See also #8888 ## PR Checklist * [x] In the pursuit of #653 * [x] I work here * [ ] Tests added/passed * [ ] Requires documentation to be updated, but I'm not gonna do any of that till quake mode is totally done. ## Detailed Description of the Pull Request / Additional comments Note that this doesn't do things like: * dropdown * global hotkey summon * summon to the current monitor * summon to the current desktop I'm doing #653 _very_ piecemeal, to try and make the PRs less egregious. ## Validation Steps Performed * validated that center on launch still works * validated that QM works on different monitors based on `initialPosition` * validated entering/exiting QM behaves as expected ## TODO! * [ ] When snapping the quake window between desktops with win+shift+arrow, the window doesn't horizontally re-size to the new monitor dimensions. It should. --- .github/actions/spelling/expect/expect.txt | 6 + src/cascadia/TerminalApp/AppLogic.cpp | 18 ++- src/cascadia/TerminalApp/AppLogic.h | 2 + src/cascadia/TerminalApp/AppLogic.idl | 2 + src/cascadia/TerminalApp/TabManagement.cpp | 17 ++- src/cascadia/TerminalApp/TerminalPage.cpp | 34 +++++- src/cascadia/TerminalApp/TerminalPage.h | 4 + src/cascadia/TerminalApp/TerminalPage.idl | 2 + src/cascadia/WindowsTerminal/AppHost.cpp | 79 ++++++++---- src/cascadia/WindowsTerminal/AppHost.h | 3 + src/cascadia/WindowsTerminal/IslandWindow.cpp | 113 +++++++++++++++++- src/cascadia/WindowsTerminal/IslandWindow.h | 7 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 21 +++- src/inc/til/point.h | 13 ++ src/inc/til/size.h | 5 + 15 files changed, 284 insertions(+), 42 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 46907132d8e..bdad97dcf46 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1026,14 +1026,20 @@ hsl hstr hstring hsv +HTBOTTOMLEFT +HTBOTTOMRIGHT HTCAPTION HTCLIENT +HTLEFT htm HTMAXBUTTON HTMINBUTTON html HTMLTo +HTRIGHT HTTOP +HTTOPLEFT +HTTOPRIGHT hu hungapp HVP diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 4acd95f2510..3cc669badab 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -285,12 +285,21 @@ namespace winrt::TerminalApp::implementation // launched _fullscreen_, toggle fullscreen mode. This will make sure // that the window size is _first_ set up as something sensible, so // leaving fullscreen returns to a reasonable size. + // + // We know at the start, that the root TerminalPage definitely isn't + // in focus nor fullscreen mode. So "Toggle" here will always work + // to "enable". const auto launchMode = this->GetLaunchMode(); - if (launchMode == LaunchMode::FullscreenMode) + if (IsQuakeWindow()) + { + _root->ToggleFocusMode(); + } + else if (launchMode == LaunchMode::FullscreenMode) { _root->ToggleFullscreen(); } - else if (launchMode == LaunchMode::FocusMode || launchMode == LaunchMode::MaximizedFocusMode) + else if (launchMode == LaunchMode::FocusMode || + launchMode == LaunchMode::MaximizedFocusMode) { _root->ToggleFocusMode(); } @@ -1439,4 +1448,9 @@ namespace winrt::TerminalApp::implementation } } + bool AppLogic::IsQuakeWindow() const noexcept + { + return _root->IsQuakeWindow(); + } + } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index b35b7232159..54c1a206944 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -66,6 +66,7 @@ namespace winrt::TerminalApp::implementation void WindowName(const winrt::hstring& name); uint64_t WindowId(); void WindowId(const uint64_t& id); + bool IsQuakeWindow() const noexcept; Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi); bool CenterOnLaunch(); @@ -158,6 +159,7 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(SetTaskbarProgress, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, SetTaskbarProgress); FORWARDED_TYPED_EVENT(IdentifyWindowsRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IdentifyWindowsRequested); FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested); + FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged); #ifdef UNIT_TESTING friend class TerminalAppLocalTests::CommandlineTest; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 19647928ddc..6bbd2a5d1e4 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -52,6 +52,7 @@ namespace TerminalApp String WindowName; UInt64 WindowId; void RenameFailed(); + Boolean IsQuakeWindow(); Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); Boolean CenterOnLaunch { get; }; @@ -85,5 +86,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler RenameWindowRequested; + event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; } } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 7a19ceec1cf..f8739f1c82b 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -272,12 +272,17 @@ namespace winrt::TerminalApp::implementation (_tabs.Size() > 1) || _settings.GlobalSettings().AlwaysShowTabs()); - // collapse/show the tabs themselves - _tabView.Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed); - - // collapse/show the row that the tabs are in. - // NaN is the special value XAML uses for "Auto" sizing. - _tabRow.Height(isVisible ? NAN : 0); + if (_tabView) + { + // collapse/show the tabs themselves + _tabView.Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed); + } + if (_tabRow) + { + // collapse/show the row that the tabs are in. + // NaN is the special value XAML uses for "Auto" sizing. + _tabRow.Height(isVisible ? NAN : 0); + } } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8b4ec292a11..98818bcd61c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -42,6 +42,8 @@ namespace winrt using IInspectable = Windows::Foundation::IInspectable; } +static constexpr std::wstring_view QuakeWindowName{ L"_quake" }; + namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage() : @@ -2062,9 +2064,20 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::ToggleFocusMode() { - _isInFocusMode = !_isInFocusMode; - _UpdateTabView(); - _FocusModeChangedHandlers(*this, nullptr); + _SetFocusMode(!_isInFocusMode); + } + + void TerminalPage::_SetFocusMode(const bool inFocusMode) + { + // If we're the quake window, we must always be in focus mode. + // Prevent leaving focus mode here. + const bool newInFocusMode = inFocusMode || IsQuakeWindow(); + if (newInFocusMode != FocusMode()) + { + _isInFocusMode = newInFocusMode; + _UpdateTabView(); + _FocusModeChangedHandlers(*this, nullptr); + } } // Method Description: @@ -2610,6 +2623,7 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget TerminalPage::WindowName(const winrt::hstring& value) { + const bool oldIsQuakeMode = IsQuakeWindow(); const bool changed = _WindowName != value; if (changed) { @@ -2631,6 +2645,16 @@ namespace winrt::TerminalApp::implementation if (page->_startupState == StartupState::Initialized) { page->IdentifyWindow(); + + // If we're entering quake mode, or leaving it + if (IsQuakeWindow() != oldIsQuakeMode) + { + // If we're entering Quake Mode from ~Focus Mode, then this will enter Focus Mode + // If we're entering Quake Mode from Focus Mode, then this will do nothing + // If we're leaving Quake Mode (we're already in Focus Mode), then this will do nothing + _SetFocusMode(true); + _IsQuakeWindowChangedHandlers(*this, nullptr); + } } } } @@ -2773,4 +2797,8 @@ namespace winrt::TerminalApp::implementation } } + bool TerminalPage::IsQuakeWindow() const noexcept + { + return WindowName() == QuakeWindowName; + } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 9414a261572..060a45f99a6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -107,6 +107,7 @@ namespace winrt::TerminalApp::implementation void WindowId(const uint64_t& value); winrt::hstring WindowIdForDisplay() const noexcept; winrt::hstring WindowNameForDisplay() const noexcept; + bool IsQuakeWindow() const noexcept; WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); @@ -122,6 +123,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(Initialized, IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs); TYPED_EVENT(IdentifyWindowsRequested, IInspectable, IInspectable); TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs); + TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable); private: friend struct TerminalPageT; // for Xaml to bind events @@ -337,6 +339,8 @@ namespace winrt::TerminalApp::implementation void _UpdateTeachingTipTheme(winrt::Windows::UI::Xaml::FrameworkElement element); + void _SetFocusMode(const bool inFocusMode); + #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp #define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index bf665d91bb2..f1c1e55fc15 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -33,6 +33,7 @@ namespace TerminalApp String WindowNameForDisplay { get; }; String WindowIdForDisplay { get; }; void RenameFailed(); + Boolean IsQuakeWindow(); // We cannot use the default XAML APIs because we want to make sure // that there's only one application-global dialog visible at a time, @@ -54,5 +55,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SetTaskbarProgress; event Windows.Foundation.TypedEventHandler IdentifyWindowsRequested; event Windows.Foundation.TypedEventHandler RenameWindowRequested; + event Windows.Foundation.TypedEventHandler IsQuakeWindowChanged; } } diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 6082ea45977..b25e96ece22 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -57,6 +57,9 @@ AppHost::AppHost() noexcept : _window = std::make_unique(); } + // Update our own internal state tracking if we're in quake mode or not. + _IsQuakeWindowChanged(nullptr, nullptr); + // Tell the window to callback to us when it's about to handle a WM_CREATE auto pfn = std::bind(&AppHost::_HandleCreateWindow, this, @@ -251,6 +254,7 @@ void AppHost::Initialize() _logic.SetTaskbarProgress({ this, &AppHost::SetTaskbarProgress }); _logic.IdentifyWindowsRequested({ this, &AppHost::_IdentifyWindowsRequested }); _logic.RenameWindowRequested({ this, &AppHost::_RenameWindowRequested }); + _logic.IsQuakeWindowChanged({ this, &AppHost::_IsQuakeWindowChanged }); _window->UpdateTitle(_logic.Title()); @@ -379,39 +383,58 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, LaunchMode // 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 til::size nonClientSize = _window->GetTotalNonClientExclusiveSize(dpix); + adjustedWidth = islandWidth + nonClientSize.width(); + adjustedHeight = islandHeight + nonClientSize.height(); + + til::size dimensions{ Utils::ClampToShortMax(adjustedWidth, 1), + Utils::ClampToShortMax(adjustedHeight, 1) }; + + // Find nearest monitor for the position that we've actually settled on + HMONITOR hMonNearest = MonitorFromRect(&proposedRect, MONITOR_DEFAULTTONEAREST); + MONITORINFO nearestMonitorInfo; + nearestMonitorInfo.cbSize = sizeof(MONITORINFO); + // Get monitor dimensions: + GetMonitorInfo(hMonNearest, &nearestMonitorInfo); + const til::size desktopDimensions{ gsl::narrow(nearestMonitorInfo.rcWork.right - nearestMonitorInfo.rcWork.left), + gsl::narrow(nearestMonitorInfo.rcWork.bottom - nearestMonitorInfo.rcWork.top) }; - const COORD dimensions{ Utils::ClampToShortMax(adjustedWidth, 1), - Utils::ClampToShortMax(adjustedHeight, 1) }; + til::point origin{ (proposedRect.left), + (proposedRect.top) }; - if (centerOnLaunch) + if (_logic.IsQuakeWindow()) + { + // If we just use rcWork by itself, we'll fail to account for the invisible + // space reserved for the resize handles. So retrieve that size here. + const til::size ncSize{ _window->GetTotalNonClientExclusiveSize(dpix) }; + const til::size availableSpace = desktopDimensions + nonClientSize; + + origin = til::point{ + ::base::ClampSub(nearestMonitorInfo.rcWork.left, (nonClientSize.width() / 2)), + (nearestMonitorInfo.rcWork.top) + }; + dimensions = til::size{ + availableSpace.width(), + availableSpace.height() / 2 + }; + launchMode = LaunchMode::FocusMode; + } + else if (centerOnLaunch) { - // Find nearest monitor for the position that we've actually settled on - HMONITOR hMonNearest = MonitorFromRect(&proposedRect, MONITOR_DEFAULTTONEAREST); - MONITORINFO nearestMonitorInfo; - nearestMonitorInfo.cbSize = sizeof(MONITORINFO); - // Get monitor dimensions: - GetMonitorInfo(hMonNearest, &nearestMonitorInfo); - const COORD desktopDimensions{ gsl::narrow(nearestMonitorInfo.rcWork.right - nearestMonitorInfo.rcWork.left), - gsl::narrow(nearestMonitorInfo.rcWork.bottom - nearestMonitorInfo.rcWork.top) }; // Move our proposed location into the center of that specific monitor. - proposedRect.left = nearestMonitorInfo.rcWork.left + - ((desktopDimensions.X / 2) - (dimensions.X / 2)); - proposedRect.top = nearestMonitorInfo.rcWork.top + - ((desktopDimensions.Y / 2) - (dimensions.Y / 2)); + origin = til::point{ + (nearestMonitorInfo.rcWork.left + ((desktopDimensions.width() / 2) - (dimensions.width() / 2))), + (nearestMonitorInfo.rcWork.top + ((desktopDimensions.height() / 2) - (dimensions.height() / 2))) + }; } - const COORD origin{ gsl::narrow(proposedRect.left), - gsl::narrow(proposedRect.top) }; - const auto newPos = Viewport::FromDimensions(origin, dimensions); + const til::rectangle newRect{ origin, dimensions }; bool succeeded = SetWindowPos(hwnd, nullptr, - newPos.Left(), - newPos.Top(), - newPos.Width(), - newPos.Height(), + newRect.left(), + newRect.top(), + newRect.width(), + newRect.height(), SWP_NOACTIVATE | SWP_NOZORDER); // Refresh the dpi of HWND because the dpi where the window will launch may be different @@ -674,3 +697,9 @@ winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Fou } } } + +void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable&, + const winrt::Windows::Foundation::IInspectable&) +{ + _window->IsQuakeWindow(_logic.IsQuakeWindow()); +} diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index a3a22603adb..3c32af7190a 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -59,4 +59,7 @@ class AppHost const winrt::TerminalApp::RenameWindowRequestedArgs args); GUID _CurrentDesktopGuid(); + + void _IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::Foundation::IInspectable& args); }; diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index bd739a8f625..9a7d681908c 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -170,7 +170,7 @@ 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; + return false; } LPRECT winRect = reinterpret_cast(lParam); @@ -182,8 +182,9 @@ LRESULT IslandWindow::_OnSizing(const WPARAM wParam, const LPARAM lParam) // 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); + // If this fails, we'll use the default of 96. I think it can only fail for + // bad parameters, which we won't have, so no big deal. + LOG_IF_FAILED(GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy)); const auto widthScale = base::ClampedNumeric(dpix) / USER_DEFAULT_SCREEN_DPI; const long minWidthScaled = minimumWidth * widthScale; @@ -195,6 +196,16 @@ LRESULT IslandWindow::_OnSizing(const WPARAM wParam, const LPARAM lParam) auto clientHeight = winRect->bottom - winRect->top - nonClientSize.cy; + // If we're the quake window, prevent resizing on all sides except the + // bottom. This also applies to resizing with the Alt+Space menu + if (IsQuakeWindow() && wParam != WMSZ_BOTTOM) + { + // Stuff our current window size into the lParam, and return true. This + // will tell User32 to use our current dimensions to resize to. + ::GetWindowRect(_window.get(), winRect); + return true; + } + if (wParam != WMSZ_TOP && wParam != WMSZ_BOTTOM) { // If user has dragged anything but the top or bottom border (so e.g. left border, @@ -244,7 +255,31 @@ LRESULT IslandWindow::_OnSizing(const WPARAM wParam, const LPARAM lParam) break; } - return TRUE; + return true; +} + +// Method Description: +// - Handle the WM_MOVING message +// - If we're the quake window, then we don't want to be able to be moved. +// Immediately return our current window position, which will prevent us from +// being moved at all. +// Arguments: +// - lParam: a LPRECT with the proposed window position, that should be filled +// with the resultant position. +// Return Value: +// - true iff we handled this message. +LRESULT IslandWindow::_OnMoving(const WPARAM /*wParam*/, const LPARAM lParam) +{ + LPRECT winRect = reinterpret_cast(lParam); + // If we're the quake window, prevent moving the window + if (IsQuakeWindow()) + { + // Stuff our current window into the lParam, and return true. This + // will tell User32 to use our current position to move to. + ::GetWindowRect(_window.get(), winRect); + return true; + } + return false; } void IslandWindow::Initialize() @@ -405,6 +440,10 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize { return _OnSizing(wparam, lparam); } + case WM_MOVING: + { + return _OnMoving(wparam, lparam); + } case WM_CLOSE: { // If the user wants to close the app by clicking 'X' button, @@ -749,6 +788,7 @@ void IslandWindow::_SetIsBorderless(const bool borderlessEnabled) // 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(), @@ -931,5 +971,70 @@ winrt::fire_and_forget IslandWindow::SummonWindow() LOG_IF_WIN32_BOOL_FALSE(ShowWindow(_window.get(), SW_SHOW)); } +bool IslandWindow::IsQuakeWindow() const noexcept +{ + return _isQuakeWindow; +} + +void IslandWindow::IsQuakeWindow(bool isQuakeWindow) noexcept +{ + if (_isQuakeWindow != isQuakeWindow) + { + _isQuakeWindow = isQuakeWindow; + // Don't enter quake mode if we don't have an HWND yet + if (IsQuakeWindow() && _window) + { + _enterQuakeMode(); + } + } +} + +void IslandWindow::_enterQuakeMode() +{ + if (!_window) + { + return; + } + + RECT windowRect = GetWindowRect(); + HMONITOR hmon = MonitorFromRect(&windowRect, MONITOR_DEFAULTTONEAREST); + MONITORINFO nearestMonitorInfo; + + UINT dpix = USER_DEFAULT_SCREEN_DPI; + UINT dpiy = USER_DEFAULT_SCREEN_DPI; + // If this fails, we'll use the default of 96. I think it can only fail for + // bad parameters, which we won't have, so no big deal. + LOG_IF_FAILED(GetDpiForMonitor(hmon, MDT_EFFECTIVE_DPI, &dpix, &dpiy)); + + nearestMonitorInfo.cbSize = sizeof(MONITORINFO); + // Get monitor dimensions: + GetMonitorInfo(hmon, &nearestMonitorInfo); + const til::size desktopDimensions{ (nearestMonitorInfo.rcWork.right - nearestMonitorInfo.rcWork.left), + (nearestMonitorInfo.rcWork.bottom - nearestMonitorInfo.rcWork.top) }; + + // If we just use rcWork by itself, we'll fail to account for the invisible + // space reserved for the resize handles. So retrieve that size here. + const til::size ncSize{ GetTotalNonClientExclusiveSize(dpix) }; + const til::size availableSpace = desktopDimensions + ncSize; + + const til::point origin{ + ::base::ClampSub(nearestMonitorInfo.rcWork.left, (ncSize.width() / 2)), + (nearestMonitorInfo.rcWork.top) + }; + const til::size dimensions{ + availableSpace.width(), + availableSpace.height() / 2 + }; + + const til::rectangle newRect{ origin, dimensions }; + SetWindowPos(GetHandle(), + HWND_TOP, + newRect.left(), + newRect.top(), + newRect.width(), + newRect.height(), + SWP_SHOWWINDOW | SWP_FRAMECHANGED | SWP_NOACTIVATE); +} + DEFINE_EVENT(IslandWindow, DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); DEFINE_EVENT(IslandWindow, WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 6ecf2c11525..d20bd115556 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -40,7 +40,8 @@ class IslandWindow : winrt::fire_and_forget SummonWindow(); -#pragma endregion + bool IsQuakeWindow() const noexcept; + void IsQuakeWindow(bool isQuakeWindow) noexcept; DECLARE_EVENT(DragRegionClicked, _DragRegionClickedHandlers, winrt::delegate<>); DECLARE_EVENT(WindowCloseButtonClicked, _windowCloseButtonClickedHandler, winrt::delegate<>); @@ -66,6 +67,7 @@ class IslandWindow : void _HandleCreateWindow(const WPARAM wParam, const LPARAM lParam) noexcept; [[nodiscard]] LRESULT _OnSizing(const WPARAM wParam, const LPARAM lParam); + [[nodiscard]] LRESULT _OnMoving(const WPARAM wParam, const LPARAM lParam); bool _borderless{ false }; bool _alwaysOnTop{ false }; @@ -87,6 +89,9 @@ class IslandWindow : void _OnGetMinMaxInfo(const WPARAM wParam, const LPARAM lParam); long _calculateTotalSize(const bool isWidth, const long clientSize, const long nonClientSize); + bool _isQuakeWindow{ false }; + void _enterQuakeMode(); + private: // This minimum width allows for width the tabs fit static constexpr long minimumWidth = 460L; diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index a5d5c4b5672..eb4e6400ed4 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -532,6 +532,23 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept const auto originalRet = DefWindowProc(_window.get(), WM_NCHITTEST, 0, lParam); if (originalRet != HTCLIENT) { + // If we're the quake window, suppress resizing on any side except the + // bottom. I don't believe that this actually works on the top. That's + // handled below. + if (IsQuakeWindow()) + { + switch (originalRet) + { + case HTBOTTOMRIGHT: + case HTRIGHT: + case HTTOPRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTLEFT: + case HTBOTTOMLEFT: + return HTCLIENT; + } + } return originalRet; } @@ -551,7 +568,9 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept // the top of the drag bar is used to resize the window if (!_isMaximized && isOnResizeBorder) { - return HTTOP; + // However, if we're the quake window, then just return HTCAPTION so we + // don't get a resize handle on the top. + return IsQuakeWindow() ? HTCAPTION : HTTOP; } return HTCAPTION; diff --git a/src/inc/til/point.h b/src/inc/til/point.h index 87c802ebb27..366ba18077b 100644 --- a/src/inc/til/point.h +++ b/src/inc/til/point.h @@ -25,6 +25,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" point(static_cast(x), static_cast(y)) { } + constexpr point(ptrdiff_t width, int height) noexcept : + point(width, static_cast(height)) + { + } + constexpr point(int width, ptrdiff_t height) noexcept : + point(static_cast(width), height) + { + } #endif point(size_t x, size_t y) @@ -32,6 +40,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(x).AssignIfValid(&_x)); THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(y).AssignIfValid(&_y)); } + point(long x, long y) + { + THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(x).AssignIfValid(&_x)); + THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(y).AssignIfValid(&_y)); + } constexpr point(ptrdiff_t x, ptrdiff_t y) noexcept : _x(x), diff --git a/src/inc/til/size.h b/src/inc/til/size.h index 383cb13d92e..907089ef690 100644 --- a/src/inc/til/size.h +++ b/src/inc/til/size.h @@ -40,6 +40,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width).AssignIfValid(&_width)); THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height).AssignIfValid(&_height)); } + size(long width, long height) + { + THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width).AssignIfValid(&_width)); + THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height).AssignIfValid(&_height)); + } constexpr size(ptrdiff_t width, ptrdiff_t height) noexcept : _width(width),