Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ buflen
buildsystems
buildtransitive
BValue
BYPOSITION
Cacafire
CALLCONV
CANDRABINDU
Expand Down
50 changes: 50 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,54 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Flyout(newTabFlyout);
}

// Method Description:
// - Builds the list of active profiles and raises SystemMenuNewTabProfilesChanged
// so the hosting window can populate its system menu "New Tab" submenu.
void TerminalPage::PopulateSystemMenuNewTabProfiles()
{
const auto activeProfiles = _settings.ActiveProfiles();
if (!activeProfiles || activeProfiles.Size() == 0)
{
return;
}

const auto defaultProfileGuid = _settings.GlobalSettings().DefaultProfile();

auto profileItems = winrt::single_threaded_vector<winrt::TerminalApp::SystemMenuNewTabProfileItem>();

for (uint32_t i = 0; i < activeProfiles.Size(); i++)
{
const auto profile = activeProfiles.GetAt(i);
auto displayName = profile.Name();

if (profile.Guid() == defaultProfileGuid)
{
displayName = displayName + L" (default)";
}

NewTerminalArgs newTerminalArgs{ gsl::narrow_cast<int32_t>(i) };

auto handler = winrt::TerminalApp::SystemMenuNewTabProfileHandler(
[weakThis{ get_weak() }, newTerminalArgs]() {
if (auto page{ weakThis.get() })
{
NewTabArgs newTabArgs{ newTerminalArgs };
ActionAndArgs actionAndArgs{ ShortcutAction::NewTab, newTabArgs };
page->_actionDispatch->DoAction(actionAndArgs);
}
});

auto item = winrt::make_self<winrt::TerminalApp::implementation::SystemMenuNewTabProfileItem>();
item->DisplayName(displayName);
item->Handler(handler);
profileItems.Append(*item);
}

auto args = winrt::make_self<winrt::TerminalApp::implementation::SystemMenuNewTabProfilesArgs>();
args->Profiles(profileItems);
SystemMenuNewTabProfilesChanged.raise(*this, *args);
}

// Method Description:
// - For a given list of tab menu entries, this method will create the corresponding
// list of flyout items. This is a recursive method that calls itself when it comes
Expand Down Expand Up @@ -3862,6 +3910,8 @@ namespace winrt::TerminalApp::implementation
_UpdateTabWidthMode();
_CreateNewTabFlyout();

PopulateSystemMenuNewTabProfiles();

// Reload the current value of alwaysOnTop from the settings file. This
// will let the user hot-reload this setting, but any runtime changes to
// the alwaysOnTop setting will be lost.
Expand Down
21 changes: 21 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "RenameWindowRequestedArgs.g.h"
#include "RequestMoveContentArgs.g.h"
#include "LaunchPositionRequest.g.h"
#include "SystemMenuNewTabProfileItem.g.h"
#include "SystemMenuNewTabProfilesArgs.g.h"
#include "Toast.h"

#include "WindowsPackageManagerFactory.h"
Expand Down Expand Up @@ -77,6 +79,21 @@ namespace winrt::TerminalApp::implementation
_TabIndex{ tabIndex } {};
};

struct SystemMenuNewTabProfileItem : SystemMenuNewTabProfileItemT<SystemMenuNewTabProfileItem>
{
SystemMenuNewTabProfileItem() = default;

WINRT_PROPERTY(winrt::hstring, DisplayName, L"");
WINRT_PROPERTY(winrt::TerminalApp::SystemMenuNewTabProfileHandler, Handler, nullptr);
};

struct SystemMenuNewTabProfilesArgs : SystemMenuNewTabProfilesArgsT<SystemMenuNewTabProfilesArgs>
{
SystemMenuNewTabProfilesArgs() = default;

WINRT_PROPERTY(Windows::Foundation::Collections::IVector<winrt::TerminalApp::SystemMenuNewTabProfileItem>, Profiles, nullptr);
};

