Personal tools

Xmonad/Mutable state in contrib modules or xmonad.hs

From HaskellWiki

< Xmonad(Difference between revisions)
Jump to: navigation, search
(Data.IORef: Note about persistency)
m (General options: swapDown Util.StringProp so ExtState text referring to IORefs in prev section makes sense)
(8 intermediate revisions by 3 users not shown)
Line 6: Line 6:
   
 
Note that this sensible for per-workspace state.
 
Note that this sensible for per-workspace state.
  +
  +
<haskell>
  +
{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, UndecidableInstances #-}
  +
import XMonad
  +
import XMonad.Layout.LayoutModifier
  +
import Data.Traversable (traverse)
  +
import Control.Monad
  +
  +
data WSState s a = WSState s deriving (Read,Show)
  +
  +
withState s0 layout = ModifiedLayout (WSState s0) layout
  +
  +
newtype (WSF s) = WSF (s -> X (Maybe s)) deriving (Typeable)
  +
instance Typeable s => Message (WSF s)
  +
  +
instance (Typeable s, Show (WSState s a), Read (WSState s a)) =>
  +
LayoutModifier (WSState s) a where
  +
handleMess (WSState s) m = case fromMessage m of
  +
Just (WSF f) -> do
  +
s' <- f s
  +
return $ case s' of
  +
Just s'' -> Just (WSState s'')
  +
Nothing -> Nothing
  +
Nothing -> return Nothing
  +
  +
{- -- equivalent but harder to understand :p
  +
handleMess (WSState s) m = fmap join $
  +
(\(WSF f) -> fmap WSState `fmap` f s) `traverse` fromMessage m
  +
-}
  +
</haskell>
  +
  +
Using the above 'withState' layout modifier and the message 'WSF', you can do per-layout/workspace state. For example one could keep track of whether a workspace has been previously viewed (ex. to start some applications iff you have not seen the workspace before, as done with A.TopicSpace):
  +
  +
<haskell>
  +
-- this modifies a layout
  +
viewed layout = withState False layout
  +
  +
-- this goes in logHook
  +
viewedHook = sendMessage (WSF $ \b -> if b then return Nothing -- already viewed
  +
else do
  +
spawn "xterm"
  +
return (Just True))
  +
</haskell>
   
 
== Data.IORef ==
 
== Data.IORef ==
Line 52: Line 95:
   
 
=== UnsafePerformIO ===
 
=== UnsafePerformIO ===
This is even less desirable alternative, but it is one of the more acceptable uses of this unsafe function.
+
This is an even less desirable alternative, but it is one of the more acceptable uses of this unsafe function.
   
 
Basically, you create a top-level IORef which is forced to be a single copy:
 
Basically, you create a top-level IORef which is forced to be a single copy:
Line 63: Line 106:
 
Previous versions of xmonad had this used in some contrib modules such as XMonad.Hooks.UrgencyHook [http://hackage.haskell.org/packages/archive/xmonad-contrib/0.9.1/doc/html/src/XMonad-Hooks-UrgencyHook.html#urgents source here].
 
Previous versions of xmonad had this used in some contrib modules such as XMonad.Hooks.UrgencyHook [http://hackage.haskell.org/packages/archive/xmonad-contrib/0.9.1/doc/html/src/XMonad-Hooks-UrgencyHook.html#urgents source here].
   
== XMonad.Util.StringProp ==
 
Refer to [http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Util-StringProp.html haddock documentation].
 
 
= Only available in darcs =
 
 
== XMonad.Util.ExtensibleState ==
 
== XMonad.Util.ExtensibleState ==
This module allows you to store data in xmonad's internal state eliminating the need to explicitly pass around the IORef value seen in the previous example.
+
This module, available in xmonad-0.10 or later, allows you to store data in xmonad's internal state eliminating the need to explicitly pass around IORef values as seen in the previous sections.
   
 
For information on how to use it, refer to the module's documentation.
 
For information on how to use it, refer to the module's documentation.
  +
  +
== XMonad.Util.StringProp ==
  +
This module has been available since xmonad-0.8. Refer to [http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Util-StringProp.html haddock documentation].

Revision as of 22:02, 3 December 2011

This page describes how to keep track of mutable state in a module in xmonad-contrib or in the configuration.

Contents

1 General options

1.1 Layouts

Layouts can keep track of their state placing the information in the data type that is an instance of LayoutClass. The various methods of this typeclass allow one to update this information by supplying a new value of this type as a return value.

Note that this sensible for per-workspace state.

{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, UndecidableInstances #-}
import XMonad
import XMonad.Layout.LayoutModifier
import Data.Traversable (traverse)
import Control.Monad
 
data WSState s a = WSState s deriving (Read,Show)
 
withState s0 layout = ModifiedLayout (WSState s0) layout
 
newtype (WSF s) = WSF (s -> X (Maybe s)) deriving (Typeable)
instance Typeable s => Message (WSF s)
 
instance (Typeable s, Show (WSState s a), Read (WSState s a)) =>
             LayoutModifier (WSState s) a where
    handleMess (WSState s) m = case fromMessage m of
        Just (WSF f) -> do
            s' <- f s
            return $ case s' of
                Just s'' -> Just (WSState s'')
                Nothing -> Nothing
        Nothing -> return Nothing
 
    {- -- equivalent but harder to understand :p
    handleMess (WSState s) m = fmap join $
      (\(WSF f) -> fmap WSState `fmap` f s) `traverse` fromMessage m
      -}

Using the above 'withState' layout modifier and the message 'WSF', you can do per-layout/workspace state. For example one could keep track of whether a workspace has been previously viewed (ex. to start some applications iff you have not seen the workspace before, as done with A.TopicSpace):

-- this modifies a layout
viewed layout = withState False layout
 
-- this goes in logHook
viewedHook = sendMessage (WSF $ \b -> if b then return Nothing -- already viewed
    else do
      spawn "xterm"
      return (Just True))

1.2 Data.IORef

A more general way is to store data using Data.IORef. To create an IORef, one uses newIORef; the returned value can then be passed to functions for reading from or writing to it.

Here is an example for a keybinding that keeps track of how many times it has been pressed:

import Data.IORef
..
updatingBinding :: IORef Integer -> X ()
updatingBinding ref = do
  val <- io $ readIORef ref
  io $ writeIORef ref (val+1)
  spawn $ "xmessage I have been pressed " ++ show val ++ " times before!"

To use this, it needs to be passed an IORef as a parameter:

import Data.IORef
import XMonad.Util.EZConfig
..
main = do
  ref <- newIORef 0 -- this is the initival value
  xmonad defaultConfig `additionalKeysP` [("M-S-t", updatingBinding ref)]

Note that using IORefs is not persistent across xmonad restarts.

1.2.1 Implicit Parameters

This extension is useful for passing in the iorefs. You generally need these two extensions together, if you like to leave out the type signatures (which are as much work as explicitly passing the parameters):

{-# LANGUAGE ImplicitParams, NoMonomorphismRestriction #-}
 
main = do
  ref <- newIORef 0
  let ?ref = ref -- all references to ?ref later on will be this one
  xmonad defaultConfig `additionalKeysP` [("M-S-t", updatingBinding)]
 
-- updatingBinding :: (?ref :: IORef Integer) => X () -- this is the actual type
updatingBinding = do
  val <- io $ readIORef ?ref
  io $ writeIORef ?ref (val+1)
  spawn $ "xmessage I have been pressed " ++ show val ++ " times before!"

1.2.2 UnsafePerformIO

This is an even less desirable alternative, but it is one of the more acceptable uses of this unsafe function.

Basically, you create a top-level IORef which is forced to be a single copy:

{-# NOINLINE ref #-}
ref = unsafePerformIO (newIORef 0)

Previous versions of xmonad had this used in some contrib modules such as XMonad.Hooks.UrgencyHook source here.

1.3 XMonad.Util.ExtensibleState

This module, available in xmonad-0.10 or later, allows you to store data in xmonad's internal state eliminating the need to explicitly pass around IORef values as seen in the previous sections.

For information on how to use it, refer to the module's documentation.

1.4 XMonad.Util.StringProp

This module has been available since xmonad-0.8. Refer to haddock documentation.