Repository: microsoft/terminal (language: cpp)

Issue:
Request: Right-click menu inside TerminalControl (w/ Copy & Paste?)
I use cmd.exe's right-click on the menu bar to Select All, Mark, Find, Copy, and Paste. I would like Windows Terminal to support a similar feature. 
Conhost.exe
![image](https://user-images.githubusercontent.com/3976990/67612708-d85fcc00-f772-11e9-8830-a50e1e8007c7.png)
I also get the right mouse button on WSL's Ubuntu console. 
![image](https://user-images.githubusercontent.com/3976990/67612732-42787100-f773-11e9-9e20-f31e3cb278b9.png)




Discussion / hints:
Seems vaguely related to #1912 as well.

This might require some more detailed design discussion. For example, if you right click on the titlebar (when tabs aren't in the titlebar), then it makes sense that these options would apply only to the active tab/pane. Start a selection in the current tab, copy the current tab's selected text, etc. 

What about if you right-click on a tab that's not focused? Would "copy" there copy the text from the unfocused tab?

Would "find" still bring up the search dialog for the currently focused pane?
Same request here. Not having a right-click menu seriously reduces Terminal's usability compared to MobaXterm on Windows and Konsole in KDE on Linux.
Yes, please! 
Here is what it looks like in Konsole, it is incredibly useful 

![image](https://user-images.githubusercontent.com/1409019/77149057-13033180-6a91-11ea-97f1-fe44546aebf3.png)

@cor3ntin Literally this a million times over. That's what an actually useful terminal app is supposed to do/look like. 
You don’t need to throw shade to make your point.
@DHowett-MSFT I'm just being honest. There's a reason I have Terminal installed but still use [MobaXTerm](https://mobaxterm.mobatek.net/), even for my [WSL instance](https://github.com/jdrch/Hardware/blob/master/Lenovo%20L380%20Yoga%20ThinkPad%2020M7CTO1WW.md#operating-systems) (which I had to blow away anyway to get an Insider Build update to take, but that's another story.) Stuff like lack of a right-click really diminishes the usefulness of a terminal emulator to me because I can't even use the context menu keyboard button in it. If I said otherwise I'd be lying. 🤷‍♂️

And no, I'm not a hater. [More of my PCs run Windows 10 (5)](https://github.com/jdrch/Hardware/blob/master/OSes.md#windows-64-bit) than run any other OS and [Windows is my daily driver at work and home](https://github.com/jdrch/Hardware#you-should-switch-from-windows-because-insert-reasons-here).
[This terminal](https://github.com/felixse/FluentTerminal "Fluent terminal") is a highly customizable UWP app that also has a context menu and looks cleaner and better than the official Windows Terminal app. I was thinking why don't we take that app as an "inspiration"?
@SFM61319 Thanks for sharing a link to fluentterm on multiple bug reports on our repo. We'll take it into consideration.
This context menu should also include the pane commands
"Split Vertically" and "Split Horizontally"
@mdtauk Which other terminal apps have that feature? Just curious. Most multiplexing is done server-side via `byobu`, `tmux`, or something similar.
@jdrch the feature is there, however it can only be used via keyboard shortcuts.
@mdtauk Ah. TIL!
@jdrch [Hyper](https://github.com/zeit/hyperterm/pull/693), [Terminator](https://gnometerminator.blogspot.com/p/introduction.html), Windows Terminal (see #1000), [iterm2](https://osxdaily.com/2011/01/03/split-terminal-mac-iterm2/)
> @mdtauk Ah. TIL!

This is why I think the feature needs more UI awareness
Also, the profile menu and the buttons don't look much "fluent". Please add some `AcrylicBrush` and `RevealBrush` brushes to those for a more fluent effect.
@SFM61319 The discussion of where to use acrylic and where not to isn't really on-topic for this thread - I've moved the discussion over to #1375
Since the issue https://github.com/microsoft/terminal/issues/3424, asking to be able to disable paste on right-click, was closed as a duplicate of this issue, I would like to bring that up as a separate issue (that is much easier to address) here. *PLEASE* allow me to disable this functionality in the settings. 

I am constantly accidentally pasting my clipboard into the terminal while trying to do something else, which is going to force me to switch back to ConEmu where I can disable this functionality.
Since there's already settings for copy on select and key bindings for copy-paste, **please please please** make this feature turn-off-able too.
So, we're tracking the ability to fully customize what happens when you click in #1553. As part of that work, we'll of course make sure you can turn off all right-click actions.
There is another issue related to this that I think is even more important than neat features: right-click paste can be dangerous or even catastrophic when working in a source-code environment -- I just had it happen to me that a series of lines pasted into a command window (which is why I did a search and found this issue) -- I'm very glad it is on the table.  I'm just glad the (unintentional) paste didn't delete or alter any files.  (This would be a really insidious way for a hacker to ruin a PC too with the right commands snuck onto the clipboard.)  I am aware there is a way to warn before multi-line pasting, which is what I am going to look up now.

In any case, I wanted to reinforce the importance of this one:  I believe it is critical to making Windows Terminal safe to use in a source-code environment, where I would very much like to prevent any pasting unless I hit Shift-Ins or Ctrl-V or other bound key sequence to do it.  Thanks!!
> right-click paste can be dangerous or even catastrophic when working in a source-code environment

Good to know. Hadn't considered that!

> I am aware there is a way to warn before multi-line pasting, which is what I am going to look up now.

MobaXterm will warn you if there's an `Enter` (or newline) included in the paste that might cause the resulting line to execute.
I'm excited by Terminal. But I'm not willing to use it until I can disable right-click paste. I don't want to dump a bunch of unknown text or commands into a live terminal just because of a stray mouse click. 
> 
> 
> I'm excited by Terminal. But I'm not willing to use it until I can disable right-click paste. I don't want to dump a bunch of unknown text or commands into a live terminal just because of a stray mouse click.

I did find something that mitigates this problem enough for me to start using it:  the global setting

"multiLinePasteWarning": true, 

at least prevents any commands from executing unintentionally, since it looks for line-feeds in the clipboard contents and requires a confirmation before it pastes.  However, I'm anxious to see an ability to turn paste-on-right-click feature off or ONLY paste with Ctrl-Ins or other configured key mappings.
`"multiLinePasteWarning": true,` is not a fix for me cause I am often pasting multi-line text into VIM.
I wonder where the right-click-immediately-pastes behaviour originally started? The same behaviour is present in Visual Studio Code's integrated terminal in Windows, but not in Linux.
> 
> 
> I wonder where the right-click-immediately-pastes behaviour originally started? The same behaviour is present in Visual Studio Code's integrated terminal in Windows, but not in Linux.

I know it from [Console Window Host](https://www.howtogeek.com/howto/4996/what-is-conhost.exe-and-why-is-it-running/). Right-click paste has been the *default* option there for as long as I can remember. But all console shortcuts have a shortcut property that allows you to disable 'Quick Edit Mode'. I assume the Terminal team picked the default behavior to start with. 
An right click menu like linux terminal would be amazing! It would avoid accidently pasting stuff and it would make it more conveinient to copy and paste stuff into the terminal. ![](https://i.imgur.com/bf3qcLU.png)
any alternative for this?
> @mdtauk Which other terminal apps have that feature? Just curious. Most multiplexing is done server-side via `byobu`, `tmux`, or something similar.

1. Splitting screen is super useful to me, I do it all the time in daily work,  especially we now have large monitorers.
2. I only use tmux/byobu when I have to, e.g.: on server, since a lot of functions are inconvenient with them, such as copy & paste, scrolling, mouse, etc.
3. I normally work on Ubuntu with Terminator, the main reason is because is has `Horizontal Split` and `Vertical Split` plus icons in right click context menu. Having them in menu is helpful so I don't need to remember the shortcuts. Having the icons is also helpful,  since when you say "Horizontal Split" (or "Vertical Split") , it is actually confusing:  are you spliting a new window in the horizontal direction(口|口), or you mean the split line is is horizontal (曰)? The icons make you understand it without thinking.

It would be really nice to be able to right-click a pane to close it from a context menu.
misc notes from last week:

* Some of these actions should only appear when text is selected.
  * how? 
  * How do we feed the selected text into the action in the menu?
  * There are some notes in #5916 
* I want to be able to right-click search for some text, but again, that requires parsing the action at time of invoke, because the action name would be dependent on the selected text? (some notes in #10175)
* I want to be able to right click a selection and save that as a `sendInput` action _with that text_. That's crazy. 
* "Open Current Directory" or "Copy current path" actions would require that a path had been set by the shell



### Actions that could be here:
* Always:
  * duplicate tab
  * split pane
  * paste
* Without a zoomed pane(?)
  * zoom pane
* With text selected:
  * Find this text in the buffer 
  * Search the web (requires #10175) 
  * Copy
  * #12857
  * Bookmark selection, #11000
* When there's a path provided by cmdline app
  * Open in explorer
  * copy current path
* When the connection is closed(?)
  * Close pane #12849 (only mentioned because it's a conditional KB. May want to just always have.)


### More notes, circa late june 2022

* Is it valuable at all to have this be configurable? Other than just "right click opens the menu" - I reckon it might _not_
* We could just raise an event from control->app `ContextMenuRequestedArgs { selectedText, currentDirectory, connectionState(?) }`, then have the app build the menu using that info. 
* This doesn't seem like something that should be an action
* Without real mouse bindings, I'd put this under `"experimental.mouseRightClick": "paste"| "menu"`
So, noticing that this issue was opened 2-1/2 years ago, and that functionality is being mentioned/consolidated into it, can we get some visibility into where it sits in the roadmap - that is, when will it come out of planning and go into development?

Fix patch:
diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.cpp b/src/cascadia/TerminalApp/ColorPickupFlyout.cpp
index e582ca7fe6f..f82d86feecc 100644
--- a/src/cascadia/TerminalApp/ColorPickupFlyout.cpp
+++ b/src/cascadia/TerminalApp/ColorPickupFlyout.cpp
@@ -1,9 +1,6 @@
 #include "pch.h"
 #include "ColorPickupFlyout.h"
 #include "ColorPickupFlyout.g.cpp"
-#include "winrt/Windows.UI.Xaml.Media.h"
-#include "winrt/Windows.UI.Xaml.Shapes.h"
-#include "winrt/Windows.UI.Xaml.Interop.h"
 #include <LibraryResources.h>
 
 namespace winrt::TerminalApp::implementation
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index fa5194db7ab..2d7a385fbb2 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -205,6 +205,15 @@
   <data name="TabClose" xml:space="preserve">
     <value>Close Tab</value>
   </data>
+  <data name="PaneClose" xml:space="preserve">
+    <value>Close Pane</value>
+  </data>
+  <data name="SplitTabText" xml:space="preserve">
+    <value>Split Tab</value>
+  </data>
+  <data name="SplitPaneText" xml:space="preserve">
+    <value>Split Pane</value>
+  </data>
   <data name="TabColorChoose" xml:space="preserve">
     <value>Color...</value>
   </data>
@@ -708,9 +717,6 @@
   <data name="DropPathTabRun.Text" xml:space="preserve">
     <value>Open a new tab in given starting directory</value>
   </data>
-  <data name="SplitTabText" xml:space="preserve">
-    <value>Split Tab</value>
-  </data>
   <data name="DropPathTabNewWindow.Text" xml:space="preserve">
     <value>Open a new window with given starting directory</value>
   </data>
@@ -795,4 +801,4 @@
   <data name="NewTabMenuFolderEmpty" xml:space="preserve">
     <value>Empty...</value>
   </data>
-</root>
\ No newline at end of file
+</root>
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 6cfbc4bdf0b..f281ebadbd4 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -1616,6 +1616,9 @@ namespace winrt::TerminalApp::implementation
         });
 
         term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
+
+        term.ContextMenu().Opening({ this, &TerminalPage::_ContextMenuOpened });
+        term.SelectionContextMenu().Opening({ this, &TerminalPage::_SelectionMenuOpened });
     }
 
     // Method Description:
@@ -4305,6 +4308,78 @@ namespace winrt::TerminalApp::implementation
         _updateThemeColors();
     }
 
+    void TerminalPage::_ContextMenuOpened(const IInspectable& sender,
+                                          const IInspectable& /*args*/)
+    {
+        _PopulateContextMenu(sender, false /*withSelection*/);
+    }
+    void TerminalPage::_SelectionMenuOpened(const IInspectable& sender,
+                                            const IInspectable& /*args*/)
+    {
+        _PopulateContextMenu(sender, true /*withSelection*/);
+    }
+
+    void TerminalPage::_PopulateContextMenu(const IInspectable& sender,
+                                            const bool /*withSelection*/)
+    {
+        // withSelection can be used to add actions that only appear if there's
+        // selected text, like "search the web". In this initial draft, it's not
+        // actually augmented by the TerminalPage, so it's left commented out.
+
+        const auto& menu{ sender.try_as<MUX::Controls::CommandBarFlyout>() };
+        if (!menu)
+        {
+            return;
+        }
+
+        // Helper lambda for dispatching an ActionAndArgs onto the
+        // ShortcutActionDispatch. Used below to wire up each menu entry to the
+        // respective action.
+
+        auto weak = get_weak();
+        auto makeCallback = [weak](const ActionAndArgs& actionAndArgs) {
+            return [weak, actionAndArgs](auto&&, auto&&) {
+                if (auto page{ weak.get() })
+                {
+                    page->_actionDispatch->DoAction(actionAndArgs);
+                }
+            };
+        };
+
+        auto makeItem = [&menu, &makeCallback](const winrt::hstring& label,
+                                               const winrt::hstring& icon,
+                                               const auto& action) {
+            AppBarButton button{};
+
+            if (!icon.empty())
+            {
+                auto iconElement = IconPathConverter::IconWUX(icon);
+                Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
+                button.Icon(iconElement);
+            }
+
+            button.Label(label);
+            button.Click(makeCallback(action));
+            menu.SecondaryCommands().Append(button);
+        };
+
+        // Wire up each item to the action that should be performed. By actually
+        // connecting these to actions, we ensure the implementation is
+        // consistent. This also leaves room for customizing this menu with
+        // actions in the future.
+
+        makeItem(RS_(L"SplitPaneText"), L"\xF246", ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } });
+        makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr });
+
+        // Only wire up "Close Pane" if there's multiple panes.
+        if (_GetFocusedTabImpl()->GetLeafPaneCount() > 1)
+        {
+            makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr });
+        }
+
+        makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } });
+    }
+
     // Handler for our WindowProperties's PropertyChanged event. We'll use this
     // to pop the "Identify Window" toast when the user renames our window.
     winrt::fire_and_forget TerminalPage::_windowPropertyChanged(const IInspectable& /*sender*/,
@@ -4328,4 +4403,5 @@ namespace winrt::TerminalApp::implementation
             }
         }
     }
