    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Util.SpawnOnce
    -- Description :  A module for spawning a command once, and only once.
    -- Copyright   :  (c) Spencer Janssen 2009
    -- License     :  BSD3-style (see LICENSE)
    --
    -- Maintainer  :  spencerjanssen@gmail.com
    -- Stability   :  unstable
    -- Portability :  not portable
    --
    -- A module for spawning a command once, and only once.  Useful to start
    -- status bars and make session settings inside startupHook. See also
    -- "XMonad.Util.SessionStart" for a different and more flexible way to
    -- run commands only on first startup.
    --
    -----------------------------------------------------------------------------

    module XMonad.Util.SpawnOnce (spawnOnce,
                                  -- * 'SpawnOn' helpers
                                  -- $spawnon
                                  manageSpawn,
                                  spawnOnOnce,
                                  spawnNOnOnce,
                                  spawnAndDoOnce) where

    import XMonad
    import XMonad.Actions.SpawnOn
    import Data.Set as Set
    import qualified XMonad.Util.ExtensibleState as XS
    import XMonad.Prelude

    newtype SpawnOnce = SpawnOnce { SpawnOnce -> Set String
    unspawnOnce :: Set String }
        deriving (ReadPrec [SpawnOnce]
    ReadPrec SpawnOnce
    Int -> ReadS SpawnOnce
    ReadS [SpawnOnce]
    (Int -> ReadS SpawnOnce)
    -> ReadS [SpawnOnce]
    -> ReadPrec SpawnOnce
    -> ReadPrec [SpawnOnce]
    -> Read SpawnOnce
    forall a.
    (Int -> ReadS a)
    -> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
    $creadsPrec :: Int -> ReadS SpawnOnce
    readsPrec :: Int -> ReadS SpawnOnce
    $creadList :: ReadS [SpawnOnce]
    readList :: ReadS [SpawnOnce]
    $creadPrec :: ReadPrec SpawnOnce
    readPrec :: ReadPrec SpawnOnce
    $creadListPrec :: ReadPrec [SpawnOnce]
    readListPrec :: ReadPrec [SpawnOnce]
    Read, Int -> SpawnOnce -> ShowS
    [SpawnOnce] -> ShowS
    SpawnOnce -> String
    (Int -> SpawnOnce -> ShowS)
    -> (SpawnOnce -> String)
    -> ([SpawnOnce] -> ShowS)
    -> Show SpawnOnce
    forall a.
    (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
    $cshowsPrec :: Int -> SpawnOnce -> ShowS
    showsPrec :: Int -> SpawnOnce -> ShowS
    $cshow :: SpawnOnce -> String
    show :: SpawnOnce -> String
    $cshowList :: [SpawnOnce] -> ShowS
    showList :: [SpawnOnce] -> ShowS
    Show)

    instance ExtensionClass SpawnOnce where
        initialValue :: SpawnOnce
    initialValue = Set String -> SpawnOnce
    SpawnOnce Set String
    forall a. Set a
    Set.empty
        extensionType :: SpawnOnce -> StateExtension
    extensionType = SpawnOnce -> StateExtension
    forall a. (Read a, Show a, ExtensionClass a) => a -> StateExtension
    PersistentExtension

    doOnce :: (String -> X ()) -> String -> X ()
    doOnce :: (String -> X ()) -> String -> X ()
    doOnce String -> X ()
    f String
    s = do
        b <- (SpawnOnce -> Bool) -> X Bool
    forall a (m :: * -> *) b.
    (ExtensionClass a, XLike m) =>
    (a -> b) -> m b
    XS.gets (String -> Set String -> Bool
    forall a. Ord a => a -> Set a -> Bool
    Set.member String
    s (Set String -> Bool)
    -> (SpawnOnce -> Set String) -> SpawnOnce -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . SpawnOnce -> Set String
    unspawnOnce)
        unless b $ do
            f s
            XS.modify (SpawnOnce . Set.insert s . unspawnOnce)

    -- | The first time 'spawnOnce' is executed on a particular command,
    -- that command is executed.  Subsequent invocations for a command do
    -- nothing.
    spawnOnce :: String -> X ()
    spawnOnce :: String -> X ()
    spawnOnce = (String -> X ()) -> String -> X ()
    doOnce String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn

    -- $spawnon
    -- These functions combine 'spawnOnce' with their relatives in
    -- "XMonad.Actions.SpawnOn". You must add 'manageSpawn' to your
    -- @manageHook@ for them to work, as with @SpawnOn@.

    -- | Like 'spawnOnce' but launches the application on the given workspace.
    spawnOnOnce :: WorkspaceId -> String -> X ()
    spawnOnOnce :: String -> String -> X ()
    spawnOnOnce String
    ws = (String -> X ()) -> String -> X ()
    doOnce (String -> String -> X ()
    spawnOn String
    ws)

    -- | Lanch the given application n times on the specified
    -- workspace. Subsequent attempts to spawn this application will be
    -- ignored.
    spawnNOnOnce :: Int -> WorkspaceId -> String -> X ()
    spawnNOnOnce :: Int -> String -> String -> X ()
    spawnNOnOnce Int
    n String
    ws = (String -> X ()) -> String -> X ()
    doOnce (Int -> X () -> X ()
    forall (m :: * -> *) a. Applicative m => Int -> m a -> m ()
    replicateM_ Int
    n (X () -> X ()) -> (String -> X ()) -> String -> X ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> String -> X ()
    spawnOn String
    ws)

    -- | Spawn the application once and apply the manage hook. Subsequent
    -- attempts to spawn this application will be ignored.
    spawnAndDoOnce :: ManageHook -> String -> X ()
    spawnAndDoOnce :: ManageHook -> String -> X ()
    spawnAndDoOnce ManageHook
    mh = (String -> X ()) -> String -> X ()
    doOnce (ManageHook -> String -> X ()
    spawnAndDo ManageHook
    mh)