packages feed

patat-0.14.2.0: lib/Patat/Images/WezTerm.hs

--------------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

module Patat.Images.WezTerm
    ( backend
    ) where


--------------------------------------------------------------------------------
import           Codec.Picture           (DynamicImage,
                                          Image (imageHeight, imageWidth),
                                          decodeImage, dynamicMap)
import           Control.Exception       (throwIO)
import           Control.Monad           (unless, when)
import qualified Data.Aeson              as A
import qualified Data.ByteString         as B
import qualified Data.ByteString.Base64  as B64
import qualified Data.Text.Lazy          as TL
import           Data.Text.Lazy.Encoding (encodeUtf8)
import           Patat.Cleanup           (Cleanup)
import qualified Patat.Images.Internal   as Internal
import           System.Directory        (findExecutable)
import           System.Environment      (lookupEnv)
import           System.Process          (readProcess)


--------------------------------------------------------------------------------
backend :: Internal.Backend
backend = Internal.Backend new


--------------------------------------------------------------------------------
data Config = Config deriving (Eq)
instance A.FromJSON Config where parseJSON _ = return Config


--------------------------------------------------------------------------------
data Pane =
    Pane { paneSize     :: Size
         , paneIsActive :: Bool
         } deriving (Show)

instance A.FromJSON Pane where
    parseJSON = A.withObject "Pane" $ \o -> Pane
        <$> o A..: "size"
        <*> o A..: "is_active"


--------------------------------------------------------------------------------
data Size =
    Size { sizePixelWidth  :: Int
         , sizePixelHeight :: Int
         } deriving (Show)

instance A.FromJSON Size where
    parseJSON = A.withObject "Size" $ \o -> Size
        <$> o A..: "pixel_width"
        <*> o A..: "pixel_height"


--------------------------------------------------------------------------------
new :: Internal.Config Config -> IO Internal.Handle
new config = do
    when (config == Internal.Auto) $ do
        termProgram <- lookupEnv "TERM_PROGRAM"
        unless (termProgram == Just "WezTerm") $ throwIO $
            Internal.BackendNotSupported "TERM_PROGRAM not WezTerm"

    return Internal.Handle {Internal.hDrawImage = drawImage}


--------------------------------------------------------------------------------
drawImage :: FilePath -> IO Cleanup
drawImage path = do
    content <- B.readFile path

    wez <- wezExecutable
    resp <- fmap (encodeUtf8 . TL.pack) $ readProcess wez ["cli", "list", "--format", "json"] []
    let panes = (A.decode resp :: Maybe [Pane])

    Internal.withEscapeSequence $ do
        putStr "1337;File=inline=1;doNotMoveCursor=1;"
        case decodeImage content of
            Left _    -> pure ()
            Right img -> putStr $ wezArString (imageAspectRatio img) (activePaneAspectRatio panes)
        putStr ":"
        B.putStr (B64.encode content)
    return mempty


--------------------------------------------------------------------------------
wezArString  :: Double -> Double -> String
wezArString i p | i < p     = "width=auto;height=95%;"
                | otherwise = "width=100%;height=auto;"


--------------------------------------------------------------------------------
wezExecutable :: IO String
wezExecutable = do
    w <- findExecutable "wezterm.exe"
    case w of
        Nothing -> return "wezterm"
        Just x  -> return x


--------------------------------------------------------------------------------
imageAspectRatio  :: DynamicImage -> Double
imageAspectRatio i = imgW i / imgH i
    where
        imgH = fromIntegral . (dynamicMap imageHeight)
        imgW = fromIntegral . (dynamicMap imageWidth)


--------------------------------------------------------------------------------
paneAspectRatio :: Pane -> Double
paneAspectRatio p = paneW p / paneH p
    where
        paneH = fromIntegral . sizePixelHeight . paneSize
        paneW = fromIntegral . sizePixelWidth . paneSize


--------------------------------------------------------------------------------
activePaneAspectRatio :: Maybe [Pane] -> Double
activePaneAspectRatio Nothing = defaultAr -- This should never happen
activePaneAspectRatio (Just x) =
    case filter paneIsActive x of
        [p] -> paneAspectRatio p
        _   -> defaultAr                  -- This shouldn't either


--------------------------------------------------------------------------------
defaultAr :: Double
defaultAr = (4 / 3 :: Double)             -- Good enough for a VT100