+
 }
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index a99854374ca..f141d46025b 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -457,6 +457,10 @@ namespace winrt::TerminalApp::implementation
         winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
         winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
 
+        void _ContextMenuOpened(const IInspectable& sender, const IInspectable& args);
+        void _SelectionMenuOpened(const IInspectable& sender, const IInspectable& args);
+        void _PopulateContextMenu(const IInspectable& sender, const bool withSelection);
+
 #pragma region ActionHandlers
         // These are all defined in AppActionHandlers.cpp
 #define ON_ALL_ACTIONS(action) DECLARE_ACTION_HANDLER(action);
diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp
index 7eaf4275535..b4b6915c7db 100644
--- a/src/cascadia/TerminalControl/ControlInteractivity.cpp
+++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp
@@ -205,7 +205,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         // GH#9396: we prioritize hyper-link over VT mouse events
         auto hyperlink = _core->GetHyperlink(terminalPosition.to_core_point());
         if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown) &&
-            ctrlEnabled && !hyperlink.empty())
+            ctrlEnabled &&
+            !hyperlink.empty())
         {
             const auto clickCount = _numberOfClicks(pixelPosition, timestamp);
             // Handle hyper-link only on the first click to prevent multiple activations
@@ -255,14 +256,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         }
         else if (WI_IsFlagSet(buttonState, MouseButtonState::IsRightButtonDown))
         {
-            // Try to copy the text and clear the selection
-            const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, nullptr);
-            _core->ClearSelection();
-            if (_core->CopyOnSelect() || !successfulCopy)
+            if (_core->Settings().RightClickContextMenu())
             {
-                // CopyOnSelect: right click always pastes!
-                // Otherwise: no selection --> paste
-                RequestPasteTextFromClipboard();
+                auto contextArgs = winrt::make<ContextMenuRequestedEventArgs>(til::point{ pixelPosition }.to_winrt_point());
+                _ContextMenuRequestedHandlers(*this, contextArgs);
+            }
+            else
+            {
+                // Try to copy the text and clear the selection
+                const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, nullptr);
+                _core->ClearSelection();
+                if (_core->CopyOnSelect() || !successfulCopy)
+                {
+                    // CopyOnSelect: right click always pastes!
+                    // Otherwise: no selection --> paste
+                    RequestPasteTextFromClipboard();
+                }
             }
         }
     }
