{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ImpredicativeTypes #-}
-----------------------------------------------------------------------------
-- |
-- Module      : System.Taffybar.Context
-- Copyright   : (c) Ivan A. Malison
-- License     : BSD3-style (see LICENSE)
--
-- Maintainer  : Ivan A. Malison
-- Stability   : unstable
-- Portability : unportable
--
-- The "System.Taffybar.Context" module provides the core functionality of the
-- taffybar library. It gets its name from the 'Context' record, which stores
-- runtime information and objects, which are used by many of the widgets that
-- taffybar provides. 'Context' is typically accessed through the 'Reader'
-- interface of 'TaffyIO'.
-----------------------------------------------------------------------------

module System.Taffybar.Context
  ( -- * Configuration
    TaffybarConfig(..)
  , defaultTaffybarConfig
  , appendHook
  -- ** Bars
  , BarConfig(..)
  , BarConfigGetter
  , showBarId

  -- * Taffy monad
  , Taffy
  , TaffyIO
  -- ** Context
  , Context(..)
  , buildContext
  , buildEmptyContext
  -- ** Context State
  , getState
  , getStateDefault
  , putState

  -- * Control
  , refreshTaffyWindows
  , exitTaffybar

  -- * X11
  , runX11
  , runX11Def
  -- ** Event subscription
  , subscribeToAll
  , subscribeToPropertyEvents
  , unsubscribe

  -- * Threading
  , taffyFork
  ) where

import           Control.Arrow ((&&&), (***))
import           Control.Concurrent (forkIO)
import qualified Control.Concurrent.MVar as MV
import           Control.Exception.Enclosed (catchAny)
import           Control.Monad
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Class
import           Control.Monad.Trans.Maybe
import           Control.Monad.Trans.Reader
import qualified DBus.Client as DBus
import           Data.Data
import           Data.Default (Default(..))
import           Data.GI.Base.ManagedPtr (unsafeCastTo)
import           Data.Int
import           Data.List
import qualified Data.Map as M
import qualified Data.Text as T
import           Data.Tuple.Select
import           Data.Tuple.Sequence
import           Data.Unique
import qualified GI.Gdk
import qualified GI.GdkX11 as GdkX11
import           GI.GdkX11.Objects.X11Window
import qualified GI.Gtk as Gtk
import           Graphics.UI.GIGtkStrut
import           StatusNotifier.TransparentWindow
import           System.Log.Logger (Priority(..), logM)
import           System.Taffybar.Information.SafeX11
import           System.Taffybar.Information.X11DesktopInfo
import           System.Taffybar.Util
import           System.Taffybar.Widget.Util
import           Text.Printf
import           Unsafe.Coerce

logIO :: Priority -> String -> IO ()
logIO :: Priority -> String -> IO ()
logIO = String -> Priority -> String -> IO ()
logM String
"System.Taffybar.Context"

logC :: MonadIO m => Priority -> String -> m ()
logC :: forall (m :: * -> *). MonadIO m => Priority -> String -> m ()
logC Priority
p = IO () -> m ()
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> (String -> IO ()) -> String -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Priority -> String -> IO ()
logIO Priority
p

-- | 'Taffy' is a monad transformer that provides 'ReaderT' for 'Context'.
type Taffy m v = ReaderT Context m v

-- | 'TaffyIO' is 'IO' wrapped with a 'ReaderT' providing 'Context'. This is the
-- type of most widgets and callback in Taffybar.
type TaffyIO v = ReaderT Context IO v

type Listener = Event -> Taffy IO ()
type SubscriptionList = [(Unique, Listener)]
data Value = forall t. Typeable t => Value t

fromValue :: forall t. Typeable t => Value -> Maybe t
fromValue :: forall t. Typeable t => Value -> Maybe t
fromValue (Value t
v) =
  if t -> TypeRep
forall a. Typeable a => a -> TypeRep
typeOf t
v TypeRep -> TypeRep -> Bool
forall a. Eq a => a -> a -> Bool
== Proxy t -> TypeRep
forall {k} (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy t
forall {k} (t :: k). Proxy t
Proxy :: Proxy t) then
    t -> Maybe t
forall a. a -> Maybe a
Just (t -> Maybe t) -> t -> Maybe t
forall a b. (a -> b) -> a -> b
$ t -> t
forall a b. a -> b
unsafeCoerce t
v
  else
    Maybe t
forall a. Maybe a
Nothing

-- | 'BarConfig' specifies the configuration for a single taffybar window.
data BarConfig = BarConfig
  {
  -- | The strut configuration to use for the bar
    BarConfig -> StrutConfig
strutConfig :: StrutConfig
  -- | The amount of spacing in pixels between bar widgets
  , BarConfig -> Int32
widgetSpacing :: Int32
  -- | Constructors for widgets that should be placed at the beginning of the bar.
  , BarConfig -> [TaffyIO Widget]
startWidgets :: [TaffyIO Gtk.Widget]
  -- | Constructors for widgets that should be placed in the center of the bar.
  , BarConfig -> [TaffyIO Widget]
centerWidgets :: [TaffyIO Gtk.Widget]
  -- | Constructors for widgets that should be placed at the end of the bar.
  , BarConfig -> [TaffyIO Widget]
endWidgets :: [TaffyIO Gtk.Widget]
  -- | A unique identifier for the bar, that can be used e.g. when toggling.
  , BarConfig -> Unique
barId :: Unique
  }

instance Eq BarConfig where
  BarConfig
a == :: BarConfig -> BarConfig -> Bool
== BarConfig
b = BarConfig -> Unique
barId BarConfig
a Unique -> Unique -> Bool
forall a. Eq a => a -> a -> Bool
== BarConfig -> Unique
barId BarConfig
b

type BarConfigGetter = TaffyIO [BarConfig]

-- | 'TaffybarConfig' provides an advanced interface for configuring taffybar.
-- Through the 'getBarConfigsParam', it is possible to specify different
-- taffybar configurations depending on the number of monitors present, and even
-- to specify different taffybar configurations for each monitor.
data TaffybarConfig = TaffybarConfig
  {
  -- | An optional dbus client to use.
    TaffybarConfig -> Maybe Client
dbusClientParam :: Maybe DBus.Client
  -- | Hooks that should be executed at taffybar startup.
  , TaffybarConfig -> TaffyIO ()
startupHook :: TaffyIO ()
  -- | A 'TaffyIO' action that returns a list of 'BarConfig' where each element
  -- describes a taffybar window that should be spawned.
  , TaffybarConfig -> BarConfigGetter
getBarConfigsParam :: BarConfigGetter
  -- | A list of 'FilePath' each of which should be loaded as css files at
  -- startup.
  , TaffybarConfig -> [String]
cssPaths :: [FilePath]
  -- | A field used (only) by dyre to provide an error message.
  , TaffybarConfig -> Maybe String
errorMsg :: Maybe String
  }


-- | Append the provided 'TaffyIO' hook to the 'startupHook' of the given
-- 'TaffybarConfig'.
appendHook :: TaffyIO () -> TaffybarConfig -> TaffybarConfig
appendHook :: TaffyIO () -> TaffybarConfig -> TaffybarConfig
appendHook TaffyIO ()
hook TaffybarConfig
config = TaffybarConfig
config
  { startupHook = startupHook config >> hook }

-- | Default values for a 'TaffybarConfig'. Not usuable without at least
-- properly setting 'getBarConfigsParam'.
defaultTaffybarConfig :: TaffybarConfig
defaultTaffybarConfig :: TaffybarConfig
defaultTaffybarConfig = TaffybarConfig
  { dbusClientParam :: Maybe Client
dbusClientParam = Maybe Client
forall a. Maybe a
Nothing
  , startupHook :: TaffyIO ()
startupHook = () -> TaffyIO ()
forall a. a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  , getBarConfigsParam :: BarConfigGetter
getBarConfigsParam = [BarConfig] -> BarConfigGetter
forall a. a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return []
  , cssPaths :: [String]
cssPaths = []
  , errorMsg :: Maybe String
errorMsg = Maybe String
forall a. Maybe a
Nothing
  }

instance Default TaffybarConfig where
  def :: TaffybarConfig
def = TaffybarConfig
defaultTaffybarConfig

-- | A "Context" value holds all of the state associated with a single running
-- instance of taffybar. It is typically accessed from a widget constructor
-- through the "TaffyIO" monad transformer stack.
data Context = Context
  {
  -- | The X11Context that will be used to service X11Property requests.
    Context -> MVar X11Context
x11ContextVar :: MV.MVar X11Context
  -- | The handlers which will be evaluated against incoming X11 events.
  , Context -> MVar SubscriptionList
listeners :: MV.MVar SubscriptionList
  -- | A collection of miscellaneous pieces of state which are keyed by their
  -- types. Most new pieces of state should go here, rather than in a new field
  -- in 'Context'. State stored here is typically accessed through
  -- 'getStateDefault'.
  , Context -> MVar (Map TypeRep Value)
contextState :: MV.MVar (M.Map TypeRep Value)
  -- | Used to track the windows that taffybar is currently controlling, and
  -- which 'BarConfig' objects they are associated with.
  , Context -> MVar [(BarConfig, Window)]
existingWindows :: MV.MVar [(BarConfig, Gtk.Window)]
  -- | The shared user session 'DBus.Client'.
  , Context -> Client
sessionDBusClient :: DBus.Client
  -- | The shared system session 'DBus.Client'.
  , Context -> Client
systemDBusClient :: DBus.Client
  -- | The action that will be evaluated to get the bar configs associated with
  -- each active monitor taffybar should run on.
  , Context -> BarConfigGetter
getBarConfigs :: BarConfigGetter
  -- | Populated with the BarConfig that resulted in the creation of a given
  -- widget, when its constructor is called. This lets widgets access thing like
  -- who their neighbors are. Note that the value of 'contextBarConfig' is
  -- different for widgets belonging to bar windows on different monitors.
  , Context -> Maybe BarConfig
contextBarConfig :: Maybe BarConfig
  }

-- | Build the "Context" for a taffybar process.
buildContext :: TaffybarConfig -> IO Context
buildContext :: TaffybarConfig -> IO Context
buildContext TaffybarConfig
               { dbusClientParam :: TaffybarConfig -> Maybe Client
dbusClientParam = Maybe Client
maybeDBus
               , getBarConfigsParam :: TaffybarConfig -> BarConfigGetter
getBarConfigsParam = BarConfigGetter
barConfigGetter
               , startupHook :: TaffybarConfig -> TaffyIO ()
startupHook = TaffyIO ()
startup
               } = do
  Priority -> String -> IO ()
logIO Priority
DEBUG String
"Building context"
  dbusC <- IO Client -> (Client -> IO Client) -> Maybe Client -> IO Client
forall b a. b -> (a -> b) -> Maybe a -> b
maybe IO Client
DBus.connectSession Client -> IO Client
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe Client
maybeDBus
  sDBusC <- DBus.connectSystem
  _ <- DBus.requestName dbusC "org.taffybar.Bar"
       [DBus.nameAllowReplacement, DBus.nameReplaceExisting]
  listenersVar <- MV.newMVar []
  state <- MV.newMVar M.empty
  x11Context <- getX11Context def >>= MV.newMVar
  windowsVar <- MV.newMVar []
  let context = Context
                { x11ContextVar :: MVar X11Context
x11ContextVar = MVar X11Context
x11Context
                , listeners :: MVar SubscriptionList
listeners = MVar SubscriptionList
listenersVar
                , contextState :: MVar (Map TypeRep Value)
contextState = MVar (Map TypeRep Value)
state
                , sessionDBusClient :: Client
sessionDBusClient = Client
dbusC
                , systemDBusClient :: Client
systemDBusClient = Client
sDBusC
                , getBarConfigs :: BarConfigGetter
getBarConfigs = BarConfigGetter
barConfigGetter
                , existingWindows :: MVar [(BarConfig, Window)]
existingWindows = MVar [(BarConfig, Window)]
windowsVar
                , contextBarConfig :: Maybe BarConfig
contextBarConfig = Maybe BarConfig
forall a. Maybe a
Nothing
                }
  _ <- runMaybeT $ MaybeT GI.Gdk.displayGetDefault >>=
              (lift . GI.Gdk.displayGetDefaultScreen) >>=
              (lift . flip GI.Gdk.afterScreenMonitorsChanged
               -- XXX: We have to do a force refresh here because there is no
               -- way to reliably move windows, since the window manager can do
               -- whatever it pleases.
               (runReaderT forceRefreshTaffyWindows context))
  flip runReaderT context $ do
    logC DEBUG "Starting X11 Handler"
    startX11EventHandler
    logC DEBUG "Running startup hook"
    startup
    logC DEBUG "Queing build windows command"
    refreshTaffyWindows
  logIO DEBUG "Context build finished"
  return context

-- | Build an empty taffybar context. This function is mostly useful for
-- invoking functions that yield 'TaffyIO' values in a testing setting (e.g. in
-- a repl).
buildEmptyContext :: IO Context
buildEmptyContext :: IO Context
buildEmptyContext = TaffybarConfig -> IO Context
buildContext TaffybarConfig
forall a. Default a => a
def

-- | Format the 'barId' as a numeric string.
showBarId :: BarConfig -> String
showBarId :: BarConfig -> String
showBarId = Int -> String
forall a. Show a => a -> String
show (Int -> String) -> (BarConfig -> Int) -> BarConfig -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Unique -> Int
hashUnique (Unique -> Int) -> (BarConfig -> Unique) -> BarConfig -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BarConfig -> Unique
barId

buildBarWindow :: Context -> BarConfig -> IO Gtk.Window
buildBarWindow :: Context -> BarConfig -> IO Window
buildBarWindow Context
context BarConfig
barConfig = do
  let thisContext :: Context
thisContext = Context
context { contextBarConfig = Just barConfig }
  Priority -> String -> IO ()
forall (m :: * -> *). MonadIO m => Priority -> String -> m ()
logC Priority
INFO (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$
      String -> String -> String -> String
forall r. PrintfType r => String -> r
printf String
"Building window for Taffybar(id=%s) with %s"
      (BarConfig -> String
showBarId BarConfig
barConfig)
      (StrutConfig -> String
forall a. Show a => a -> String
show (StrutConfig -> String) -> StrutConfig -> String
forall a b. (a -> b) -> a -> b
$ BarConfig -> StrutConfig
strutConfig BarConfig
barConfig)

  window <- WindowType -> IO Window
forall (m :: * -> *).
(HasCallStack, MonadIO m) =>
WindowType -> m Window
Gtk.windowNew WindowType
Gtk.WindowTypeToplevel

  void $ Gtk.onWidgetDestroy window $ do
    let bId = BarConfig -> String
showBarId BarConfig
barConfig
    logC INFO $ printf "Window for Taffybar(id=%s) destroyed" bId
    MV.modifyMVar_ (existingWindows context) (pure . filter ((/=) window . sel2))
    logC DEBUG $ printf "Window for Taffybar(id=%s) unregistered" bId

  box <- Gtk.boxNew Gtk.OrientationHorizontal $ fromIntegral $
         widgetSpacing barConfig
  _ <- widgetSetClassGI box "taffy-box"
  centerBox <- Gtk.boxNew Gtk.OrientationHorizontal $
               fromIntegral $ widgetSpacing barConfig

  _ <- widgetSetClassGI centerBox "center-box"
  Gtk.widgetSetVexpand centerBox True
  Gtk.setWidgetValign centerBox Gtk.AlignFill
  Gtk.setWidgetHalign centerBox Gtk.AlignCenter
  Gtk.boxSetCenterWidget box (Just centerBox)

  setupStrutWindow (strutConfig barConfig) window
  Gtk.containerAdd window box

  _ <- widgetSetClassGI window "taffy-window"

  let addWidgetWith Int -> Widget -> IO ()
widgetAdd (Int
count, TaffyIO Widget
buildWidget) =
        TaffyIO Widget -> Context -> IO Widget
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT TaffyIO Widget
buildWidget Context
thisContext IO Widget -> (Widget -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> Widget -> IO ()
widgetAdd Int
count
      addToStart Int
count Widget
widget = do
        _ <- Widget -> Text -> IO Widget
forall b (m :: * -> *). (IsWidget b, MonadIO m) => b -> Text -> m b
widgetSetClassGI Widget
widget (Text -> IO Widget) -> Text -> IO Widget
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ String -> Int -> String
forall r. PrintfType r => String -> r
printf String
"left-%d" (Int
count :: Int)
        Gtk.boxPackStart box widget False False 0
      addToEnd Int
count Widget
widget = do
        _ <- Widget -> Text -> IO Widget
forall b (m :: * -> *). (IsWidget b, MonadIO m) => b -> Text -> m b
widgetSetClassGI Widget
widget (Text -> IO Widget) -> Text -> IO Widget
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ String -> Int -> String
forall r. PrintfType r => String -> r
printf String
"right-%d" (Int
count :: Int)
        Gtk.boxPackEnd box widget False False 0
      addToCenter Int
count Widget
widget = do
        _ <- Widget -> Text -> IO Widget
forall b (m :: * -> *). (IsWidget b, MonadIO m) => b -> Text -> m b
widgetSetClassGI Widget
widget (Text -> IO Widget) -> Text -> IO Widget
forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ String -> Int -> String
forall r. PrintfType r => String -> r
printf String
"center-%d" (Int
count :: Int)
        Gtk.boxPackStart centerBox widget False False 0

  logIO DEBUG "Building start widgets"
  mapM_ (addWidgetWith addToStart) $ zip [1..] (startWidgets barConfig)
  logIO DEBUG "Building center widgets"
  mapM_ (addWidgetWith addToCenter) $ zip [1..] (centerWidgets barConfig)
  logIO DEBUG "Building end widgets"
  mapM_ (addWidgetWith addToEnd) $ zip [1..] (endWidgets barConfig)

  makeWindowTransparent window

  logIO DEBUG "Showing window"
  Gtk.widgetShow window
  Gtk.widgetShow box
  Gtk.widgetShow centerBox

  runX11Context context () $ void $ runMaybeT $ do
    gdkWindow <- MaybeT $ Gtk.widgetGetWindow window
    xid <- GdkX11.x11WindowGetXid =<< liftIO (unsafeCastTo X11Window gdkWindow)
    logC DEBUG $ printf "Lowering X11 window %s" $ show xid
    lift $ doLowerWindow (fromIntegral xid)

  return window

-- | Use the "barConfigGetter" field of "Context" to get the set of taffybar
-- windows that should active. Will avoid recreating windows if there is already
-- a window with the appropriate geometry and "BarConfig".
refreshTaffyWindows :: TaffyIO ()
refreshTaffyWindows :: TaffyIO ()
refreshTaffyWindows = (IO () -> IO ()) -> TaffyIO () -> TaffyIO ()
forall (m :: * -> *) a (n :: * -> *) b r.
(m a -> n b) -> ReaderT r m a -> ReaderT r n b
mapReaderT IO () -> IO ()
postGUIASync (TaffyIO () -> TaffyIO ()) -> TaffyIO () -> TaffyIO ()
forall a b. (a -> b) -> a -> b
$ do
  Priority -> String -> TaffyIO ()
forall (m :: * -> *). MonadIO m => Priority -> String -> m ()
logC Priority
DEBUG String
"Refreshing windows"
  ctx <- ReaderT Context IO Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
  windowsVar <- asks existingWindows

  let rebuildWindows [(BarConfig, Window)]
currentWindows = (ReaderT Context IO [(BarConfig, Window)]
 -> Context -> IO [(BarConfig, Window)])
-> Context
-> ReaderT Context IO [(BarConfig, Window)]
-> IO [(BarConfig, Window)]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ReaderT Context IO [(BarConfig, Window)]
-> Context -> IO [(BarConfig, Window)]
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT Context
ctx (ReaderT Context IO [(BarConfig, Window)]
 -> IO [(BarConfig, Window)])
-> ReaderT Context IO [(BarConfig, Window)]
-> IO [(BarConfig, Window)]
forall a b. (a -> b) -> a -> b
$
        do
          barConfigs <- ReaderT Context IO BarConfigGetter -> BarConfigGetter
forall (m :: * -> *) a. Monad m => m (m a) -> m a
join (ReaderT Context IO BarConfigGetter -> BarConfigGetter)
-> ReaderT Context IO BarConfigGetter -> BarConfigGetter
forall a b. (a -> b) -> a -> b
$ (Context -> BarConfigGetter) -> ReaderT Context IO BarConfigGetter
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks Context -> BarConfigGetter
getBarConfigs

          let currentConfigs = ((BarConfig, Window) -> BarConfig)
-> [(BarConfig, Window)] -> [BarConfig]
forall a b. (a -> b) -> [a] -> [b]
map (BarConfig, Window) -> BarConfig
forall a b. Sel1 a b => a -> b
sel1 [(BarConfig, Window)]
currentWindows
              newConfs = (BarConfig -> Bool) -> [BarConfig] -> [BarConfig]
forall a. (a -> Bool) -> [a] -> [a]
filter (BarConfig -> [BarConfig] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [BarConfig]
currentConfigs) [BarConfig]
barConfigs
              (remainingWindows, removedWindows) =
                partition ((`elem` barConfigs) . sel1) currentWindows
              setPropertiesFromPair (BarConfig
barConf, Window
window) =
                StrutConfig -> Window -> m ()
forall (m :: * -> *).
(MonadFail m, MonadIO m) =>
StrutConfig -> Window -> m ()
setupStrutWindow (BarConfig -> StrutConfig
strutConfig BarConfig
barConf) Window
window

          newWindowPairs <- lift $ do
            logIO DEBUG $ printf "removedWindows: %s" $
                  show $ map (strutConfig . sel1) removedWindows
            logIO DEBUG $ printf "remainingWindows: %s" $
                  show $ map (strutConfig . sel1) remainingWindows
            logIO DEBUG $ printf "newWindows: %s" $
                  show $ map strutConfig newConfs
            logIO DEBUG $ printf "barConfigs: %s" $
                  show $ map strutConfig barConfigs

            logIO DEBUG "Removing windows"
            mapM_ (Gtk.widgetDestroy . sel2) removedWindows

            -- TODO: This should actually use the config that is provided from
            -- getBarConfigs so that the strut properties of the window can be
            -- altered.
            logIO DEBUG "Updating strut properties for existing windows"
            mapM_ setPropertiesFromPair remainingWindows

            logIO DEBUG "Constructing new windows"
            mapM (sequenceT . ((return :: a -> IO a) &&& buildBarWindow ctx))
                 newConfs

          return $ newWindowPairs ++ remainingWindows

  lift $ MV.modifyMVar_ windowsVar rebuildWindows
  logC DEBUG "Finished refreshing windows"
  return ()

-- | Unconditionally delete all existing Taffybar top-level windows.
removeTaffyWindows :: TaffyIO ()
removeTaffyWindows :: TaffyIO ()
removeTaffyWindows = (Context -> MVar [(BarConfig, Window)])
-> ReaderT Context IO (MVar [(BarConfig, Window)])
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks Context -> MVar [(BarConfig, Window)]
existingWindows ReaderT Context IO (MVar [(BarConfig, Window)])
-> (MVar [(BarConfig, Window)]
    -> ReaderT Context IO [(BarConfig, Window)])
-> ReaderT Context IO [(BarConfig, Window)]
forall a b.
ReaderT Context IO a
-> (a -> ReaderT Context IO b) -> ReaderT Context IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO [(BarConfig, Window)]
-> ReaderT Context IO [(BarConfig, Window)]
forall a. IO a -> ReaderT Context IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [(BarConfig, Window)]
 -> ReaderT Context IO [(BarConfig, Window)])
-> (MVar [(BarConfig, Window)] -> IO [(BarConfig, Window)])
-> MVar [(BarConfig, Window)]
-> ReaderT Context IO [(BarConfig, Window)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MVar [(BarConfig, Window)] -> IO [(BarConfig, Window)]
forall a. MVar a -> IO a
MV.readMVar ReaderT Context IO [(BarConfig, Window)]
-> ([(BarConfig, Window)] -> TaffyIO ()) -> TaffyIO ()
forall a b.
ReaderT Context IO a
-> (a -> ReaderT Context IO b) -> ReaderT Context IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= [(BarConfig, Window)] -> TaffyIO ()
deleteWindows
  where
    deleteWindows :: [(BarConfig, Window)] -> TaffyIO ()
deleteWindows = ((BarConfig, Window) -> ReaderT Context IO ((), ()))
-> [(BarConfig, Window)] -> TaffyIO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((TaffyIO (), TaffyIO ()) -> ReaderT Context IO ((), ())
forall a b. SequenceT a b => a -> b
sequenceT ((TaffyIO (), TaffyIO ()) -> ReaderT Context IO ((), ()))
-> ((BarConfig, Window) -> (TaffyIO (), TaffyIO ()))
-> (BarConfig, Window)
-> ReaderT Context IO ((), ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (BarConfig -> TaffyIO ()
msg (BarConfig -> TaffyIO ())
-> (Window -> TaffyIO ())
-> (BarConfig, Window)
-> (TaffyIO (), TaffyIO ())
forall b c b' c'. (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
forall (a :: * -> * -> *) b c b' c'.
Arrow a =>
a b c -> a b' c' -> a (b, b') (c, c')
*** Window -> TaffyIO ()
del))

    msg :: BarConfig -> TaffyIO ()
    msg :: BarConfig -> TaffyIO ()
msg BarConfig
barConfig = Priority -> String -> TaffyIO ()
forall (m :: * -> *). MonadIO m => Priority -> String -> m ()
logC Priority
INFO (String -> TaffyIO ()) -> String -> TaffyIO ()
forall a b. (a -> b) -> a -> b
$
      String -> String -> String
forall r. PrintfType r => String -> r
printf String
"Destroying window for Taffybar(id=%s)" (BarConfig -> String
showBarId BarConfig
barConfig)

    del :: Gtk.Window -> TaffyIO ()
    del :: Window -> TaffyIO ()
del = Window -> TaffyIO ()
forall (m :: * -> *) a.
(HasCallStack, MonadIO m, IsWidget a) =>
a -> m ()
Gtk.widgetDestroy

-- | Forcibly refresh taffybar windows, even if there are existing windows that
-- correspond to the uniques in the bar configs yielded by 'barConfigGetter'.
forceRefreshTaffyWindows :: TaffyIO ()
forceRefreshTaffyWindows :: TaffyIO ()
forceRefreshTaffyWindows = TaffyIO ()
removeTaffyWindows TaffyIO () -> TaffyIO () -> TaffyIO ()
forall a b.
ReaderT Context IO a
-> ReaderT Context IO b -> ReaderT Context IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> TaffyIO ()
refreshTaffyWindows

-- | Destroys all top-level windows belonging to Taffybar, then
-- requests the GTK main loop to exit.
--
-- This ensures that the windows disappear promptly. For GTK windows
-- to be destroyed, the main loop still needs to be running.
exitTaffybar :: Context -> IO ()
exitTaffybar :: Context -> IO ()
exitTaffybar Context
ctx = do
  IO () -> IO ()
postGUIASync (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ TaffyIO () -> Context -> IO ()
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT TaffyIO ()
removeTaffyWindows Context
ctx
  IO ()
forall (m :: * -> *). (HasCallStack, MonadIO m) => m ()
Gtk.mainQuit

asksContextVar :: (r -> MV.MVar b) -> ReaderT r IO b
asksContextVar :: forall r b. (r -> MVar b) -> ReaderT r IO b
asksContextVar r -> MVar b
getter = (r -> MVar b) -> ReaderT r IO (MVar b)
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks r -> MVar b
getter ReaderT r IO (MVar b)
-> (MVar b -> ReaderT r IO b) -> ReaderT r IO b
forall a b.
ReaderT r IO a -> (a -> ReaderT r IO b) -> ReaderT r IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO b -> ReaderT r IO b
forall (m :: * -> *) a. Monad m => m a -> ReaderT r m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (IO b -> ReaderT r IO b)
-> (MVar b -> IO b) -> MVar b -> ReaderT r IO b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MVar b -> IO b
forall a. MVar a -> IO a
MV.readMVar

-- | Run a function needing an X11 connection in 'TaffyIO'.
runX11 :: X11Property a -> TaffyIO a
runX11 :: forall a. X11Property a -> TaffyIO a
runX11 X11Property a
action =
  (Context -> MVar X11Context) -> ReaderT Context IO X11Context
forall r b. (r -> MVar b) -> ReaderT r IO b
asksContextVar Context -> MVar X11Context
x11ContextVar ReaderT Context IO X11Context
-> (X11Context -> ReaderT Context IO a) -> ReaderT Context IO a
forall a b.
ReaderT Context IO a
-> (a -> ReaderT Context IO b) -> ReaderT Context IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= IO a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => m a -> ReaderT Context m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (IO a -> ReaderT Context IO a)
-> (X11Context -> IO a) -> X11Context -> ReaderT Context IO a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. X11Property a -> X11Context -> IO a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT X11Property a
action

-- | Use 'runX11' together with 'postX11RequestSyncProp' on the provided
-- property. Return the provided default if 'Nothing' is returned
-- 'postX11RequestSyncProp'.
runX11Def :: a -> X11Property a -> TaffyIO a
runX11Def :: forall a. a -> X11Property a -> TaffyIO a
runX11Def a
dflt X11Property a
prop = X11Property a -> TaffyIO a
forall a. X11Property a -> TaffyIO a
runX11 (X11Property a -> TaffyIO a) -> X11Property a -> TaffyIO a
forall a b. (a -> b) -> a -> b
$ X11Property a -> a -> X11Property a
forall a. X11Property a -> a -> X11Property a
postX11RequestSyncProp X11Property a
prop a
dflt

runX11Context :: MonadIO m => Context -> a -> X11Property a -> m a
runX11Context :: forall (m :: * -> *) a.
MonadIO m =>
Context -> a -> X11Property a -> m a
runX11Context Context
context a
dflt X11Property a
prop =
  IO a -> m a
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO a -> m a) -> IO a -> m a
forall a b. (a -> b) -> a -> b
$ ReaderT Context IO a -> Context -> IO a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (a -> X11Property a -> ReaderT Context IO a
forall a. a -> X11Property a -> TaffyIO a
runX11Def a
dflt X11Property a
prop) Context
context

-- | Get a state value by type from the 'contextState' field of 'Context'.
getState :: forall t. Typeable t => Taffy IO (Maybe t)
getState :: forall t. Typeable t => Taffy IO (Maybe t)
getState = do
  stateMap <- (Context -> MVar (Map TypeRep Value))
-> ReaderT Context IO (Map TypeRep Value)
forall r b. (r -> MVar b) -> ReaderT r IO b
asksContextVar Context -> MVar (Map TypeRep Value)
contextState
  let maybeValue = TypeRep -> Map TypeRep Value -> Maybe Value
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (Proxy t -> TypeRep
forall {k} (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy t
forall {k} (t :: k). Proxy t
Proxy :: Proxy t)) Map TypeRep Value
stateMap
  return $ maybeValue >>= fromValue

-- | Like "putState", but avoids aquiring a lock if the value is already in the
-- map.
getStateDefault :: Typeable t => Taffy IO t -> Taffy IO t
getStateDefault :: forall t. Typeable t => Taffy IO t -> Taffy IO t
getStateDefault Taffy IO t
defaultGetter =
  Taffy IO (Maybe t)
forall t. Typeable t => Taffy IO (Maybe t)
getState Taffy IO (Maybe t) -> (Maybe t -> Taffy IO t) -> Taffy IO t
forall a b.
ReaderT Context IO a
-> (a -> ReaderT Context IO b) -> ReaderT Context IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Taffy IO t -> (t -> Taffy IO t) -> Maybe t -> Taffy IO t
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Taffy IO t -> Taffy IO t
forall t. Typeable t => Taffy IO t -> Taffy IO t
putState Taffy IO t
defaultGetter) t -> Taffy IO t
forall a. a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return

-- | Get a value of the type returned by the provided action from the the
-- current taffybar state, unless the state does not exist, in which case the
-- action will be called to populate the state map.
putState :: forall t. Typeable t => Taffy IO t -> Taffy IO t
putState :: forall t. Typeable t => Taffy IO t -> Taffy IO t
putState Taffy IO t
getValue = do
  contextVar <- (Context -> MVar (Map TypeRep Value))
-> ReaderT Context IO (MVar (Map TypeRep Value))
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks Context -> MVar (Map TypeRep Value)
contextState
  ctx <- ask
  lift $ MV.modifyMVar contextVar $ \Map TypeRep Value
contextStateMap ->
    let theType :: TypeRep
theType = Proxy t -> TypeRep
forall {k} (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy t
forall {k} (t :: k). Proxy t
Proxy :: Proxy t)
        currentValue :: Maybe Value
currentValue = TypeRep -> Map TypeRep Value -> Maybe Value
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup TypeRep
theType Map TypeRep Value
contextStateMap
        insertAndReturn :: t -> (Map TypeRep Value, t)
insertAndReturn t
value =
          (TypeRep -> Value -> Map TypeRep Value -> Map TypeRep Value
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert TypeRep
theType (t -> Value
forall t. Typeable t => t -> Value
Value t
value) Map TypeRep Value
contextStateMap, t
value)
    in (ReaderT Context IO (Map TypeRep Value, t)
 -> Context -> IO (Map TypeRep Value, t))
-> Context
-> ReaderT Context IO (Map TypeRep Value, t)
-> IO (Map TypeRep Value, t)
forall a b c. (a -> b -> c) -> b -> a -> c
flip ReaderT Context IO (Map TypeRep Value, t)
-> Context -> IO (Map TypeRep Value, t)
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT Context
ctx (ReaderT Context IO (Map TypeRep Value, t)
 -> IO (Map TypeRep Value, t))
-> ReaderT Context IO (Map TypeRep Value, t)
-> IO (Map TypeRep Value, t)
forall a b. (a -> b) -> a -> b
$  ReaderT Context IO (Map TypeRep Value, t)
-> (t -> ReaderT Context IO (Map TypeRep Value, t))
-> Maybe t
-> ReaderT Context IO (Map TypeRep Value, t)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
         (t -> (Map TypeRep Value, t)
insertAndReturn  (t -> (Map TypeRep Value, t))
-> Taffy IO t -> ReaderT Context IO (Map TypeRep Value, t)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Taffy IO t
getValue)
         ((Map TypeRep Value, t) -> ReaderT Context IO (Map TypeRep Value, t)
forall a. a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ((Map TypeRep Value, t)
 -> ReaderT Context IO (Map TypeRep Value, t))
-> (t -> (Map TypeRep Value, t))
-> t
-> ReaderT Context IO (Map TypeRep Value, t)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Map TypeRep Value
contextStateMap,))
         (Maybe Value
currentValue Maybe Value -> (Value -> Maybe t) -> Maybe t
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> Maybe t
forall t. Typeable t => Value -> Maybe t
fromValue)

-- | A version of 'forkIO' in 'TaffyIO'.
taffyFork :: ReaderT r IO () -> ReaderT r IO ()
taffyFork :: forall r. ReaderT r IO () -> ReaderT r IO ()
taffyFork = ReaderT r IO ThreadId -> ReaderT r IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (ReaderT r IO ThreadId -> ReaderT r IO ())
-> (ReaderT r IO () -> ReaderT r IO ThreadId)
-> ReaderT r IO ()
-> ReaderT r IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (IO () -> IO ThreadId) -> ReaderT r IO () -> ReaderT r IO ThreadId
forall (m :: * -> *) a (n :: * -> *) b r.
(m a -> n b) -> ReaderT r m a -> ReaderT r n b
mapReaderT IO () -> IO ThreadId
forkIO

startX11EventHandler :: Taffy IO ()
startX11EventHandler :: TaffyIO ()
startX11EventHandler = TaffyIO () -> TaffyIO ()
forall r. ReaderT r IO () -> ReaderT r IO ()
taffyFork (TaffyIO () -> TaffyIO ()) -> TaffyIO () -> TaffyIO ()
forall a b. (a -> b) -> a -> b
$ do
  c <- ReaderT Context IO Context
forall (m :: * -> *) r. Monad m => ReaderT r m r
ask
  -- XXX: The event loop needs its own X11Context to separately handle
  -- communications from the X server. We deliberately avoid using the context
  -- from x11ContextVar here.
  lift $ withX11Context def $ eventLoop
         (\Event
e -> TaffyIO () -> Context -> IO ()
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (Event -> TaffyIO ()
handleX11Event Event
e) Context
c)

-- | Remove the listener associated with the provided "Unique" from the
-- collection of listeners.
unsubscribe :: Unique -> Taffy IO ()
unsubscribe :: Unique -> TaffyIO ()
unsubscribe Unique
identifier = do
  listenersVar <- (Context -> MVar SubscriptionList)
-> ReaderT Context IO (MVar SubscriptionList)
forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks Context -> MVar SubscriptionList
listeners
  lift $ MV.modifyMVar_ listenersVar $ return . filter ((== identifier) . fst)

-- | Subscribe to all incoming events on the X11 event loop. The returned
-- "Unique" value can be used to unregister the listener using "unsuscribe".
subscribeToAll :: Listener -> Taffy IO Unique
subscribeToAll :: (Event -> TaffyIO ()) -> Taffy IO Unique
subscribeToAll Event -> TaffyIO ()
listener = do
  identifier <- IO Unique -> Taffy IO Unique
forall (m :: * -> *) a. Monad m => m a -> ReaderT Context m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift IO Unique
newUnique
  listenersVar <- asks listeners
  let
    -- XXX: This type annotation probably has something to do with the warnings
    -- that occur without MonoLocalBinds, but it still seems to be necessary
    addListener :: SubscriptionList -> SubscriptionList
    addListener = ((Unique
identifier, Event -> TaffyIO ()
listener)(Unique, Event -> TaffyIO ())
-> SubscriptionList -> SubscriptionList
forall a. a -> [a] -> [a]
:)
  lift $ MV.modifyMVar_ listenersVar (return . addListener)
  return identifier

-- | Subscribe to X11 "PropertyEvent"s where the property changed is in the
-- provided list.
subscribeToPropertyEvents :: [String] -> Listener -> Taffy IO Unique
subscribeToPropertyEvents :: [String] -> (Event -> TaffyIO ()) -> Taffy IO Unique
subscribeToPropertyEvents [String]
eventNames Event -> TaffyIO ()
listener = do
  eventAtoms <- (String -> ReaderT Context IO Atom)
-> [String] -> ReaderT Context IO [Atom]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (X11Property Atom -> ReaderT Context IO Atom
forall a. X11Property a -> TaffyIO a
runX11 (X11Property Atom -> ReaderT Context IO Atom)
-> (String -> X11Property Atom)
-> String
-> ReaderT Context IO Atom
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> X11Property Atom
getAtom) [String]
eventNames
  let filteredListener event :: Event
event@PropertyEvent { ev_atom :: Event -> Atom
ev_atom = Atom
atom } =
        Bool -> TaffyIO () -> TaffyIO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Atom
atom Atom -> [Atom] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Atom]
eventAtoms) (TaffyIO () -> TaffyIO ()) -> TaffyIO () -> TaffyIO ()
forall a b. (a -> b) -> a -> b
$
             TaffyIO () -> (SomeException -> TaffyIO ()) -> TaffyIO ()
forall (m :: * -> *) a.
MonadBaseControl IO m =>
m a -> (SomeException -> m a) -> m a
catchAny (Event -> TaffyIO ()
listener Event
event) (TaffyIO () -> SomeException -> TaffyIO ()
forall a b. a -> b -> a
const (TaffyIO () -> SomeException -> TaffyIO ())
-> TaffyIO () -> SomeException -> TaffyIO ()
forall a b. (a -> b) -> a -> b
$ () -> TaffyIO ()
forall a. a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ())
      filteredListener Event
_ = () -> TaffyIO ()
forall a. a -> ReaderT Context IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  subscribeToAll filteredListener

handleX11Event :: Event -> Taffy IO ()
handleX11Event :: Event -> TaffyIO ()
handleX11Event Event
event =
  (Context -> MVar SubscriptionList)
-> ReaderT Context IO SubscriptionList
forall r b. (r -> MVar b) -> ReaderT r IO b
asksContextVar Context -> MVar SubscriptionList
listeners ReaderT Context IO SubscriptionList
-> (SubscriptionList -> TaffyIO ()) -> TaffyIO ()
forall a b.
ReaderT Context IO a
-> (a -> ReaderT Context IO b) -> ReaderT Context IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ((Unique, Event -> TaffyIO ()) -> TaffyIO ())
-> SubscriptionList -> TaffyIO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Unique, Event -> TaffyIO ()) -> TaffyIO ()
applyListener
  where applyListener :: (Unique, Listener) -> Taffy IO ()
        applyListener :: (Unique, Event -> TaffyIO ()) -> TaffyIO ()
applyListener (Unique
_, Event -> TaffyIO ()
listener) = TaffyIO () -> TaffyIO ()
forall r. ReaderT r IO () -> ReaderT r IO ()
taffyFork (TaffyIO () -> TaffyIO ()) -> TaffyIO () -> TaffyIO ()
forall a b. (a -> b) -> a -> b
$ Event -> TaffyIO ()
listener Event
event