-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove Monarch/Peasant & Make UI single-threaded #18215
Conversation
b39c5a7
to
7853853
Compare
@@ -170,7 +170,6 @@ namespace winrt::TerminalApp::implementation | |||
|
|||
const auto canDragDrop = CanDragDrop(); | |||
|
|||
_tabRow.PointerMoved({ get_weak(), &TerminalPage::_RestorePointerCursorHandler }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved into TerminalControl
as a different workaround is needed for this functionality.
{ | ||
auto weakThis{ get_weak() }; | ||
co_await wil::resume_foreground(Dispatcher()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No multi-threading = No problem. (Suppress whitespace in the diff.)
<CurrentXamlPackage Include="@(AppxPackageRegistration)" | ||
Condition="'%(AppxPackageRegistration.Architecture)'=='$(Platform)' AND | ||
$([System.String]::new('%(AppxPackageRegistration.Filename)').StartsWith('Microsoft.UI.Xaml'))" /> | ||
<CurrentXamlPackage Include="@(AppxPackageRegistration)" Condition="'%(AppxPackageRegistration.Architecture)'=='$(Platform)' AND
 $([System.String]::new('%(AppxPackageRegistration.Filename)').StartsWith('Microsoft.UI.Xaml'))" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VS mangled this :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
86/154! It's easy to review deletions ;)
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
139/154
auto isCurrentlyDark = Theme::IsSystemInDarkTheme(); | ||
if (isCurrentlyDark != _currentSystemThemeIsDark) | ||
{ | ||
_currentSystemThemeIsDark = isCurrentlyDark; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 DH - Guessing this moved to the Message Window; confirm
// On the foreground thread: | ||
co_await wil::resume_foreground(_rootGrid.Dispatcher()); | ||
_summonWindowRoutineBody(args); | ||
_summonWindowRoutineBody(std::move(args)); | ||
} | ||
|
||
// Method Description: | ||
// - As above. | ||
// BODGY: ARM64 BUILD FAILED WITH fatal error C1001: Internal compiler error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now that we got a new compiler, is this still valid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not even a coroutine any longer, so we can probably dispense with the separation entirely...
ScopedResourceLoader loader{ L"TerminalApp/ContextMenu" }; | ||
const auto appNameLoc = loader.GetLocalizedString(L"AppName"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 DH - Loading up somebody else's resources - probably moved to Emperor
HWND GetMainWindow() const noexcept; | ||
AppHost* GetWindowById(uint64_t id) const noexcept; | ||
AppHost* GetWindowByName(std::wstring_view name) const noexcept; | ||
void CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: why not const&
?
bool _registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept; | ||
void _unregisterHotKey(const int index) noexcept; | ||
safe_void_coroutine _setupGlobalHotkeys(); | ||
enum class TriBool : uint8_t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HAHAHAHAHAHAHAHAHAHAH
hahahahahaha
hahah
std::optional<bool>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::optional<bool>
is two bools in a trench coat, so... meh.
...but yeah I could do that.
const auto absoluteWindowOrigin = CoreWindow::GetForCurrentThread().Bounds(); | ||
// Get the offset (margin + tabs, etc..) of the control within the window | ||
const auto controlOrigin = TransformToVisual(nullptr).TransformPoint({}); | ||
const auto inverseScale = 1.0f / static_cast<float>(XamlRoot().RasterizationScale()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these don't seem related to regicide
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CoreWindow::GetForCurrentThread().Bounds()
won't work anymore, so this had to be rewritten. I also chose to use the technically correct way to get the rasterization scale (we do it incorrectly everywhere, according to the docs).
This comment has been minimized.
This comment has been minimized.
|
||
if (const auto manager = *s_desktopManager.lock_shared()) | ||
{ | ||
return manager; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this will still return a dead RPC object if we had one stored, right? there's no way to communicate failure and invalidate it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it by killing explorer.exe but it still worked for me. Does the client perhaps reconnect automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Classic Leonard code - linear, straightforward, and readable. Another tour-de-force.
const wil::unique_environstrings_ptr strings{ GetEnvironmentStringsW() }; | ||
const auto beg = strings.get(); | ||
auto end = beg; | ||
wil::unique_mutex mutex{ CreateMutexW(nullptr, TRUE, className) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw unique_mutex_nothrow
et al have .try_create
which returns false if it fails and populates an out param boolean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
116/164, but the baby's awake
Exit(); | ||
{ | ||
MSG msg = {}; | ||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 did this get moved somewhere into like, main()
// us. On Windows 10, that CoreWindow will show up as a visible | ||
// window on the taskbar, unless we hide it manually. So, go get it | ||
// and do the SW_HIDE thing on it. | ||
if (const auto& coreWindow{ winrt::Windows::UI::Core::CoreWindow::GetForCurrentThread() }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this sounds dumb - but we tested this on a Win10 VM right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did test it and from what I can tell it seems to work.
Better yet, I just tested #16183 and that's fixed now.
@@ -5,16 +5,18 @@ | |||
********************************************************/ | |||
#include "pch.h" | |||
#include "NonClientIslandWindow.h" | |||
|
|||
#include <dwmapi.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unrelated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to the removal of some other files, this .cpp file now lacks the necessary imports for the DWM API without this.
break; | ||
} | ||
case WM_CONTEXTMENU: | ||
if (const auto menu = CreatePopupMenu()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is definitely grosser to read now. I kinda liked the idea that the logic for the tray icon was all encapsulated into its own file. Even if it's not a separate class, just anything that's not deep in a switch/case...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also encapsulate it into its own function. That seems like a good trade-off to me. It's better than using a separate class IMO and sharing a HWND and any other implicit state across class boundaries.
|
||
if (const auto title = logic.Title(); !title.empty()) | ||
{ | ||
fmt::format_to(std::back_inserter(displayText), L": {}", title); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... like look here - this line starts with 32 columns of whitespace 😨
@@ -496,12 +480,6 @@ namespace winrt::TerminalApp::implementation | |||
|
|||
void _TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex); | |||
|
|||
bool _shouldMouseVanish{ false }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝where'd this go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➡️TermControl.cpp
pointerPosition.Y - windowOrigin.Y - tabPosition.Y, | ||
}; | ||
const auto inverseScale = 1.0f / static_cast<float>(eventTab.XamlRoot().RasterizationScale()); | ||
POINT cursorPos; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 IIRC the old one gets the offest within the tab, right? so that we create the new window s.t. the tab itself doesn't seem to move from where we dropped it.
Now we're stashing the relative position within the old window of the mouse pointer. I'd guess for a tab farther to the right in the row, that causes us to spawn the new window to the left of where we'd expect...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell, the new logic works correct on my 150% scale display.
// - <none> | ||
// Return Value: | ||
// - true iff we should exit the application before even starting the window | ||
bool TerminalWindow::ShouldExitEarly() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 check that we still handle this scenario
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is handled by the following code in WindowEmperor.cpp:
// If we created no windows, e.g. because the args are "/?" we can just exit now.
_postQuitMessageIfNeeded();
But thank you for bringing this up, because I just noticed that wtd /?
does not work.
// Once the loop exits, the app exits, and the message box will be closed. | ||
_messageBoxCount += 1; | ||
const auto decrement = wil::scope_exit([hwnd = _window.get()]() noexcept { | ||
PostMessageW(hwnd, WM_MESSAGE_BOX_CLOSED, 0, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW the reason I went with PostMessageW
instead of resume_foreground
is because I think it's crucial that this does not fail so that the ref-count does not go out of sync. I think PostMessageW
is fundamentally more robust in that regard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
161/164, but alas, the burrito screams
sometimes when I get that deep into a PR as big as this, I just assume the last couple files must be good too
} | ||
|
||
void WindowEmperor::WaitForWindows() | ||
void WindowEmperor::CreateNewWindow(winrt::TerminalApp::WindowRequestedArgs args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔖
{ | ||
if (const auto interop = coreWindow.try_as<ICoreWindowInterop>()) | ||
{ | ||
HWND coreHandle = nullptr; | ||
if (SUCCEEDED(interop->get_WindowHandle(&coreHandle)) && coreHandle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do this before we start spawning windows (and CLI processes?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could do this at any point. Do you have something in mind?
else | ||
{ | ||
winrt::TerminalApp::WindowRequestedArgs request{ windowId, std::move(args) }; | ||
request.WindowName(std::move(windowName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Entirely sytlistically, it's a little weird that this isn't like window = _mostRecentWindowCurrentDesktop(); break;
. It's the only early return in what is otherwise a very methodical function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's unfortunately necessary because _mostRecentWindowCurrentDesktop
is a coroutine. We could of course turn this function into a coroutine as well and then do
window = co_await _mostRecentWindowCurrentDesktop();
break;
It's mostly a coincidence that I drew the boundary like that.
} | ||
} | ||
else if (!args.WindowName.empty()) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝: you know, the code is cleaner if it's one for
loop with the if(args.WindowID) / else if (!args.WindowName.empty())
statement on the inside, but it's less efficient then. 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, it'd would not really be a big difference (branch prediction + it usually never loops >10 times anyway). This is also not really intentional, and I'll just move the if condition inside.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, you know what, I'll leave this as-is for now, because it makes the window = _mostRecentWindow();
branch at the end easier to express. If anyone ever happens to change this code I would not mind at all.
wchar_t guidStr[39]; | ||
guidStr[0] = L'{'; | ||
memcpy(&guidStr[1], name.data() + 7, 36 * sizeof(wchar_t)); | ||
guidStr[37] = L'}'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
of course it can't be, that would be too easy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my defense, when I wrote that GuidFromPlainString
didn't exist yet. 😅
It's quite ugly code indeed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template<typename T> | ||
class BaseWindow | ||
{ | ||
public: | ||
static constexpr UINT CM_UPDATE_TITLE = WM_USER + 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feels weird that this guy isnt with the others in WindowEmperor. ik its lt a message "for the emperor", just threw me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WM_USER
messages are technically per-window-class from what I know, so I felt like they should be defined in their respective window classes as well.
QueryPerformanceFrequency(&freq); | ||
QueryPerformanceCounter(&counter); | ||
|
||
const auto period = freq.QuadPart * 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh intriguing, all windows will have the same frame xolor now cool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, woops, I wanted to back out this change and submit it as a separate PR.
And yeah, that, and fixing floating imprecision once WT has run for a long time, were my intentions with this.
Thanks for linking all the issues! I tested them and all but the last one seems fixed. |
Fixes * Cursor vanishing, because ```cpp CoreWindow::GetForCurrentThread().PointerCursor() ``` returned a non-null, but still invisible cursor. * Alt/F7 not dispatching to newly created windows, because the `WM_ACTIVATE` message now arrives before the `AppHost` is initialized. This caused the messages to be delivered to old windows. * Windows Terminal blocking expedited shutdown, because we return `FALSE` on non-`ENDSESSION_CLOSEAPP` messages. Closes #18331 Closes #18335 ## Validation Steps Performed * Cursor still doesn't really vanish for me in the first place ✅ * Alt/F7 work in new windows without triggering old ones ✅
As before, a minor refactor:
it into and deduplicating its functionality with
WindowEmperor
.a single instance), I wrote single-instance code with a NT mutex
and by yeeting data across processes with
WM_COPYDATA
.way faster. The more I tried to solve them the deeper I had to dig,
because you can't just put a mutex around
CascadiaSettings
.I then tried to seeif WinUI can run multiple windows on a single
thread and, as it turns out, it can.
So, I removed the multi- from the window threading.
So, to finish it up, I had to clean up the entire eventing system
around
WindowEmperor
, cleaned up all the coroutines,and cleaned up all the callbacks.
Closes #16183
Closes #16221
Closes #16487
Closes #16532
Closes #16733
Closes #16755
Closes #17015
Closes #17360
Closes #17420
Closes #17457
Closes #17799
Closes #17976
Closes #18057
Closes #18084
Closes #18169
Closes #18176
Closes #18191
Validation Steps Performed