packages feed

msu-0.2.0.0: src/MSU/Monitors.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module MSU.Monitors
    ( Monitors(..)
    , readMonitorsFileThrow
    , readMonitorsYaml
    , findMonitors

    -- * Create
    , createMonitor
    , writeMonitorsFile
    )
where

import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson
import Data.Bifunctor (first)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.List (find)
import qualified Data.Yaml as Yaml
import GHC.Generics (Generic)
import MSU.Context (Context)
import qualified MSU.Context as Context
import MSU.Match
import qualified MSU.Xrandr.Parse as Xrandr
import System.Directory (doesFileExist)

data Monitors = Monitors
    { name :: String
    , match :: MatchContext
    , exec :: String
    }
    deriving stock Generic
    deriving anyclass (FromJSON, ToJSON)

data MatchContext = MatchContext
    { displays :: Maybe (Match [String])
    , wifi :: Maybe (Match String)
    }
    deriving stock Generic
    deriving anyclass (FromJSON, ToJSON)

readMonitorsFileThrow :: MonadIO m => FilePath -> m [Monitors]
readMonitorsFileThrow = Yaml.decodeFileThrow

readMonitorsYaml :: ByteString -> Either String [Monitors]
readMonitorsYaml = first show . Yaml.decodeEither'

findMonitors :: Context -> [Monitors] -> Maybe Monitors
findMonitors context = find $ \Monitors {..} ->
    let
        connected = filter Xrandr.connected $ Context.displays context
        names = map Xrandr.name connected
        mEssid = Context.essid <$> Context.wifi context
    in displays match `matches` names && wifi match `matchesMaybe` mEssid

createMonitor :: Context -> Monitors
createMonitor context = Monitors
    { name = "created"
    , match = MatchContext
        { displays = Just $ Eq connectedNames
        , wifi = Eq . Context.essid <$> Context.wifi context
        }
    , exec = unwords $ ("xrandr" :) $ concatMap xrandrArg $ Context.displays
        context
    }
  where
    connectedNames =
        map Xrandr.name $ filter Xrandr.connected $ Context.displays context

    xrandrArg display =
        ["--output", Xrandr.name display]
            <> if Xrandr.connected display then xrandrMode display else []

    xrandrMode display = case Xrandr.modes display of
        ((x, y) : _) -> ["--mode", show x <> "x" <> show y]
        _ -> []

writeMonitorsFile :: MonadIO m => FilePath -> Monitors -> m ()
writeMonitorsFile path monitors = liftIO $ do
    exists <- doesFileExist path
    if exists
        then BS.appendFile path $ prefix <> encoded
        else BS.writeFile path encoded
  where
    prefix = "\n# TODO: created automatically\n"
    encoded = Yaml.encode [monitors]