xmonad-contrib/XMonad/Util/ActionQueue.hs
Tomas Janousek 2b5d2c5ab8 X.U.ActionQueue: Separate ExtState and ExtConf data types
Using the same type for both is confusing and requires a bogus Semigroup
instance.
2022-01-15 13:43:16 +00:00

90 lines
3.0 KiB
Haskell

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-----------------------------------------------------------------------------
-- |
-- Module : XMonad.Util.ActionQueue
-- Description : Queue of XMonad actions
-- Copyright : (c) 2021 Xiaokui Shu
-- License : BSD-style (see LICENSE)
--
-- Maintainer : subbyte@gmail.com
-- Stability : unstable
-- Portability : unportable
--
-- Put XMonad actions in the queue to be executed in either the
-- @logHook@ or another hook of your choice.
-----------------------------------------------------------------------------
module XMonad.Util.ActionQueue ( -- * Usage
-- $usage
ActionQueue
, actionQueue
, enqueue
, exequeue
) where
import XMonad
import qualified XMonad.Util.ExtensibleConf as XC
import qualified XMonad.Util.ExtensibleState as XS
import Data.Sequence (Seq (..), ViewL (..), viewl, (|>))
-- $usage
--
-- This module provides a queue that, by default, gets executed every
-- time the @logHook@ runs. To use this module
--
-- 1. Enqueue `X ()` actions at the place you need; e.g.:
--
-- > enqueue myAction
--
-- 2. Add the 'actionQueue' combinator to your configuration:
--
-- > main = xmonad $ actionQueue $ def
-- > { ... }
--
-- This will execute all of the actions in the queue (if any) every time
-- the @logHook@ runs. Developers of other extensions using this module
-- should re-export 'actionQueue'.
--
-- Alternatively, you can directly add 'exequeue' to a hook of your choice.
-- This is discouraged when writing user-facing modules, as (accidentally)
-- adding 'exequeue' to two different hooks might lead to undesirable
-- behaviour. 'actionQueue' uses the "XMonad.Util.ExtensibleConf" interface to
-- circumvent this.
--
newtype ActionQueue = ActionQueue (Seq (X ()))
instance ExtensionClass ActionQueue where
initialValue = ActionQueue mempty
newtype ActionQueueHooked = ActionQueueHooked ()
deriving newtype (Semigroup)
-- | Every time the @logHook@ runs, execute all actions in the queue.
actionQueue :: XConfig l -> XConfig l
actionQueue = XC.once (\cfg -> cfg{ logHook = logHook cfg <> exequeue })
ActionQueueHooked
-- | Enqueue an action.
enqueue :: X () -> X ()
enqueue = XS.modify . go
where
go :: X () -> ActionQueue -> ActionQueue
go a (ActionQueue as) = ActionQueue $ as |> a
-- | Execute every action in the queue.
exequeue :: X ()
exequeue = do
-- Note that we are executing all actions one by one. Otherwise, we may
-- not execute the actions in the right order. Any of them may call
-- 'refresh' or 'windows', which triggers the logHook, which may trigger
-- 'exequeue' again if it is used in the logHook.
ActionQueue aas <- XS.get
case viewl aas of
EmptyL -> pure ()
a :< as -> do XS.put (ActionQueue as)
a `catchX` pure ()
exequeue