private void TerminalSubclassWndProc()

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;
            }
        }