in sources/Google.Solutions.Terminal/Controls/VirtualTerminal.cs [706:1121]
private void TerminalSubclassWndProc(ref Message m)
{
bool IsAcceleratorForCopyingCurrentSelection(Keys key)
{
if (ModifierKeys == Keys.None && key == Keys.Enter)
{
//
// Consistent with the classic Windows console, treat
// Enter as a "copy" command.
//
return true;
}
else if (ModifierKeys == Keys.Control && key == Keys.Insert)
{
return this.EnableCtrlInsert;
}
else if (ModifierKeys == Keys.Control && key == Keys.C)
{
//
// NB. Powershell handles Ctrl+C itself, but cmd and bash
// don't.
//
return this.EnableCtrlC;
}
else
{
return false;
};
}
bool IsAcceleratorForPasting(Keys key)
{
if (ModifierKeys == Keys.Shift && key == Keys.Insert)
{
return this.EnableShiftInsert;
}
else if (ModifierKeys == Keys.Control && key == Keys.V)
{
return this.EnableCtrlV;
}
else
{
return false;
};
}
bool IsAcceleratorForScrollingToTop(Keys key)
{
if (ModifierKeys == Keys.Control && key == Keys.Home)
{
return this.EnableCtrlHomeEnd;
}
else
{
return false;
};
}
bool IsAcceleratorForScrollingToBottom(Keys key)
{
if (ModifierKeys == Keys.Control && key == Keys.End)
{
return this.EnableCtrlHomeEnd;
}
else
{
return false;
};
}
bool IsAcceleratorForScrollingUpOnePage(Keys key)
{
if (ModifierKeys == Keys.Control && key == Keys.PageUp)
{
return this.EnableCtrlPageUpDown;
}
else
{
return false;
};
}
bool IsAcceleratorForScrollingDownOnePage(Keys key)
{
if (ModifierKeys == Keys.Control && key == Keys.PageDown)
{
return this.EnableCtrlPageUpDown;
}
else
{
return false;
};
}
/// <summary>
/// Scroll the terminal up or down by a certain number of lines.
/// </summary>
void ScrollTerminal(int linesDelta)
{
var currentValue = this.scrollBar.Value;
if (linesDelta > 0)
{
//
// Scrolling up.
//
this.scrollBar.Value = Math.Max(
this.scrollBar.Minimum,
currentValue - linesDelta);
}
else
{
//
// Scrolling down.
//
this.scrollBar.Value = Math.Min(
this.scrollBar.Maximum,
currentValue - linesDelta);
}
}
Debug.Assert(!this.DesignMode);
var msgId = (WindowMessage)m.Msg;
if (msgId == WindowMessage.WM_DESTROY)
{
//
// Pass through and set a flag.
//
// From this point on, it's no longer safe to make
// any further TerminalXxx P/Invoke calls. In
// particular, we must ignore the WM_KILLFOCUS
// message that might be coming next.
//
this.currentSubclassedMessage = null;
this.terminalWindowDestructionBegun = true;
SubclassCallback.DefaultWndProc(ref m);
return;
}
else if (this.terminalWindowDestructionBegun)
{
//
// Pass through and skip all subclass logic.
//
this.currentSubclassedMessage = null;
SubclassCallback.DefaultWndProc(ref m);
return;
}
else
{
this.currentSubclassedMessage = msgId;
}
var terminalHandle = Invariant.ExpectNotNull(this.terminal, "Terminal");
switch (msgId)
{
case WindowMessage.WM_SETFOCUS:
{
NativeMethods.TerminalSetFocus(terminalHandle);
this.caretBlinkTimer.Start();
break;
}
case WindowMessage.WM_KILLFOCUS:
{
NativeMethods.TerminalKillFocus(terminalHandle);
this.caretBlinkTimer.Stop();
NativeMethods.TerminalSetCursorVisible(terminalHandle, false);
break;
}
case WindowMessage.WM_MOUSEACTIVATE:
{
Focus();
NativeMethods.TerminalSetFocus(terminalHandle);
break;
}
case WindowMessage.WM_SYSKEYDOWN:
case WindowMessage.WM_KEYDOWN:
{
var keyParams = new WmKeyUpDownParams(m);
NativeMethods.TerminalSetCursorVisible(terminalHandle, true);
this.caretBlinkTimer.Start();
if (IsAcceleratorForCopyingCurrentSelection((Keys)keyParams.VirtualKey) &&
NativeMethods.TerminalIsSelectionActive(terminalHandle))
{
//
// Begin "copy" operation.
//
// Cache the selected text so that we can process it
// in WM_KEYUP.
//
// NB. We must not pass this key event to the terminal.
//
this.ignoreWmCharBecauseOfAccelerator = true;
this.selectionToCopyInKeyUp =
NativeMethods.TerminalGetSelection(terminalHandle);
}
else if (IsAcceleratorForPasting((Keys)keyParams.VirtualKey))
{
//
// We'll handle the paste in WM_KEYUP.
//
// NB. We must not pass this key event to the terminal.
//
this.ignoreWmCharBecauseOfAccelerator = true;
}
else if (IsAcceleratorForScrollingToTop((Keys)keyParams.VirtualKey))
{
this.scrollBar.Value = 0;
}
else if (IsAcceleratorForScrollingToBottom((Keys)keyParams.VirtualKey))
{
this.scrollBar.Value = this.scrollBar.Maximum;
}
else if (IsAcceleratorForScrollingUpOnePage((Keys)keyParams.VirtualKey))
{
ScrollTerminal(this.Dimensions.Height);
}
else if (IsAcceleratorForScrollingDownOnePage((Keys)keyParams.VirtualKey))
{
ScrollTerminal(-this.Dimensions.Height);
}
else
{
NativeMethods.TerminalSendKeyEvent(
terminalHandle,
keyParams.VirtualKey,
keyParams.ScanCode,
keyParams.Flags,
true);
}
break;
}
case WindowMessage.WM_SYSKEYUP:
case WindowMessage.WM_KEYUP:
{
var keyParams = new WmKeyUpDownParams(m);
if (IsAcceleratorForCopyingCurrentSelection((Keys)keyParams.VirtualKey) &&
this.selectionToCopyInKeyUp != null)
{
//
// Continue the "copy" operation begun in WM_KEYDOWN.
//
// NB. We must not pass this key event to the
// terminal.
//
try
{
if (!string.IsNullOrWhiteSpace(this.selectionToCopyInKeyUp))
{
ClipboardUtil.SetText(this.selectionToCopyInKeyUp);
}
}
catch (ExternalException)
{
//
// Clipboard busy, ignore.
//
}
NativeMethods.TerminalClearSelection(terminalHandle);
this.selectionToCopyInKeyUp = null;
}
else if (IsAcceleratorForPasting((Keys)keyParams.VirtualKey))
{
try
{
var contents = Clipboard.GetText();
if (!string.IsNullOrWhiteSpace(contents))
{
OnUserInput(SanitizeTextForPasting(contents));
}
}
catch (ExternalException)
{
//
// Clipboard busy, ignore.
//
}
}
else if (
IsAcceleratorForScrollingToTop((Keys)keyParams.VirtualKey) ||
IsAcceleratorForScrollingToBottom((Keys)keyParams.VirtualKey) ||
IsAcceleratorForScrollingUpOnePage((Keys)keyParams.VirtualKey) ||
IsAcceleratorForScrollingDownOnePage((Keys)keyParams.VirtualKey))
{
//
// We handled these in WM_KEYUP already, so don't pass a stray
// WM_KEYDOWN to the terminal.
//
}
else
{
NativeMethods.TerminalSendKeyEvent(
terminalHandle,
keyParams.VirtualKey,
keyParams.ScanCode,
keyParams.Flags,
false);
}
break;
}
case WindowMessage.WM_CHAR:
{
if (this.ignoreWmCharBecauseOfAccelerator)
{
//
// Ignore because it's part of an accelerator.
//
this.ignoreWmCharBecauseOfAccelerator = false;
}
else
{
var charParams = new WmCharParams(m);
NativeMethods.TerminalSendCharEvent(
terminalHandle,
charParams.Character,
charParams.ScanCode,
charParams.Flags);
}
break;
}
case WindowMessage.WM_LBUTTONDOWN:
case WindowMessage.WM_RBUTTONDOWN:
{
if (NativeMethods.TerminalIsSelectionActive(terminalHandle))
{
//
// Get (and clear) selected text.
//
var selection = NativeMethods.TerminalGetSelection(terminalHandle);
if (string.IsNullOrWhiteSpace(selection))
{
//
// Clear clipboard instead of copying some whitespace.
// This is concistent with how the Windows console
// handles this case.
//
ClipboardUtil.Clear();
}
else
{
ClipboardUtil.SetText(selection);
}
}
//
// Continue processing message.
//
// NB. In case of WM_RBUTTONDOWN, the terminal will cause
// the copied text to be pasted right away.
//
goto default;
}
case WindowMessage.WM_MOUSEWHEEL:
{
//
// The hi-word contains the the distance, in multiples
// of 120.
//
var delta = (short)(((long)m.WParam) >> 16);
if (Control.ModifierKeys.HasFlag(Keys.Control))
{
//
// Control key pressed -> Zoom.
//
// We only need the sign of the delta to know
// whether to zoom in or out.
//
var oldFont = this.Font;
var newFontSize = (delta > 0)
? Math.Min(this.Font.Size + 1, MaximumFontSizeForScrolling)
: Math.Max(this.Font.Size - 1, MinimumFontSizeForScrolling);
this.Font = new Font(
this.Font.FontFamily,
newFontSize);
oldFont.Dispose();
}
else
{
//
// Control key not pressed -> scroll.
//
// Translate delta to the number of lines (+/-) to scroll.
//
var linesDelta = delta / 120 *
Math.Max(SystemInformation.MouseWheelScrollLines, 1);
ScrollTerminal(linesDelta);
}
break;
}
default:
SubclassCallback.DefaultWndProc(ref m);
break;
}
}