terminal/src/cascadia/Remoting/Peasant.cpp
Schuyler Rosefield 75e2b5fae7
Persist window layout cont. save multiple windows (#11083)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? -->
## Summary of the Pull Request
Continuation of https://github.com/microsoft/terminal/pull/10972 to handle multiple windows, requires that to be merged first. 

<!-- Other than the issue solved, is this relevant to any other issues/existing PRs? --> 
## References

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Also closes #766
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [ ] Tests added/passed
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
* [x] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments
Rough changelog:
Normally saving is triggered to occur every 30s, or sooner if a window is created/closed. The existing behavior of saving on last close is maintained to bypass that throttling. The automatic saving allows for crash recovery. Additionally all window layouts will be saved upon taking the `quit` action.

For loading we will check if we are the first window, that there are any saved layouts, and if the setting is enabled, and then depending on if we were given command line args or startup actions.

- create a new window for each saved layout, or
- take the first layout for our self and then a new window for each other layout.

This also saves the layout when the quit action is taken.

Misc changes
- A -s,--saved argument was added to the command line to facilitate opening all of the windows with the right settings. This also means that while a terminal session is running you can do wt -s idx to open a copy of window idx. There isn't a stable ordering of which idx each window gets saved as (it is whatever the iteration order of _peasants is), so it is just a cute hack for now.
- All position calculation has been moved up to AppHost this does mean we need to awkwardly pass around positions in a couple of unexpected places, but no solution was perfect.
- Renamed "Open tabs from a previous session" to "Open windows from a previous session". (not reflected in video below)
- Now save runtime tab color and window names
- Only enabled for non-elevated windows
- Add some change tracking to ApplicationState

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
![output](https://user-images.githubusercontent.com/6185249/131163473-d649d204-a589-41ad-b9d9-c4c0528cb684.gif)
2021-09-27 21:18:39 +00:00

314 lines
11 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Peasant.h"
#include "CommandlineArgs.h"
#include "SummonWindowBehavior.h"
#include "GetWindowLayoutArgs.h"
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
using namespace winrt;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Windows::Foundation;
using namespace ::Microsoft::Console;
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
Peasant::Peasant() :
_ourPID{ GetCurrentProcessId() }
{
}
// This constructor is intended to be used in unit tests,
// but we need to make it public in order to use make_self
// in the tests. It's not exposed through the idl though
// so it's not _truly_ fully public which should be acceptable.
Peasant::Peasant(const uint64_t testPID) :
_ourPID{ testPID }
{
}
void Peasant::AssignID(uint64_t id)
{
_id = id;
}
uint64_t Peasant::GetID()
{
return _id;
}
uint64_t Peasant::GetPID()
{
return _ourPID;
}
bool Peasant::ExecuteCommandline(const Remoting::CommandlineArgs& args)
{
// If this is the first set of args we were ever told about, stash them
// away. We'll need to get at them later, when we setup the startup
// actions for the window.
if (_initialArgs == nullptr)
{
_initialArgs = args;
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_ExecuteCommandline",
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
TraceLoggingWideString(args.CurrentDirectory().c_str(), "directory", "the provided cwd"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
// Raise an event with these args. The AppHost will listen for this
// event to know when to take these args and dispatch them to a
// currently-running window.
_ExecuteCommandlineRequestedHandlers(*this, args);
return true;
}
Remoting::CommandlineArgs Peasant::InitialArgs()
{
return _initialArgs;
}
void Peasant::ActivateWindow(const Remoting::WindowActivatedArgs& args)
{
// TODO: projects/5 - somehow, pass an identifier for the current
// desktop into this method. The Peasant shouldn't need to be able to
// figure it out, but it will need to report it to the monarch.
// Store these new args as our last activated state. If a new monarch
// comes looking, we can use this info to tell them when we were last
// activated.
_lastActivatedArgs = args;
bool successfullyNotified = false;
// Raise our WindowActivated event, to let the monarch know we've been
// activated.
try
{
// Try/catch this, because the other side of this event is handled
// by the monarch. The monarch might have died. If they have, this
// will throw an exception. Just eat it, the election thread will
// handle hooking up the new one.
_WindowActivatedHandlers(*this, args);
successfullyNotified = true;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_ActivateWindow",
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
// Method Description:
// - Retrieve the WindowActivatedArgs describing the last activation of this
// peasant. New monarchs can use this state to determine when we were last
// activated.
// Arguments:
// - <none>
// Return Value:
// - a WindowActivatedArgs with info about when and where we were last activated.
Remoting::WindowActivatedArgs Peasant::GetLastActivatedArgs()
{
return _lastActivatedArgs;
}
// Method Description:
// - Summon this peasant to become the active window. Currently, it just
// causes the peasant to become the active window wherever the window
// already was.
// - Will raise a SummonRequested event to ask the hosting window to handle for us.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Peasant::Summon(const Remoting::SummonWindowBehavior& summonBehavior)
{
auto localCopy = winrt::make<implementation::SummonWindowBehavior>(summonBehavior);
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_Summon",
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
TraceLoggingUInt64(localCopy.MoveToCurrentDesktop(), "MoveToCurrentDesktop", "true if we should move to the current desktop"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
_SummonRequestedHandlers(*this, localCopy);
}
// Method Description:
// - Tell this window to display it's window ID. We'll raise a
// DisplayWindowIdRequested event, which will get handled in the AppHost,
// and used to tell the app to display the ID toast.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Peasant::DisplayWindowId()
{
// Not worried about try/catching this. The handler is in AppHost, which
// is in-proc for us.
_DisplayWindowIdRequestedHandlers(*this, nullptr);
}
// Method Description:
// - Raises an event to ask that all windows be identified. This will come
// back to us when the Monarch handles the event and calls our
// DisplayWindowId method.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Peasant::RequestIdentifyWindows()
{
bool successfullyNotified = false;
try
{
// Try/catch this, because the other side of this event is handled
// by the monarch. The monarch might have died. If they have, this
// will throw an exception. Just eat it, the election thread will
// handle hooking up the new one.
_IdentifyWindowsRequestedHandlers(*this, nullptr);
successfullyNotified = true;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestIdentifyWindows",
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestRename(const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args)
{
bool successfullyNotified = false;
const auto oldName{ _WindowName };
try
{
// Try/catch this, because the other side of this event is handled
// by the monarch. The monarch might have died. If they have, this
// will throw an exception. Just eat it, the election thread will
// handle hooking up the new one.
_RenameRequestedHandlers(*this, args);
if (args.Succeeded())
{
_WindowName = args.NewName();
}
successfullyNotified = true;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestRename",
TraceLoggingUInt64(GetID(), "peasantID", "Our ID"),
TraceLoggingWideString(oldName.c_str(), "oldName", "Our old name"),
TraceLoggingWideString(args.NewName().c_str(), "newName", "The proposed name"),
TraceLoggingBoolean(args.Succeeded(), "succeeded", "true if the monarch ok'd this new name for us."),
TraceLoggingBoolean(successfullyNotified, "successfullyNotified", "true if we successfully notified the monarch"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestShowNotificationIcon()
{
try
{
_ShowNotificationIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestShowNotificationIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestHideNotificationIcon()
{
try
{
_HideNotificationIconRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestHideNotificationIcon",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::RequestQuitAll()
{
try
{
_QuitAllRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_RequestQuit",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
void Peasant::Quit()
{
try
{
_QuitRequestedHandlers(*this, nullptr);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
TraceLoggingWrite(g_hRemotingProvider,
"Peasant_Quit",
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
// Method Description:
// - Request and return the window layout from the current TerminalPage
// Arguments:
// - <none>
// Return Value:
// - the window layout as a json string
hstring Peasant::GetWindowLayout()
{
auto args = winrt::make_self<implementation::GetWindowLayoutArgs>();
_GetWindowLayoutRequestedHandlers(nullptr, *args);
if (const auto op = args->WindowLayoutJsonAsync())
{
// This will fail if called on the UI thread, so the monarch should
// never set WindowLayoutJsonAsync.
auto str = op.get();
return str;
}
return args->WindowLayoutJson();
}
}