packages feed

hmp3-ng-2.19.0: app/Main.hs

-- Copyright (c) Don Stewart 2004-2008.
-- Copyright (c) Tuomo Valkonen 2004.
-- Copyright (c) 2019-2026 Galen Huntington
-- SPDX-License-Identifier: GPL-2.0-or-later

module Main where

import Base

import Core     (start, shutdown, Options(..))
import Config qualified
import Keymap   (keyLoop)
import Playlist (buildPlaylist, isEmpty)

import System.Posix.Signals (installHandler, Handler(Ignore, Default, Catch),
                             sigTERM, sigPIPE, sigINT, sigHUP , sigALRM, sigABRT)

import Data.ByteString.UTF8 qualified as UTF8

import Options.Applicative

-- ---------------------------------------------------------------------
-- | Set up the signal handlers

initSignals :: IO ()
initSignals = do
    -- ignore
    for_ [sigPIPE, sigALRM] \sig ->
        installHandler sig Ignore Nothing
    -- and exit if we get the following
    for_ [sigINT, sigHUP, sigABRT, sigTERM] \sig ->
        installHandler sig (Catch exitHandler) Nothing

exitHandler :: IO ()
exitHandler = do
    releaseSignals  -- in case shutdown itself gets stuck
    shutdown $ Just "Killed"

releaseSignals :: IO ()
releaseSignals =
    for_ [sigINT, sigPIPE, sigHUP, sigABRT, sigTERM] \sig ->
        installHandler sig Default Nothing

------------------------------------------------------------------------
-- | Command-line parsing.

-- | The options together with the file/directory arguments.
invocation :: Parser (Options, [ByteString])
invocation = (,) <$> opts <*> files
  where
    opts = Options
        <$> switch
            (long "paused" <> short 'P' <> help "Start in a paused state")
        <*> optional (strOption -- temporarily internal since feature needs work
            (long "config" <> short 'c' <> metavar "FILE" <> internal
                <> help "Read this config file instead of the XDG default"))
        <*> optional (option (maybeReader prefixMatch) (
            long "mode" <> short 'm' <> metavar "MODE"
                <> help "Initial play mode (default: last selected, or once)"))
        <*> option auto (
            long "history" <> short 'h' <> metavar "NUM" <> value 61
                <> help "Size of play history, up to 61 selectable" <> showDefault)
    files = some $ argument (UTF8.fromString <$> str) (metavar "FILE|DIR...")

parserInfo :: ParserInfo (Options, [ByteString])
parserInfo = info (invocation <**> versionOpt <**> helper) $
       fullDesc
    <> header Config.versinfo
    <> progDesc "Play mp3 files in a curses interface."
  where
    versionOpt = infoOption Config.versinfo
        (hidden <> long "version" <> short 'V' <> help "Show version information")

-- XXX should this have tests?
prefixMatch :: (Enum a, Bounded a, Show a) => String -> Maybe a
prefixMatch s =
    case [ x | x <- [minBound .. maxBound], s' `isPrefixOf` map toLower (show x) ] of
        [x] -> Just x
        _   -> Nothing
    where s' = map toLower s

------------------------------------------------------------------------

main :: IO ()
main = do
    (opts, args) <- customExecParser (prefs showHelpOnEmpty) parserInfo
    list <- buildPlaylist args
    when (isEmpty list) $
        errorWithoutStackTrace "Error: No music files found."
    initSignals
    err <- either id absurd <$> try @SomeException do
        start opts list
        keyLoop
    shutdown $ Just $ "Error: " ++ show err