diff --git a/src/cascadia/TerminalControl/ControlInteractivity.h b/src/cascadia/TerminalControl/ControlInteractivity.h
index 4cc4df10bdd..15fee5574c5 100644
--- a/src/cascadia/TerminalControl/ControlInteractivity.h
+++ b/src/cascadia/TerminalControl/ControlInteractivity.h
@@ -88,6 +88,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         TYPED_EVENT(OpenHyperlink, IInspectable, Control::OpenHyperlinkEventArgs);
         TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs);
         TYPED_EVENT(ScrollPositionChanged, IInspectable, Control::ScrollPositionChangedArgs);
+        TYPED_EVENT(ContextMenuRequested, IInspectable, Control::ContextMenuRequestedEventArgs);
 
     private:
         // NOTE: _uiaEngine must be ordered before _core.
diff --git a/src/cascadia/TerminalControl/ControlInteractivity.idl b/src/cascadia/TerminalControl/ControlInteractivity.idl
index aada01ee0ad..6906999aa38 100644
--- a/src/cascadia/TerminalControl/ControlInteractivity.idl
+++ b/src/cascadia/TerminalControl/ControlInteractivity.idl
@@ -65,6 +65,9 @@ namespace Microsoft.Terminal.Control
         event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
         event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
 
+        // Used to communicate to the TermControl, but not necessarily higher up in the stack
+        event Windows.Foundation.TypedEventHandler<Object, ContextMenuRequestedEventArgs> ContextMenuRequested;
+
 
     };
 }
