XMonad: how to show the currently visible workspace on xmobar when using multiple screens? - xmonad

I'm using XMonad in a setup with multiple physical screens.
I would like each of the physical screens to have an instance of xmobar which
shows which workspace is visible on that particular screen, regardless of whether
that workspace is "current"/"active" or not.
E.g.
+--------------+ +--------------+
| | | |
| | | |
| | | |
| | | |
+--------------+ +--------------+
|Workspace 3 | |Workspace 5 |
+--------------+ +--------------+
My current (minimized for clarity) xmonad.hs is below.
import XMonad
import XMonad.Layout.NoBorders
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.DynamicLog
import XMonad.Util.Run
import XMonad.Layout.IndependentScreens
main = do
n <- countScreens
xmprocs <- mapM (\i -> spawnPipe $ "xmobar" ++ " -x " ++ show i) [0..n-1]
xmonad $ docks def
{ layoutHook = avoidStruts $ smartBorders $ layoutHook defaultConfig
, logHook = mapM_ (\xmobarPipe -> dynamicLogWithPP $ def
{ ppOutput = hPutStrLn xmobarPipe
, ppCurrent = \s -> s
, ppVisible = \s -> ""
, ppHidden = \s -> ""
, ppLayout = \s -> ""
, ppTitle = \s -> ""
}) xmprocs
}
That is, I managed to spawn 2 instances of xmobar, one for each screen. However it simply shows the currently active Workspace (across screens) on both screens' xmobar. E.g. it would show:
+--------------+ +--------------+
| | | |
| | | |
| | | |
| | | |
+--------------+ +--------------+
|Workspace 3 | |Workspace 3 |
+--------------+ +--------------+
Now, how do I achieve what I actually want?
I think the configuration here
https://github.com/nwf/xconfig/blob/208e6d6ce48fba45ec30bb1df1389f9ff2263edd/xmonad/lib/XMonad/Actions/XMobars.hs#L163
might contain hints to the answer but I'm not proficient enough in Haskell to
work back from that example to something minimal that I can use.

This works:
import GHC.IO.Handle.Types (Handle)
import XMonad (MonadIO, WorkspaceId, Layout, Window, ScreenId, ScreenDetail, WindowSet, layoutHook, logHook, X, io, ScreenId(..), gets, windowset, xmonad)
import Graphics.X11.ExtraTypes.XF86 (xF86XK_MonBrightnessUp, xF86XK_MonBrightnessDown)
import XMonad.Layout.NoBorders (smartBorders)
import XMonad.Hooks.ManageDocks (docks, avoidStruts)
import XMonad.Hooks.DynamicLog (def)
import XMonad.Util.Run (spawnPipe, hPutStrLn)
import XMonad.Layout.IndependentScreens (countScreens)
import XMonad.StackSet (current, screen, visible, Screen, workspace, tag)
spawnXMobar :: MonadIO m => Int -> m (Int, Handle)
spawnXMobar i = (spawnPipe $ "xmobar" ++ " -x " ++ show i) >>= (\handle -> return (i, handle))
spawnXMobars :: MonadIO m => Int -> m [(Int, Handle)]
spawnXMobars n = mapM spawnXMobar [0..n-1]
type ScreenFoo = Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail
visibleScreens :: WindowSet -> [ScreenFoo]
visibleScreens cws = ([current cws]) ++ (visible cws)
joinToString :: [String] -> String
joinToString workspaceIds = foldl (++) "" workspaceIds
myLogHookForPipe :: WindowSet -> (Int, Handle) -> X ()
myLogHookForPipe currentWindowSet (i, xmobarPipe) =
io $ hPutStrLn xmobarPipe $ -- write to pipe and lift to XMonad
joinToString $ map (tag . workspace) $ -- extract workspace names and join into a single string
filter ((==) (S i) . screen) $ -- filter to this xmobar's screen only
visibleScreens currentWindowSet -- get list of all visible screens
myLogHook :: [(Int, Handle)] -> X ()
myLogHook xmobarPipes = do
currentWindowSet <- gets windowset
mapM_ (myLogHookForPipe currentWindowSet) xmobarPipes
main = do
n <- countScreens
xmobarPipes <- spawnXMobars n
xmonad $ docks def
{ layoutHook = avoidStruts $ smartBorders $ layoutHook def
, logHook = myLogHook xmobarPipes
}