struct LaunchPositionRequest : LaunchPositionRequestT<LaunchPositionRequest>
{
LaunchPositionRequest() = default;
Expand Down Expand Up @@ -176,6 +193,8 @@ namespace winrt::TerminalApp::implementation

uint32_t NumberOfTabs() const;

void PopulateSystemMenuNewTabProfiles();

til::property_changed_event PropertyChanged;

// -------------------------------- WinRT Events ---------------------------------
Expand Down Expand Up @@ -204,6 +223,8 @@ namespace winrt::TerminalApp::implementation

til::typed_event<IInspectable, winrt::TerminalApp::LaunchPositionRequest> RequestLaunchPosition;

til::typed_event<IInspectable, winrt::TerminalApp::SystemMenuNewTabProfilesArgs> SystemMenuNewTabProfilesChanged;

WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);

Expand Down
15 changes: 15 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.idl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ namespace TerminalApp
Microsoft.Terminal.Settings.Model.LaunchPosition Position;
}

delegate void SystemMenuNewTabProfileHandler();

[default_interface] runtimeclass SystemMenuNewTabProfileItem
{
String DisplayName { get; };
SystemMenuNewTabProfileHandler Handler { get; };
};

[default_interface] runtimeclass SystemMenuNewTabProfilesArgs
{
IVector<SystemMenuNewTabProfileItem> Profiles { get; };
};

[default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged, Microsoft.Terminal.UI.IDirectKeyListener
{
TerminalPage(WindowProperties properties, ContentManager manager);
Expand Down Expand Up @@ -103,5 +116,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RequestReceiveContentArgs> RequestReceiveContent;

event Windows.Foundation.TypedEventHandler<Object, LaunchPositionRequest> RequestLaunchPosition;

event Windows.Foundation.TypedEventHandler<Object, SystemMenuNewTabProfilesArgs> SystemMenuNewTabProfilesChanged;
}
}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/TerminalWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ namespace winrt::TerminalApp::implementation
SystemMenuItemHandler(this, &TerminalWindow::_OpenSettingsUI));
SystemMenuChangeRequested.raise(*this, *args);

_root->PopulateSystemMenuNewTabProfiles();

TraceLoggingWrite(
g_hTerminalAppProvider,
"WindowCreated",
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalApp/TerminalWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ namespace winrt::TerminalApp::implementation
til::typed_event<winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs> SettingsChanged;
til::typed_event<winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Control::WindowSizeChangedEventArgs> WindowSizeChanged;

FORWARDED_TYPED_EVENT(SystemMenuNewTabProfilesChanged, Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuNewTabProfilesArgs, _root, SystemMenuNewTabProfilesChanged);

private:
// If you add controls here, but forget to null them either here or in
// the ctor, you're going to have a bad time. It'll mysteriously fail to
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/TerminalWindow.idl
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.SystemMenuChangeArgs> SystemMenuChangeRequested;
event Windows.Foundation.TypedEventHandler<Object, TerminalApp.SystemMenuNewTabProfilesArgs> SystemMenuNewTabProfilesChanged;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Microsoft.Terminal.Control.WindowSizeChangedEventArgs> WindowSizeChanged;

Expand Down
43 changes: 43 additions & 0 deletions src/cascadia/WindowsTerminal/AppHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ void AppHost::Initialize()
_revokers.AlwaysOnTopChanged = _windowLogic.AlwaysOnTopChanged(winrt::auto_revoke, { this, &AppHost::_AlwaysOnTopChanged });
_revokers.RaiseVisualBell = _windowLogic.RaiseVisualBell(winrt::auto_revoke, { this, &AppHost::_RaiseVisualBell });
_revokers.SystemMenuChangeRequested = _windowLogic.SystemMenuChangeRequested(winrt::auto_revoke, { this, &AppHost::_SystemMenuChangeRequested });
_revokers.SystemMenuNewTabProfilesChanged = _windowLogic.SystemMenuNewTabProfilesChanged(winrt::auto_revoke, { this, &AppHost::_SystemMenuNewTabProfilesChanged });
_revokers.ChangeMaximizeRequested = _windowLogic.ChangeMaximizeRequested(winrt::auto_revoke, { this, &AppHost::_ChangeMaximizeRequested });
_revokers.RequestLaunchPosition = _windowLogic.RequestLaunchPosition(winrt::auto_revoke, { this, &AppHost::_HandleRequestLaunchPosition });

Expand Down Expand Up @@ -1091,6 +1092,48 @@ void AppHost::_SystemMenuChangeRequested(const winrt::Windows::Foundation::IInsp
}
}