diff --git a/src/cascadia/TerminalControl/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp
index 9f9b41ac1f7..c4089ebd76c 100644
--- a/src/cascadia/TerminalControl/EventArgs.cpp
+++ b/src/cascadia/TerminalControl/EventArgs.cpp
@@ -5,6 +5,7 @@
 #include "EventArgs.h"
 #include "TitleChangedEventArgs.g.cpp"
 #include "CopyToClipboardEventArgs.g.cpp"
+#include "ContextMenuRequestedEventArgs.g.cpp"
 #include "PasteFromClipboardEventArgs.g.cpp"
 #include "OpenHyperlinkEventArgs.g.cpp"
 #include "NoticeEventArgs.g.cpp"
diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h
index a542e391f63..2551843df20 100644
--- a/src/cascadia/TerminalControl/EventArgs.h
+++ b/src/cascadia/TerminalControl/EventArgs.h
@@ -5,6 +5,7 @@
 
 #include "TitleChangedEventArgs.g.h"
 #include "CopyToClipboardEventArgs.g.h"
+#include "ContextMenuRequestedEventArgs.g.h"
 #include "PasteFromClipboardEventArgs.g.h"
 #include "OpenHyperlinkEventArgs.g.h"
 #include "NoticeEventArgs.g.h"
@@ -53,6 +54,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         Windows::Foundation::IReference<CopyFormat> _formats;
     };
 
