    {-# LANGUAGE RecordWildCards, CPP #-}
    -- | This module provides a function to build an 'Input' for Unix
    -- terminal devices.
    --
    -- This module is exposed for testing purposes only; applications should
    -- never need to import this directly.
    --
    -- Note that due to the evolution of terminal emulators, some keys
    -- and combinations will not reliably map to the expected events by
    -- any terminal program. There is no 1:1 mapping from key events to
    -- bytes read from the terminal input device. In very limited cases the
    -- terminal and vty's input process can be customized to resolve these
    -- issues; see "Graphics.Vty.Config" to learn how to configure Vty's
    -- input processing.
    --
    -- = @vty-unix@ Implementation
    --
    -- There are two input modes:
    --
    --  1. 7-bit
    --
    --  2. 8-bit
    --
    -- The 7-bit input mode is the default and the expected mode in most use
    -- cases. This is what Vty uses.
    --
    -- == 7-bit input encoding
    --
    -- Control key combinations are represented by masking the two high bits
    -- of the 7-bit input. Historically the control key actually grounded
    -- the two high bit wires: 6 and 7. This is why control key combos
    -- map to single character events: the input bytes are identical. The
    -- input byte is the bit encoding of the character with bits 6 and 7
    -- masked. Bit 6 is set by shift. Bit 6 and 7 are masked by control. For
    -- example,
    --
    -- * Control-I is 'i', `01101001`, and has bit 6 and 7 masked to become
    -- `00001001`, which is the ASCII and UTF-8 encoding of the Tab key.
    --
    -- * Control+Shift-C is 'C', `01000011`, with bit 6 and 7 set to zero
    -- which is `0000011` and is the "End of Text" code.
    --
    -- * Hypothesis: This is why capital-A, 'A', has value 65 in ASCII: this
    -- is the value 1 with bit 7 set and 6 unset.
    --
    -- * Hypothesis: Bit 6 is unset by upper case letters because,
    -- initially, there were only upper case letters used and a 5 bit
    -- encoding.
    --
    -- == 8-bit encoding
    --
    -- The 8th bit was originally used for parity checking which is useless
    -- for terminal emulators. Some terminal emulators support an 8-bit
    -- input encoding. While this provides some advantages, the actual usage
    -- is low. Most systems use 7-bit mode but recognize 8-bit control
    -- characters when escaped. This is what Vty does.
    --
    -- == Escaped Control Keys
    --
    -- Using 7-bit input encoding, the @ESC@ byte can signal the start of
    -- an encoded control key. To differentiate a single @ESC@ event from a
    -- control key, the timing of the input is used.
    --
    -- 1. @ESC@ individually: @ESC@ byte; no bytes following for a period of
    -- 'VMIN' milliseconds.
    --
    -- 2. Control keys that contain @ESC@ in their encoding: The @ESC byte
    -- is followed by more bytes read within 'VMIN' milliseconds. All bytes
    -- up until the next valid input block are passed to the classifier.
    --
    -- If the current runtime is the threaded runtime then the terminal's
    -- @VMIN@ and @VTIME@ behavior reliably implement the above rules. If
    -- the current runtime does not support 'forkOS' then there is currently
    -- no implementation.
    --
    -- == Unicode Input and Escaped Control Key Sequences
    --
    -- The input encoding determines how UTF-8 encoded characters are
    -- recognized.
    --
    -- * 7-bit mode: UTF-8 can be input unambiguously. UTF-8 input is
    -- a superset of ASCII. UTF-8 does not overlap escaped control key
    -- sequences. However, the escape key must be differentiated from
    -- escaped control key sequences by the timing of the input bytes.
    --
    -- * 8-bit mode: UTF-8 cannot be input unambiguously. This does not
    -- require using the timing of input bytes to differentiate the escape
    -- key. Many terminals do not support 8-bit mode.
    --
    -- == Terminfo
    --
    -- The terminfo system is used to determine how some keys are encoded.
    -- Terminfo is incomplete and in some cases terminfo is incorrect. Vty
    -- assumes terminfo is correct but provides a mechanism to override
    -- terminfo; see "Graphics.Vty.Config", specifically 'inputOverrides'.
    --
    -- == Terminal Input is Broken
    --
    -- Clearly terminal input has fundamental issues. There is no easy way
    -- to reliably resolve these issues.
    --
    -- One resolution would be to ditch standard terminal interfaces
    -- entirely and just go directly to scancodes. This would be a
    -- reasonable option for Vty if everybody used the linux kernel console
    -- but for obvious reasons this is not possible.
    --
    -- The "Graphics.Vty.Config" module supports customizing the
    -- input-byte-to-event mapping and xterm supports customizing the
    -- scancode-to-input-byte mapping. With a lot of work a user's system
    -- can be set up to encode all the key combos in an almost-sane manner.
    --
    -- == See also
    --
    -- * http://www.leonerd.org.uk/hacks/fixterms/
    module Graphics.Vty.Platform.Unix.Input
      ( buildInput
      , attributeControl
      )
    where

    import Control.Concurrent.STM
    #if !MIN_VERSION_base(4,11,0)
    import Data.Monoid ((<>))
    #endif
    import qualified System.Console.Terminfo as Terminfo
    import System.Posix.Signals.Exts
    import System.Posix.Terminal
    import System.Posix.Types (Fd(..))

    import Graphics.Vty.Input
    import Graphics.Vty.Config (VtyUserConfig(..))

    import Graphics.Vty.Platform.Unix.Settings
    import Graphics.Vty.Platform.Unix.Input.Loop
    import Graphics.Vty.Platform.Unix.Input.Terminfo (classifyMapForTerm)

    buildInput :: VtyUserConfig -> UnixSettings -> IO Input
    buildInput :: VtyUserConfig -> UnixSettings -> IO Input
    buildInput VtyUserConfig
    userConfig UnixSettings
    settings = do
        let tName :: String
    tName = UnixSettings -> String
    settingTermName UnixSettings
    settings
            fd :: Fd
    fd = UnixSettings -> Fd
    settingInputFd UnixSettings
    settings

        terminal <- String -> IO Terminal
    Terminfo.setupTerm String
    tName
        let inputOverrides = [(String
    s,Event
    e) | (Maybe String
    t,String
    s,Event
    e) <- VtyUserConfig -> InputMap
    configInputMap VtyUserConfig
    userConfig, Maybe String
    t Maybe String -> Maybe String -> Bool
    forall a. Eq a => a -> a -> Bool
    == Maybe String
    forall a. Maybe a
    Nothing Bool -> Bool -> Bool
    || Maybe String
    t Maybe String -> Maybe String -> Bool
    forall a. Eq a => a -> a -> Bool
    == String -> Maybe String
    forall a. a -> Maybe a
    Just String
    tName]
            activeInputMap = String -> Terminal -> [(String, Event)]
    classifyMapForTerm String
    tName Terminal
    terminal [(String, Event)] -> [(String, Event)] -> [(String, Event)]
    forall a. Monoid a => a -> a -> a
    `mappend` [(String, Event)]
    inputOverrides
        (setAttrs, unsetAttrs) <- attributeControl fd
        setAttrs
        input <- initInput settings activeInputMap
        let pokeIO = IO () -> Handler
    Catch (IO () -> Handler) -> IO () -> Handler
    forall a b. (a -> b) -> a -> b
    $ do
                IO ()
    setAttrs
                STM () -> IO ()
    forall a. STM a -> IO a
    atomically (STM () -> IO ()) -> STM () -> IO ()
    forall a b. (a -> b) -> a -> b
    $ TChan InternalEvent -> InternalEvent -> STM ()
    forall a. TChan a -> a -> STM ()
    writeTChan (Input -> TChan InternalEvent
    eventChannel Input
    input) InternalEvent
    ResumeAfterInterrupt
        _ <- installHandler windowChange pokeIO Nothing
        _ <- installHandler continueProcess pokeIO Nothing

        let restore = IO ()
    unsetAttrs

        return $ input
            { shutdownInput = do
                shutdownInput input
                _ <- installHandler windowChange Ignore Nothing
                _ <- installHandler continueProcess Ignore Nothing
                restore
            , restoreInputState = restoreInputState input >> restore
            }

    -- | Construct two IO actions: one to configure the terminal for Vty and
    -- one to restore the terminal mode flags to the values they had at the
    -- time this function was called.
    --
    -- This function constructs a configuration action to clear the
    -- following terminal mode flags:
    --
    -- * IXON disabled: disables software flow control on outgoing data.
    -- This stops the process from being suspended if the output terminal
    -- cannot keep up.
    --
    -- * Raw mode is used for input.
    --
    -- * ISIG (enables keyboard combinations that result in
    -- signals)
    --
    -- * ECHO (input is not echoed to the output)
    --
    -- * ICANON (canonical mode (line mode) input is not used)
    --
    -- * IEXTEN (extended functions are disabled)
    --
    -- The configuration action also explicitly sets these flags:
    --
    -- * ICRNL (input carriage returns are mapped to newlines)
    attributeControl :: Fd -> IO (IO (), IO ())
    attributeControl :: Fd -> IO (IO (), IO ())
    attributeControl Fd
    fd = do
        original <- Fd -> IO TerminalAttributes
    getTerminalAttributes Fd
    fd
        let vtyMode = (TerminalAttributes -> TerminalMode -> TerminalAttributes)
    -> TerminalAttributes -> [TerminalMode] -> TerminalAttributes
    forall b a. (b -> a -> b) -> b -> [a] -> b
    forall (t :: * -> *) b a.
    Foldable t =>
    (b -> a -> b) -> b -> t a -> b
    foldl TerminalAttributes -> TerminalMode -> TerminalAttributes
    withMode TerminalAttributes
    clearedFlags [TerminalMode]
    flagsToSet
            clearedFlags = (TerminalAttributes -> TerminalMode -> TerminalAttributes)
    -> TerminalAttributes -> [TerminalMode] -> TerminalAttributes
    forall b a. (b -> a -> b) -> b -> [a] -> b
    forall (t :: * -> *) b a.
    Foldable t =>
    (b -> a -> b) -> b -> t a -> b
    foldl TerminalAttributes -> TerminalMode -> TerminalAttributes
    withoutMode TerminalAttributes
    original [TerminalMode]
    flagsToUnset
            flagsToSet = [ TerminalMode
    MapCRtoLF -- ICRNL
                         ]
            flagsToUnset = [ TerminalMode
    StartStopOutput -- IXON
                           , TerminalMode
    KeyboardInterrupts -- ISIG
                           , TerminalMode
    EnableEcho -- ECHO
                           , TerminalMode
    ProcessInput -- ICANON
                           , TerminalMode
    ExtendedFunctions -- IEXTEN
                           ]
        let setAttrs = Fd -> TerminalAttributes -> TerminalState -> IO ()
    setTerminalAttributes Fd
    fd TerminalAttributes
    vtyMode TerminalState
    Immediately
            unsetAttrs = Fd -> TerminalAttributes -> TerminalState -> IO ()
    setTerminalAttributes Fd
    fd TerminalAttributes
    original TerminalState
    Immediately
        return (setAttrs, unsetAttrs)