// Method Description:
// - Handles the SystemMenuNewTabProfilesChanged event from the TerminalPage.
// Rebuilds the "New Tab" submenu in the system menu (Alt+Space) with items
// driven by the app layer's action engine.
// Arguments:
// - args: The SystemMenuNewTabProfilesArgs containing the new list of
// profiles to show in the "New Tab" submenu.
// Return Value:
// - <none>
void AppHost::_SystemMenuNewTabProfilesChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::TerminalApp::SystemMenuNewTabProfilesArgs& args)
{
if (!_window)
{
return;
}

_window->RemoveSystemSubMenu(L"New Tab");

if (!args || !args.Profiles() || args.Profiles().Size() == 0)
{
return;
}

std::vector<std::pair<winrt::hstring, winrt::delegate<void()>>> profileItems;
profileItems.reserve(args.Profiles().Size());

for (const auto& item : args.Profiles())
{
auto handler = item.Handler();
auto callback = winrt::delegate<void()>([handler]() {
if (handler)
{
handler();
}
});

profileItems.emplace_back(item.DisplayName(), std::move(callback));
}

_window->AddSystemSubMenu(L"New Tab", profileItems);
}

// Method Description:
// - BODGY workaround for GH#9320. When the window moves, dismiss all the popups
// in the UI tree. Xaml Islands unfortunately doesn't do this for us, see
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/WindowsTerminal/AppHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ class AppHost : public std::enable_shared_from_this<AppHost>
void _HandleRequestLaunchPosition(const winrt::Windows::Foundation::IInspectable& sender,
winrt::TerminalApp::LaunchPositionRequest args);

void _SystemMenuNewTabProfilesChanged(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::TerminalApp::SystemMenuNewTabProfilesArgs& args);

// Helper struct. By putting these all into one struct, we can revoke them
// all at once, by assigning _revokers to a fresh Revokers instance. That'll
// cause us to dtor the old one, which will immediately call revoke on all
Expand Down Expand Up @@ -160,6 +163,7 @@ class AppHost : public std::enable_shared_from_this<AppHost>
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
winrt::TerminalApp::TerminalWindow::WindowSizeChanged_revoker WindowSizeChanged;
winrt::TerminalApp::TerminalWindow::SystemMenuNewTabProfilesChanged_revoker SystemMenuNewTabProfilesChanged;
} _revokers{};

// our IslandWindow is not a WinRT type. It can't make auto_revokers like
Expand Down
92 changes: 92 additions & 0 deletions src/cascadia/WindowsTerminal/IslandWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,51 @@ void IslandWindow::AddToSystemMenu(const winrt::hstring& itemLabel, winrt::deleg
_systemMenuNextItemId++;
}