+    struct ContextMenuRequestedEventArgs : public ContextMenuRequestedEventArgsT<ContextMenuRequestedEventArgs>
+    {
+    public:
+        ContextMenuRequestedEventArgs(winrt::Windows::Foundation::Point pos) :
+            _Position(pos) {}
+
+        WINRT_PROPERTY(winrt::Windows::Foundation::Point, Position);
+    };
+
     struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT<PasteFromClipboardEventArgs>
     {
     public:
diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl
index 941384c5b05..bc67bd13624 100644
--- a/src/cascadia/TerminalControl/EventArgs.idl
+++ b/src/cascadia/TerminalControl/EventArgs.idl
@@ -22,6 +22,11 @@ namespace Microsoft.Terminal.Control
         Windows.Foundation.IReference<CopyFormat> Formats { get; };
     }
 
+    runtimeclass ContextMenuRequestedEventArgs
+    {
+        Windows.Foundation.Point Position { get; };
+    }
+
     runtimeclass TitleChangedEventArgs
     {
         String Title;
diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl
index a47af4cc262..1e2f7d71f24 100644
--- a/src/cascadia/TerminalControl/IControlSettings.idl
+++ b/src/cascadia/TerminalControl/IControlSettings.idl
@@ -61,5 +61,6 @@ namespace Microsoft.Terminal.Control
         Boolean SoftwareRendering { get; };
         Boolean ShowMarks { get; };
         Boolean UseBackgroundImageForWindow { get; };
+        Boolean RightClickContextMenu { get; };
     };
 }
diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
index cff7cb1c9bd..aa2473890f2 100644
--- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
@@ -208,4 +208,36 @@ Please either install the missing font or choose another one.</value>
     <value>No results found</value>
     <comment>Announced to a screen reader when the user searches for some text and there are no matches for that text in the terminal.</comment>
   </data>
-</root>
\ No newline at end of file
+  <data name="PasteCommandButton.Label" xml:space="preserve">
+    <value>Paste</value>
+    <comment>The label of a button for pasting the contents of the clipboard.</comment>
+  </data>
+  <data name="PasteCommandButton.ToolTipService.ToolTip" xml:space="preserve">
+    <value>Paste</value>
+    <comment>The tooltip for a paste button</comment>
+  </data>
+  <data name="CopyCommandButton.Label" xml:space="preserve">
+    <value>Copy</value>
+    <comment>The label of a button for copying the selected text to the clipboard.</comment>
+  </data>
+  <data name="CopyCommandButton.ToolTipService.ToolTip" xml:space="preserve">
+    <value>Copy</value>
+    <comment>The tooltip for a copy button</comment>
+  </data>
+  <data name="PasteWithSelectionCommandButton.Label" xml:space="preserve">
+    <value>Paste</value>
+    <comment>The label of a button for pasting the contents of the clipboard.</comment>
+  </data>
+  <data name="PasteWithSelectionCommandButton.ToolTipService.ToolTip" xml:space="preserve">
+    <value>Paste</value>
+    <comment>The tooltip for a paste button</comment>
+  </data>
+  <data name="SearchCommandButton.Label" xml:space="preserve">
+    <value>Find...</value>
+    <comment>The label of a button for searching for the selected text</comment>
+  </data>
+  <data name="SearchCommandButton.ToolTipService.ToolTip" xml:space="preserve">
+    <value>Find</value>
+    <comment>The tooltip for a button for searching for the selected text</comment>
+  </data>
+</root>
diff --git a/src/cascadia/TerminalControl/SearchBoxControl.h b/src/cascadia/TerminalControl/SearchBoxControl.h
index 5f7dee504d7..4a680edcb68 100644
--- a/src/cascadia/TerminalControl/SearchBoxControl.h
+++ b/src/cascadia/TerminalControl/SearchBoxControl.h
@@ -14,8 +14,6 @@ Author(s):
 --*/
 
 #pragma once
-#include "winrt/Windows.UI.Xaml.h"
-#include "winrt/Windows.UI.Xaml.Controls.h"
 
 #include "SearchBoxControl.g.h"
 
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index c9ef1550f18..40081c30b62 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -88,6 +88,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         _interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler });
         _interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged });
 
+        _interactivity.ContextMenuRequested({ this, &TermControl::_contextMenuHandler });
+
         // Initialize the terminal only once the swapchainpanel is loaded - that
         //      way, we'll be able to query the real pixel size it got on layout
         _layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
@@ -135,6 +137,59 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         _autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
 
         _ApplyUISettings();
