From a09071f8f6c19294df0c4f8507bb3027fb302fbf Mon Sep 17 00:00:00 2001 From: maralorn Date: Thu, 2 Mar 2023 04:58:22 +0100 Subject: [PATCH] =?UTF-8?q?Can=E2=80=99t=20have=20a=20status-script=20with?= =?UTF-8?q?out=20GADTs=20can=20we=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- home-manager/roles/status-script.hs | 74 ++++++++++++++++++----------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/home-manager/roles/status-script.hs b/home-manager/roles/status-script.hs index 64862721..8ca6d643 100644 --- a/home-manager/roles/status-script.hs +++ b/home-manager/roles/status-script.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE ExtendedDefaultRules #-} +{-# LANGUAGE GADTs #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} @@ -19,6 +20,7 @@ import Data.ByteString.Char8 qualified as ByteString import Data.ByteString.Lazy qualified as LBS import Data.String.Interpolate (i) import Data.Text qualified as Text +import Data.Unique qualified as Unique import Relude import Say (sayErr) import Shh (ExecReference (Absolute), Proc, captureTrim, exe, ignoreFailure, load, readInputLines, (|>)) @@ -47,35 +49,54 @@ isUnpushed gitDir = do tryCmd :: Proc a -> IO LBS.ByteString tryCmd x = ignoreFailure x |> captureTrim -data Var a = MkVar - { value :: TVar a - , update :: TMVar () +newtype Var a = MkVar + { value :: TVar (a, Unique.Unique) } +getUnique = fmap snd . readTVar . value +getVal = fmap fst . readTVar . value + newVar :: a -> IO (Var a) -newVar initial_value = atomically $ MkVar <$> newTVar initial_value <*> newEmptyTMVar +newVar initial_value = do + unique <- Unique.newUnique + MkVar <$> newTVarIO (initial_value, unique) newMaybeVar :: IO (Var (Maybe a)) -newMaybeVar = atomically $ MkVar <$> newTVar Nothing <*> newEmptyTMVar +newMaybeVar = newVar Nothing type Vars = [Var (Maybe Text)] type Module a = Var a -> IO Void -repeatM :: IO a -> IO Void -repeatM action = fix (action >>) - writeVars :: Vars -> IO Void -writeVars vars = repeatM do - outputs <- atomically $ do - updates <- forM vars $ \MkVar{update} -> tryTakeTMVar update - STM.check $ any isJust updates - forM vars \MkVar{value} -> readTVar value +writeVars vars = onUpdatesMono vars do + outputs <- atomically $ mapM getVal vars writeFileText "/run/user/1000/status-bar" $ Text.replace "&" "&" $ Text.unwords $ ("" :) $ catMaybes outputs +data AnyVar where + AVar :: Var a -> AnyVar + +onUpdate :: Var a -> (a -> IO ()) -> IO Void +onUpdate var action = onUpdatesMono [var] do + action =<< atomically (getVal var) + +onUpdatesMono :: [Var a] -> IO () -> IO Void +onUpdatesMono = onUpdates . fmap AVar + +onUpdates :: [AnyVar] -> IO () -> IO Void +onUpdates vars action = go [] + where + go previous_uniques = do + next_uniques <- atomically $ do + current_uniques <- mapM (\(AVar a) -> getUnique a) vars + STM.check $ previous_uniques /= current_uniques + pure current_uniques + action + go next_uniques + runModules :: [Module (Maybe Text)] -> IO () runModules modules = do (vars, actions) <- @@ -90,16 +111,17 @@ foldConcurrently_ = Async.mapConcurrently_ id oneSecond :: Int oneSecond = 1000000 -updateVarIfChanged :: Eq a => Var a -> a -> STM () -updateVarIfChanged MkVar{value, update} new_value = do - old_value <- readTVar value - unless (old_value == new_value) do - writeTVar value new_value - putTMVar update () +updateVarIfChanged :: Eq a => Var a -> a -> IO () +updateVarIfChanged var@MkVar{value} new_value = do + new_unique <- Unique.newUnique + atomically do + old_value <- getVal var + unless (old_value == new_value) do + writeTVar value (new_value, new_unique) simpleModule :: Eq a => Int -> IO a -> Module a -simpleModule delay action var = repeatM do - atomically . updateVarIfChanged var =<< action +simpleModule delay action var = forever do + updateVarIfChanged var =<< action Concurrent.threadDelay delay withColor :: Monad m => Text -> Text -> m (Maybe Text) @@ -115,8 +137,7 @@ playerModule :: Module (Maybe Text) playerModule = \var -> let update_lines = mapM_ - ( atomically - . updateVarIfChanged var + ( updateVarIfChanged var . Just . Text.replace "@Stopped" "⏹" . Text.replace "@Playing" "▶" @@ -131,7 +152,7 @@ playerModule = \var -> main :: IO () main = do mode_var <- newVar Unrestricted - let read_mode = readTVarIO (value mode_var) + let read_mode = fst <$> readTVarIO (value mode_var) modules = [ simpleModule (5 * oneSecond) $ do appointments <- lines . decodeUtf8 <$> tryCmd (khal ["list", "-a", "Standard", "-a", "Planung", "-a", "Uni", "-a", "Maltaire", "now", "2h", "-df", ""]) @@ -196,9 +217,8 @@ main = do when' (system_dirty || modes_dirty) $ withColor "ffff00" [i|Current #{case (system_dirty,modes_dirty) of (True, True) -> "home and system"; (True, _) -> "system"; _ -> "home"} stale|] ) var - , \var -> forever do - atomically . updateVarIfChanged var =<< withColor "7287fd" . show =<< read_mode - atomically $ takeTMVar (update mode_var) + , \var -> + onUpdate mode_var $ updateVarIfChanged var . runIdentity . withColor "7287fd" . show ] foldConcurrently_ [ void $ simpleModule oneSecond getMode mode_var