Related

Am I using Boxes Package Correctly?

I am trying to convert CSV files into a table using the Boxes package
.
I am using IHaskell (Jupyter notebook extension) to do data analysis and would like to include nice looking tables. I could write a program to convert the CSV to a Jupyter markdown native table but that's not as fluid a process as generating the table on the fly.
Below is the code which produces a reasonable looking table but I had to do some manual stuff which made me think I wasn't using Boxes correctly.
There doesn't appear to be a way to set a fixed width Box (with padding) and the actual data centered within it. It's easy to get it to center the data but the box width is dependent on the width of the data resulting in a 'jagged' column.
My complicated code is below .. I've been doing Haskell for awhile but I still consider myself a novice.
Any comments greatly appreciated.
Tom
module Main (main) where
{-
Notes:
- This converst a CSV into a table
- Am I using the Boxes package but I had to do a lot
of work, using only punctuateH, punctuateV from the
package.
- Is there a better way to do it?
- Things I had to do 'manually'
- Center the data in a fixed width box. If I didn't
do this the columns would not align since the data
has various widths.
- Add the outside borders
- The input is rows of data separated by commas .. (CSV)
on,pinput,pout,eff,ledi
5.0e-6,8.43764e-2,7.88486e-2,0.934486,8.63554e-3
5.4e-5,3.04731,2.90032,0.951765,0.214559
1.03e-4,6.34257,6.03162,0.950973,0.434331
- Produces,
|--------------------------------------------------------------|
| on | pinput | pout | eff | ledi |
| ------------------------------------------------------------ |
| 5.0e-6 | 8.43764e-2 | 7.88486e-2 | 0.934486 | 8.63554e-3|
| ------------------------------------------------------------ |
| 5.4e-5 | 3.04731 | 2.90032 | 0.951765 | 0.214559 |
| ------------------------------------------------------------ |
| 1.03e-4 | 6.34257 | 6.03162 | 0.950973 | 0.434331 |
|--------------------------------------------------------------|
-}
import Text.PrettyPrint.Boxes
replace :: Eq a => a -> a -> [a] -> [a]
replace a b = map $ \c -> if c == a then b else c
setLen :: Int -> String -> String
setLen n str =
let fullPad = n - (length str)
halfPadLen = truncate $ (fromIntegral fullPad) / (2.0::Double)
halfPad = replicate halfPadLen ' '
in
if (even fullPad)
then halfPad ++ str ++ halfPad
else halfPad ++ " " ++ str ++ halfPad
padEnds :: String -> String
padEnds str =
let theLines = lines str
tableW = (length (head theLines)) + 2
allData = concatMap (\aLine -> "|" ++ aLine ++ "|\n") theLines
bar = replicate (tableW-2) '-'
in "|" ++ bar ++ "|\n" ++ allData ++ "|" ++ bar ++ "|\n"
renderTable :: String -> String
renderTable str = toStr ( lines (replace ',' ' ' str) )
where
toStr :: [String] -> String
toStr theLines =
let allBoxes = map (\aLine -> makeBoxes aLine) theLines
hs = map (\row -> punctuateH center1 (text " | ") row) allBoxes
box = punctuateV center1 (text (replicate 60 '-')) hs
rawStr = render box
in padEnds rawStr
makeBoxes :: String -> [Box]
makeBoxes aLine =
let sameLen = map (setLen 10) (words aLine)
in map text sameLen
main :: IO ()
main = do
fileData <- readFile "test2.csv"
putStrLn $ renderTable fileData
Yes, I think you're using boxes wrong. Here's what my go would look like:
import Prelude hiding ((<>)) -- ugh
import Data.List
import Text.PrettyPrint.Boxes
table :: [[String]] -> Box
table sss = border <> punctuateH center1 sep cs <> border where
border = vtext (replicate height '|')
sep = vtext (take height (cycle "-|"))
height = maxPosOn rows cs
cs = map column (transpose sss)
column :: [String] -> Box
column ss = sep // punctuateV center1 sep (map text ss) // sep where
sep = text (replicate (maxPosOn length ss+2) '-')
vtext :: String -> Box
vtext = vcat center1 . map char
maxPosOn :: (a -> Int) -> [a] -> Int
maxPosOn f = maximum . (0:) . map f
Try it out in ghci:
> printBox $ table [["on","pinput","pout","eff","ledi"],["5.0e-6","8.43764e-2","7.88486e-2","0.934486","8.63554e-3"],["5.4e-5","3.04731","2.90032","0.951765","0.214559"],["1.03e-4","6.34257","6.03162","0.950973","0.434331"]]
|-----------------------------------------------------------|
| on | pinput | pout | eff | ledi |
|-----------------------------------------------------------|
| 5.0e-6 | 8.43764e-2 | 7.88486e-2 | 0.934486 | 8.63554e-3 |
|-----------------------------------------------------------|
| 5.4e-5 | 3.04731 | 2.90032 | 0.951765 | 0.214559 |
|-----------------------------------------------------------|
| 1.03e-4 | 6.34257 | 6.03162 | 0.950973 | 0.434331 |
|-----------------------------------------------------------|