+
+        _originalPrimaryElements = winrt::single_threaded_observable_vector<Controls::ICommandBarElement>();
+        _originalSecondaryElements = winrt::single_threaded_observable_vector<Controls::ICommandBarElement>();
+        _originalSelectedPrimaryElements = winrt::single_threaded_observable_vector<Controls::ICommandBarElement>();
+        _originalSelectedSecondaryElements = winrt::single_threaded_observable_vector<Controls::ICommandBarElement>();
+        for (const auto& e : ContextMenu().PrimaryCommands())
+        {
+            _originalPrimaryElements.Append(e);
+        }
+        for (const auto& e : ContextMenu().SecondaryCommands())
+        {
+            _originalSecondaryElements.Append(e);
+        }
+        for (const auto& e : SelectionContextMenu().PrimaryCommands())
+        {
+            _originalSelectedPrimaryElements.Append(e);
+        }
+        for (const auto& e : SelectionContextMenu().SecondaryCommands())
+        {
+            _originalSelectedSecondaryElements.Append(e);
+        }
+        ContextMenu().Closed([weakThis = get_weak()](auto&&, auto&&) {
+            if (auto control{ weakThis.get() }; !control->_IsClosing())
+            {
+                const auto& menu{ control->ContextMenu() };
+                menu.PrimaryCommands().Clear();
+                menu.SecondaryCommands().Clear();
+                for (const auto& e : control->_originalPrimaryElements)
+                {
+                    menu.PrimaryCommands().Append(e);
+                }
+                for (const auto& e : control->_originalSecondaryElements)
+                {
+                    menu.SecondaryCommands().Append(e);
+                }
+            }
+        });
+        SelectionContextMenu().Closed([weakThis = get_weak()](auto&&, auto&&) {
+            if (auto control{ weakThis.get() }; !control->_IsClosing())
+            {
+                const auto& menu{ control->SelectionContextMenu() };
+                menu.PrimaryCommands().Clear();
+                menu.SecondaryCommands().Clear();
+                for (const auto& e : control->_originalSelectedPrimaryElements)
+                {
+                    menu.PrimaryCommands().Append(e);
+                }
+                for (const auto& e : control->_originalSelectedSecondaryElements)
+                {
+                    menu.SecondaryCommands().Append(e);
+                }
+            }
+        });
     }
 
     void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update)
@@ -292,7 +347,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     // - Given Settings having been updated, applies the settings to the current terminal.
     // Return Value:
     // - <none>
-    winrt::fire_and_forget TermControl::UpdateControlSettings(IControlSettings settings, IControlAppearance unfocusedAppearance)
+    winrt::fire_and_forget TermControl::UpdateControlSettings(IControlSettings settings,
+                                                              IControlAppearance unfocusedAppearance)
     {
         auto weakThis{ get_weak() };
 
@@ -3174,4 +3230,52 @@ namespace winrt::Microsoft::Terminal::Control::implementation
     {
         _core.ColorSelection(fg, bg, matchMode);
     }
+
+    void TermControl::_contextMenuHandler(IInspectable /*sender*/,
+                                          Control::ContextMenuRequestedEventArgs args)
+    {
+        Controls::Primitives::FlyoutShowOptions myOption{};
+        myOption.ShowMode(Controls::Primitives::FlyoutShowMode::Standard);
+        myOption.Placement(Controls::Primitives::FlyoutPlacementMode::TopEdgeAlignedLeft);
+
+        // Position the menu where the pointer is. This was the best way I found how.
+        const til::point absolutePointerPos{ til::math::rounding, CoreWindow::GetForCurrentThread().PointerPosition() };
+        const til::point absoluteWindowOrigin{ til::math::rounding,
+                                               CoreWindow::GetForCurrentThread().Bounds().X,
+                                               CoreWindow::GetForCurrentThread().Bounds().Y };
+        // Get the offset (margin + tabs, etc..) of the control within the window
+        const til::point controlOrigin{ til::math::flooring,
+                                        this->TransformToVisual(nullptr).TransformPoint(Windows::Foundation::Point(0, 0)) };
+
+        const auto pos = (absolutePointerPos - absoluteWindowOrigin - controlOrigin).to_winrt_point();
+        myOption.Position(pos);
+
+        (_core.HasSelection() ? SelectionContextMenu() :
+                                ContextMenu())
+            .ShowAt(*this, myOption);
+    }
+
+    void TermControl::_PasteCommandHandler(const IInspectable& /*sender*/,
+                                           const IInspectable& /*args*/)
+    {
+        _interactivity.RequestPasteTextFromClipboard();
+        ContextMenu().Hide();
+        SelectionContextMenu().Hide();
+    }
+    void TermControl::_CopyCommandHandler(const IInspectable& /*sender*/,
+                                          const IInspectable& /*args*/)
+    {
+        // formats = nullptr -> copy all formats
+        _interactivity.CopySelectionToClipboard(false, nullptr);
+        ContextMenu().Hide();
+        SelectionContextMenu().Hide();
+    }
+    void TermControl::_SearchCommandHandler(const IInspectable& /*sender*/,
+                                            const IInspectable& /*args*/)
+    {
+        ContextMenu().Hide();
+        SelectionContextMenu().Hide();
+        SearchMatch(false);
+    }
+
 }
diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h
index 590780e7524..e8f0bb8734d 100644
--- a/src/cascadia/TerminalControl/TermControl.h
+++ b/src/cascadia/TerminalControl/TermControl.h
@@ -160,6 +160,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
         TYPED_EVENT(FocusFollowMouseRequested, IInspectable, IInspectable);
         TYPED_EVENT(Initialized,               Control::TermControl, Windows::UI::Xaml::RoutedEventArgs);
         TYPED_EVENT(WarningBell,               IInspectable, IInspectable);
+
         // clang-format on
 
         WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
@@ -219,6 +220,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         bool _isBackgroundLight{ false };
 
+        winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalPrimaryElements{ nullptr };
+        winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSecondaryElements{ nullptr };
+        winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSelectedPrimaryElements{ nullptr };
+        winrt::Windows::Foundation::Collections::IObservableVector<winrt::Windows::UI::Xaml::Controls::ICommandBarElement> _originalSelectedSecondaryElements{ nullptr };
+
         inline bool _IsClosing() const noexcept
         {
 #ifndef NDEBUG
@@ -315,6 +321,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
 
         til::point _toPosInDips(const Core::Point terminalCellPos);
         void _throttledUpdateScrollbar(const ScrollBarUpdate& update);
+
+        void _contextMenuHandler(IInspectable sender, Control::ContextMenuRequestedEventArgs args);
+
+        void _PasteCommandHandler(const IInspectable& sender, const IInspectable& args);
+        void _CopyCommandHandler(const IInspectable& sender, const IInspectable& args);
+        void _SearchCommandHandler(const IInspectable& sender, const IInspectable& args);
     };
 }
 
diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl
index 044bfb0a6b1..3c9f794f42c 100644
--- a/src/cascadia/TerminalControl/TermControl.idl
+++ b/src/cascadia/TerminalControl/TermControl.idl
@@ -45,6 +45,9 @@ namespace Microsoft.Terminal.Control
         event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
         event Windows.Foundation.TypedEventHandler<Object, Object> FocusFollowMouseRequested;
 
+        Microsoft.UI.Xaml.Controls.CommandBarFlyout ContextMenu { get; };
+        Microsoft.UI.Xaml.Controls.CommandBarFlyout SelectionContextMenu { get; };
+
         event Windows.Foundation.TypedEventHandler<TermControl, Windows.UI.Xaml.RoutedEventArgs> Initialized;
         // This is an event handler forwarder for the underlying connection.
         // We expose this and ConnectionState here so that it might eventually be data bound.
diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml
index 0f5b6b86b3e..58b5c8b08d0 100644
--- a/src/cascadia/TerminalControl/TermControl.xaml
+++ b/src/cascadia/TerminalControl/TermControl.xaml
@@ -10,6 +10,7 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:local="using:Microsoft.Terminal.Control"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:mux="using:Microsoft.UI.Xaml.Controls"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch"
              d:DesignHeight="768"
@@ -31,6 +32,30 @@
              mc:Ignorable="d">
     <UserControl.Resources>
 
+        <mux:CommandBarFlyout x:Name="ContextMenu">
+            <AppBarButton x:Name="PasteCommandButton"
+                          x:Uid="PasteCommandButton"
+                          Click="_PasteCommandHandler"
+                          Icon="Paste" />
+        </mux:CommandBarFlyout>
+
+        <mux:CommandBarFlyout x:Name="SelectionContextMenu">
+            <AppBarButton x:Name="CopyCommandButton"
+                          x:Uid="CopyCommandButton"
+                          Click="_CopyCommandHandler"
+                          Icon="Copy" />
+            <AppBarButton x:Name="PasteWithSelectionCommandButton"
+                          x:Uid="PasteWithSelectionCommandButton"
+                          Click="_PasteCommandHandler"
+                          Icon="Paste" />
+            <mux:CommandBarFlyout.SecondaryCommands>
+                <AppBarButton x:Name="SearchCommandButton"
+                              x:Uid="SearchCommandButton"
+                              Click="_SearchCommandHandler"
+                              Icon="Find" />
+            </mux:CommandBarFlyout.SecondaryCommands>
+        </mux:CommandBarFlyout>
+
         <!--
             BODGY: ControlsV2 changed the size of the scrollbars from 16dips to
             12dips. This is harder for folks to hit with the mouse, and isn't
@@ -1183,6 +1208,7 @@
         <Grid.Lights>
             <local:VisualBellLight x:Name="BellLight" />
         </Grid.Lights>
+
         <Image x:Name="BackgroundImage"
                AutomationProperties.AccessibilityView="Raw" />
         <Grid>
@@ -1202,7 +1228,6 @@
                 <SwapChainPanel x:Name="SwapChainPanel"
                                 CompositionScaleChanged="_SwapChainScaleChanged"
                                 SizeChanged="_SwapChainSizeChanged">