void IslandWindow::AddSystemSubMenu(const winrt::hstring& menuLabel, const std::vector<std::pair<winrt::hstring, winrt::delegate<void()>>>& items)
{
const auto systemMenu = GetSystemMenu(_window.get(), FALSE);

HMENU subMenu = CreatePopupMenu();
if (!subMenu)
{
LOG_LAST_ERROR();
return;
}

for (const auto& [label, callback] : items)
{
auto wID = _systemMenuNextItemId;

MENUITEMINFOW item;
item.cbSize = sizeof(MENUITEMINFOW);
item.fMask = MIIM_STATE | MIIM_ID | MIIM_STRING;
item.fState = MF_ENABLED;
item.wID = wID;
item.dwTypeData = const_cast<LPWSTR>(label.c_str());
item.cch = static_cast<UINT>(label.size());

if (LOG_LAST_ERROR_IF(!InsertMenuItemW(subMenu, wID, FALSE, &item)))
{
continue;
}
_systemMenuItems.insert({ wID, { label, callback } });
_systemMenuNextItemId++;
}

MENUITEMINFOW subMenuItem;
subMenuItem.cbSize = sizeof(MENUITEMINFOW);
subMenuItem.fMask = MIIM_STRING | MIIM_SUBMENU;
subMenuItem.hSubMenu = subMenu;
subMenuItem.dwTypeData = const_cast<LPWSTR>(menuLabel.c_str());
subMenuItem.cch = static_cast<UINT>(menuLabel.size());

if (LOG_LAST_ERROR_IF(!InsertMenuItemW(systemMenu, _systemMenuNextItemId, FALSE, &subMenuItem)))
{
DestroyMenu(subMenu);
return;
}
}

void IslandWindow::RemoveFromSystemMenu(const winrt::hstring& itemLabel)
{
const auto systemMenu = GetSystemMenu(_window.get(), FALSE);
Expand All @@ -1832,6 +1877,53 @@ void IslandWindow::RemoveFromSystemMenu(const winrt::hstring& itemLabel)
_systemMenuItems.erase(it->first);
}

// Method Description:
// - Removes a submenu from the system menu by its label. Also removes all
// child items from the _systemMenuItems tracking map. The submenu HMENU
// is destroyed automatically by DeleteMenu.
// Arguments:
// - menuLabel: The label of the submenu to remove.
void IslandWindow::RemoveSystemSubMenu(const winrt::hstring& menuLabel)
{
const auto systemMenu = GetSystemMenu(_window.get(), FALSE);
const auto itemCount = GetMenuItemCount(systemMenu);
if (LOG_LAST_ERROR_IF(itemCount == -1))
{
return;
}

for (int i = 0; i < itemCount; i++)
{
wchar_t buffer[256]{};
MENUITEMINFOW mii{};
mii.cbSize = sizeof(MENUITEMINFOW);
mii.fMask = MIIM_STRING | MIIM_SUBMENU;
mii.dwTypeData = buffer;
mii.cch = ARRAYSIZE(buffer);

if (!GetMenuItemInfoW(systemMenu, static_cast<UINT>(i), TRUE, &mii))
{
continue;
}

if (mii.hSubMenu && menuLabel == std::wstring_view{ buffer })
{
const auto subItemCount = GetMenuItemCount(mii.hSubMenu);
for (int j = 0; j < subItemCount; j++)
{
const auto subItemId = GetMenuItemID(mii.hSubMenu, j);
if (subItemId != static_cast<UINT>(-1))
{
_systemMenuItems.erase(subItemId);
}
}

DeleteMenu(systemMenu, static_cast<UINT>(i), MF_BYPOSITION);
Comment thread Fixed
return;
}
}
}

void IslandWindow::_resetSystemMenu()
{
// GetSystemMenu(..., true) will revert the menu to the default state.
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/WindowsTerminal/IslandWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ class IslandWindow :

void OpenSystemMenu(const std::optional<int> mouseX, const std::optional<int> mouseY) const noexcept;
void AddToSystemMenu(const winrt::hstring& itemLabel, winrt::delegate<void()> callback);
void AddSystemSubMenu(const winrt::hstring& menuLabel, const std::vector<std::pair<winrt::hstring, winrt::delegate<void()>>>& items);
void RemoveFromSystemMenu(const winrt::hstring& itemLabel);
void RemoveSystemSubMenu(const winrt::hstring& menuLabel);

void UseDarkTheme(const bool v);
virtual void UseMica(const bool newValue, const double titlebarOpacity);
Expand Down
Loading