Use IO when creating Xmonad configuration (keymap depends on number of connected monitors)

I'm trying to set up different Xmonad key mappings depending on the number of connected monitors. The reason is that I use the same Xmonad config file on multiple systems (desktops, a laptop with different monitor configurations including 3 displays). Displays are listed in a different order on different systems, that's why I need to hardcode display indices when using a 3 monitor setup.
My current best try is something like that (everything that is not relevant has been removed):
import qualified Graphics.X11.Xlib as X11
import qualified Graphics.X11.Xinerama as X11
screenKeysFor2Monitors conf#(XConfig {XMonad.modMask = modMask}) = M.fromList $
[((m .|. mod4Mask, key), screenWorkspace sc >>= flip whenJust (windows . f)) -- Replace 'mod1Mask' with your mod key of choice.
| (key, sc) <- zip [xK_w, xK_e] [0, 1] -- Usual screen order
, (f, m) <- [(W.view, 0), (W.shift, shiftMask), (W.greedyView, mod1Mask)]]
screenKeysFor3Monitors conf#(XConfig {XMonad.modMask = modMask}) = M.fromList $
[((m .|. mod4Mask, key), screenWorkspace sc >>= flip whenJust (windows . f)) -- Replace 'mod1Mask' with your mod key of choice.
| (key, sc) <- zip [xK_w, xK_e, xK_q] [0, 2, 1] -- hardcoded according to laptop driver
, (f, m) <- [(W.view, 0), (W.shift, shiftMask), (W.greedyView, mod1Mask)]]
screenKeys x = do
numberOfScreens <- getScreens
keyConfig <- case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
return keyConfig
-- | Get number of screens
getScreens = do
screens <- do
dpy <- X11.openDisplay ""
rects <- X11.getScreenInfo dpy
X11.closeDisplay dpy
return rects
pure $ length screens
xmonadConfig = ewmh xfceConfig{
modMask = mod4Mask
keys = MyKeys.screenKeys
}
I get this error
Error detected while loading xmonad configuration file: /home/me/.xmonad/xmonad.hs
lib/MyXMonad/Keys.hs:51:64: error:
* Couldn't match expected type `M.Map (KeyMask, KeySym) (X ())`
with actual type `IO (X ())`
* In the expression: (screenKeys x)
In the second argument of `($)`, namely
`[(myKeysToAdd x), (workspaceKeys x), (screenKeys x)]`
In the expression:
M.unions $ [(myKeysToAdd x), (workspaceKeys x), (screenKeys x)]
|
51 | keysToAdd x = M.unions $ [(myKeysToAdd x), (workspaceKeys x), (screenKeys x)]
| ^^^^^^^^^^^^
lib/MyXMonad/Keys.hs:242:30: error:
* Couldn't match type `M.Map (KeyMask, KeySym)` with `IO`
Expected type: IO (X ())
Actual type: M.Map (KeyMask, KeySym) (X ())
* In the expression: screenKeysFor3Monitors x
In a case alternative: 3 -> screenKeysFor3Monitors x
In a stmt of a 'do' block:
keyConfig <- case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
|
242 | 3 -> screenKeysFor3Monitors x
| ^^^^^^^^^^^^^^^^^^^^^^^^
lib/MyXMonad/Keys.hs:243:30: error:
* Couldn't match type `M.Map (KeyMask, KeySym)` with `IO`
Expected type: IO (X ())
Actual type: M.Map (KeyMask, KeySym) (X ())
* In the expression: screenKeysFor2Monitors x
In a case alternative: _ -> screenKeysFor2Monitors x
In a stmt of a 'do' block:
keyConfig <- case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
|
243 | _ -> screenKeysFor2Monitors x
| ^^^^^^^^^^^^^^^^^^^^^^^^
Please check the file for errors.
If I understand correctly, the problem here is that my code depends on side effects (working with monitor configuration uses IO monad) and becomes non-pure. I can convert IO monad to X monad using liftIO. But the X monad is accessible only inside key binding handlers. The code that creates key bindings for Xmonad configuration has to be pure, and X monad is not expected here.
In other words, if I get the situation right, it's not possible to define key bindings using non-pure functions (e.g. by looking on connected monitors). Maybe there is some workaround? I lack a decent understanding of Haskell and maybe I'm missing something obvious for regular Haskell programmers.
not too familiar with Xmonad but you can easily do the following I guess. create a pure function mkConfig which takes the number of screens and returns the desired key mapping. Then, in your main pass it to xmonad function. I haven't tried to compile any of this but probably you can modify it easily
mkConfig numberOfScreens = -- Notice that this is a pure function
case numberOfScreens of
3 -> screenKeysFor3Monitors x
_ -> screenKeysFor2Monitors x
main :: IO ()
main = do
numberOfScreens <- getScreens -- Retrive the number of screens from the system
let keyConfig = mkConfig numberOfScreens -- Makes a key mapping out of this
xmonadConfig = ewmh xfceConfig{ modMask = mod4Mask, keys = keyConfig } -- Creates a Xmonad configuration
xmonad xmonadConfig -- Launch Xmonad.

IO action nested in other monads not executing

I have a
foobar :: IO (ParseResult [(String,String)])
ParseResult is a monad defined here: https://hackage.haskell.org/package/haskell-src-exts-1.13.5/docs/Language-Haskell-Exts-Parser.html#t:ParseResult
I want to take those strings and write them to a LaTeXT m () defined in https://hackage.haskell.org/package/HaTeX-3.17.1.0/docs/Text-LaTeX-Base-Writer.html
Running this function results in no file being created.
writeReport2 :: [Char] -> IO (ParseResult (IO ()))
writeReport2 name = do x <- foobar
return $ do y <- x
return $ do z <- (execLaTeXT.docAndGraph) y
renderFile fileName z
where
fileName = name ++ ".tex"
However the code:
writeReport :: t -> LaTeXT IO a -> IO ()
writeReport name report = createLatex >>= renderFile fileName
where
createLatex = execLaTeXT report
fileName = "AAAAA" ++ ".tex"
testFoo = [(" | HaskellExample Example File\n | Two examples are given below:\n\n >>> fib 10\n 55\n\n >>> putStrLn \"foo\\nbar\"\n foo\n bar ","fib :: Int -> Int"),("\n | This is a thing: ","fib = undefined"),("\n | This is a thing:\n","fibar :: String -> Float")]
itWorks = writeReport "AAAA.txt" $ docAndGraph testFoo
Will create a new file.
Both sets of code type check.
I could get writeReport2 working without modification.
I think what might have been your problem is the nested IO action in the return value of writeResport2!
In order to flatten the nested IO actions, I had to use the function join :: Monad m => m (m a) -> m a from Control.Monad:
main :: IO ()
main = join $ fromParseResult <$> writeReport2 "test"
Here is my complete code:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Language.Haskell.Exts.Parser
import Text.LaTeX.Base.Writer
import Text.LaTeX
import Data.String
import Control.Monad
foobar :: IO (ParseResult [(String, String)])
foobar = return (ParseOk testFoo)
testFoo = [ ( " | HaskellExample Example File\n | Two examples are given below:\n\n >>> fib 10\n 55\n\n >>> putStrLn \"foo\\nbar\"\n foo\n bar "
, "fib :: Int -> Int"
)
, ("\n | This is a thing: ", "fib = undefined")
, ("\n | This is a thing:\n", "fibar :: String -> Float")
]
docAndGraph :: Monad m => [(String, String)] -> LaTeXT m ()
docAndGraph x = do
documentclass [] article
document $
raw (fromString (show x))
writeReport2 :: [Char] -> IO (ParseResult (IO ()))
writeReport2 name = do
x <- foobar
return $ do
y <- x
return $ do
z <- (execLaTeXT . docAndGraph) y
renderFile fileName z
where
fileName = name ++ ".tex"
main :: IO ()
main = join $ fromParseResult <$> writeReport2 "test"
Loading into GHCi:
$ stack ghci
io-action-nested-in-other-monads-not-executing-0.1.0.0: initial-build-steps (exe)
Configuring GHCi with the following packages: io-action-nested-in-other-monads-not-executing
Using main module: 1. Package `io-action-nested-in-other-monads-not-executing' component exe:io-action-nested-in-other-monads-not-executing with main-is file: /home/sven/dev/stackoverflow-questions/io-action-nested-in-other-monads-not-executing/src/Main.hs
GHCi, version 8.0.2: http://www.haskell.org/ghc/ :? for help
Loaded GHCi configuration from /home/sven/.ghc/ghci.conf
[1 of 1] Compiling Main ( /home/sven/dev/stackoverflow-questions/io-action-nested-in-other-monads-not-executing/src/Main.hs, interpreted )
Ok, modules loaded: Main.
Loaded GHCi configuration from /tmp/ghci22616/ghci-script
And running it:
λ main
Creates this file:
$ cat test.tex
\documentclass{article}\begin{document}[(" | HaskellExample Example File\n | Two examples are given below:\n\n >>> fib 10\n 55\n\n >>> putStrLn \"foo\\nbar\"\n foo\n bar ","fib :: Int -> Int"),("\n | This is a thing: ","fib = undefined"),("\n | This is a thing:\n","fibar :: String -> Float")]\end{document}%
I know it is not the scope of the question, but you could circumvent the nested IO if you want, by doinf this, for example:
writeReport3 :: [Char] -> IO ()
writeReport3 name = do
let fileName = name ++ ".tex"
x <- foobar
case x of
ParseOk y -> do
z <- execLaTeXT (docAndGraph y)
renderFile fileName z
ParseFailed _ _ ->
return ()
main :: IO ()
main = writeReport3 "test"

Custom deriving(Read,Show) for enum type

Let's say I have this enumeration type:
data TVShow = BobsBurgers | MrRobot | BatmanTAS
and I want to define instances for Read and Show with the following behavior:
show BobsBurgers = "Bob's Burgers"
show MrRobot = "Mr. Robot"
show BatmanTAS = "Batman: The Animated Series"
read "Bob's Burgers" = BobsBurgers
read "Mr. Robot" = MrRobot
read "Batman: The Animated Series" = BatmanTAS
There is lots of repetition in these definitions, and so I'd like to associate each type constructor with a string and then generate Show and Read automatically from those associations. Is such a thing possible?
The paper Invertible Syntax Descriptions: Unifying Parsing and Pretty Printing describes one particularly idiomatic solution. Your example looks like this, using the invertible-syntax package based on that paper:
import Prelude hiding (Applicative(..), print)
import Data.Maybe (fromJust)
import Text.Syntax
import Text.Syntax.Parser.Naive
import Text.Syntax.Printer.Naive
data TVShow = BobsBurgers | MrRobot | BatmanTAS deriving (Eq, Ord)
tvShow :: Syntax f => f TVShow
tvShow = pure BobsBurgers <* text "Bob's Burgers"
<|> pure MrRobot <* text "Mr. Robot"
<|> pure BatmanTAS <* text "Batman: The Animated Series"
runParser (Parser p) = p
instance Read TVShow where readsPrec _ = runParser tvShow
instance Show TVShow where show = fromJust . print tvShow
This is designed to be extensible to types more exciting than simple enumerations, as well.
Aha! I found some pre-existing code written by Simon Nicholls. This template haskell can be used to achieve what I wanted:
genData :: Name -> [Name] -> DecQ
genData name keys = dataD (cxt []) name [] cons [''Eq, ''Enum, ''Bounded]
where cons = map (\n -> normalC n []) keys
genShow :: Name -> [(Name, String)] -> DecQ
genShow name pairs =
instanceD (cxt [])
(appT (conT ''Show) (conT name))
[funD (mkName "show") $ map genClause pairs]
where
genClause (k, v) = clause [(conP k [])] (normalB [|v|]) []
mkEnum :: String -> [(String, String)] -> Q [Dec]
mkEnum name' pairs' =
do
ddec <- genData name (map fst pairs)
sdec <- genShow name pairs
rdec <- [d|instance Read $(conT name) where
readsPrec _ value =
case Map.lookup value m of
Just val -> [(val, [])]
Nothing -> []
where
m = Map.fromList $ map (show &&& id) [minBound..maxBound]|]
return $ ddec : sdec : rdec
where name = mkName name'
pairs = map (\(k, v) -> (mkName k, v)) pairs'
Usage:
$(mkEnum "TVShow"
[ ("BobsBurgers", "Bob's Burgers")
, ("MrRobot", "Mr. Robot")
, ("BatmanTAS", "Batman: The Animated Series")
])
(The QuasiQuotes weren't working, so I'll have to investigate that)
I came to this:
data FeedbackType
= Abuse
| AuthFailure
| Fraud
| NotSpam
| Virus
| Other
deriving (Eq)
instance Show FeedbackType where
show Abuse = "abuse"
show AuthFailure = "auth-failure"
show Fraud = "fraud"
show NotSpam = "not-spam"
show Virus = "virus"
show Other = "other"
instance Read FeedbackType where
readsPrec _ s
| s == show Abuse = [(Abuse, "")]
| s == show AuthFailure = [(AuthFailure, "")]
| s == show Fraud = [(Fraud, "")]
| s == show NotSpam = [(NotSpam, "")]
| s == show Virus = [(Virus, "")]
| s == show Other = [(Other, "")]
| otherwise = []

why not a case with predicate guards in addition to pattern guards?

why not a case with predicate guards in addition to pattern guards?
{-# LANGUAGE MultiWayIf, LambdaCase #-}
module Main where
import System.Info (os)
import Control.Applicative ((<$>))
import Data.Char (toLower)
import Data.List (isPrefixOf)
main :: IO ()
main = print . ($ toLower <$> os) $ \x -> if
| "mingw" `isPrefixOf` x -> "windows"
| "darwin" == x -> "mac"
| otherwise -> "linux"
would be prettier as:
main = print $ case toLower <$> os of
x | "mingw" `isPrefixOf` x -> "windows"
| "darwin" == x -> "mac"
| otherwise -> "linux"
or even:
main = print $ case toLower <$> os of
| ("mingw" `isPrefixOf`) -> "windows"
| ("darwin" ==) -> "mac"
| otherwise -> "linux" -- when pattern absent: otherwise = const True
Your first proposal is valid syntax, so just use it:
main = print $ case toLower <$> os of
x | "mingw" `isPrefixOf` x -> "windows"
| "darwin" == x -> "mac"
| otherwise -> "linux"
Edit: I thought you were aiming for a point-free case at all costs. Your pointful proposal is indeed correct syntax, as András Kovács points out.
Here is a pointless-style match:
{-# LANGUAGE ViewPatterns #-}
main = print $ case toLower <$> os of
(("mingw" `isPrefixOf`) -> True) -> "windows"
(("darwin" ==) -> True) -> "mac"
_ -> "linux"
or even, without view patterns:
match :: a -> [(a -> Bool, b)] -> b
match x ((f,y):rest) | f x = y
| otherwise = match x rest
match _ [] = error "Non exhaustive predicates"
main = print $ match (toLower <$> os)
[(("mingw" `isPrefixOf`) , "windows")
,(("darwin"==) , "mac")
,((const True) , "linux")]

Resources