-
                     <Canvas x:Name="OverlayCanvas"
                             Visibility="Visible">
                         <Border x:Name="HyperlinkTooltipBorder"
diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj
index 848f79ddf01..d4560ea14a6 100644
--- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj
+++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj
@@ -25,6 +25,7 @@
 
   <PropertyGroup Label="NuGet Dependencies">
     <TerminalCppWinrt>true</TerminalCppWinrt>
+    <TerminalMUX>true</TerminalMUX>
   </PropertyGroup>
 
   <Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
diff --git a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj
index 768040fa590..82ef3b91658 100644
--- a/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj
+++ b/src/cascadia/TerminalControl/dll/TerminalControl.vcxproj
@@ -25,6 +25,7 @@
 
   <PropertyGroup Label="NuGet Dependencies">
     <TerminalCppWinrt>true</TerminalCppWinrt>
+    <TerminalMUX>true</TerminalMUX>
   </PropertyGroup>
 
   <Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h
index 4d1dd8bb85b..996c580e7b2 100644
--- a/src/cascadia/TerminalControl/pch.h
+++ b/src/cascadia/TerminalControl/pch.h
@@ -51,6 +51,10 @@
 #include <winrt/Windows.Storage.Streams.h>
 #include <winrt/Windows.UI.Xaml.Shapes.h>
 
+#include <winrt/Microsoft.UI.Xaml.Controls.h>
+#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
+#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
+
 #include <winrt/Microsoft.Terminal.TerminalConnection.h>
 #include <winrt/Microsoft.Terminal.Core.h>
 
diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
index 73d7cd80009..92348a4581c 100644
--- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h
+++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
@@ -80,6 +80,7 @@ Author(s):
     X(hstring, TabTitle, "tabTitle")                                                                                                                           \
     X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible)                                                                                            \
     X(bool, UseAtlasEngine, "useAtlasEngine", Feature_AtlasEngine::IsEnabled())                                                                                \
+    X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false)                                                                                \
     X(Windows::Foundation::Collections::IVector<winrt::hstring>, BellSound, "bellSound", nullptr)                                                              \
     X(bool, Elevate, "elevate", false)                                                                                                                         \
     X(bool, VtPassthrough, "experimental.connection.passthroughMode", false)                                                                                   \
diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl
index 608dfc5f914..0f6bf14921a 100644
--- a/src/cascadia/TerminalSettingsModel/Profile.idl
+++ b/src/cascadia/TerminalSettingsModel/Profile.idl
@@ -87,5 +87,7 @@ namespace Microsoft.Terminal.Settings.Model
         INHERITABLE_PROFILE_SETTING(Boolean, Elevate);
         INHERITABLE_PROFILE_SETTING(Boolean, AutoMarkPrompts);
         INHERITABLE_PROFILE_SETTING(Boolean, ShowMarks);
+
+        INHERITABLE_PROFILE_SETTING(Boolean, RightClickContextMenu);
     }
 }
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
index 6f54d46b794..091b33e444b 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
@@ -309,6 +309,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         _Elevate = profile.Elevate();
         _AutoMarkPrompts = Feature_ScrollbarMarks::IsEnabled() && profile.AutoMarkPrompts();
         _ShowMarks = Feature_ScrollbarMarks::IsEnabled() && profile.ShowMarks();
+
+        _RightClickContextMenu = profile.RightClickContextMenu();
     }
 
     // Method Description:
diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h
index dafdf6bf787..cb43e24eb00 100644
--- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h
+++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h
@@ -162,6 +162,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
 
         INHERITABLE_SETTING(Model::TerminalSettings, bool, AutoMarkPrompts, false);
         INHERITABLE_SETTING(Model::TerminalSettings, bool, ShowMarks, false);
+        INHERITABLE_SETTING(Model::TerminalSettings, bool, RightClickContextMenu, false);
 
     private:
         std::optional<std::array<Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE>> _ColorTable;
diff --git a/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj b/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj
index 1aba74045b3..76858394072 100644
--- a/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj
+++ b/src/cascadia/UnitTests_Control/Control.UnitTests.vcxproj
@@ -12,6 +12,7 @@
 
   <PropertyGroup Label="NuGet Dependencies">
     <TerminalCppWinrt>true</TerminalCppWinrt>
+    <TerminalMUX>true</TerminalMUX>
   </PropertyGroup>
 
   <Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h
index 38e6da14bdc..4d2bec9e209 100644
--- a/src/cascadia/inc/ControlProperties.h
+++ b/src/cascadia/inc/ControlProperties.h
@@ -73,4 +73,5 @@
     X(bool, SoftwareRendering, false)                                                                                                                    \
     X(bool, UseAtlasEngine, false)                                                                                                                       \
     X(bool, UseBackgroundImageForWindow, false)                                                                                                          \
-    X(bool, ShowMarks, false)
+    X(bool, ShowMarks, false)                                                                                                                            \
+    X(bool, RightClickContextMenu, false)


Test patch: