initial commit
commit
f26f36281b
|
@ -0,0 +1,4 @@
|
||||||
|
.stack-work/
|
||||||
|
*~
|
||||||
|
hmonitors.cabal
|
||||||
|
stack.yaml.lock
|
|
@ -0,0 +1,30 @@
|
||||||
|
Copyright Author name here (c) 2020
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Author name here nor the names of other
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,11 @@
|
||||||
|
# hmonitors
|
||||||
|
|
||||||
|
This is a collection of monitors the I use as widgets in xmobar.
|
||||||
|
Currently implemented:
|
||||||
|
* battery (throug acpi)
|
||||||
|
* volume (throuugh alsa)
|
||||||
|
* network (through network-manager)
|
||||||
|
|
||||||
|
They are all one-shot scripts, intended for xmobar to run periodically.
|
||||||
|
They are very ad-hoc to my use case. Even the icons (using NerdFonts) and
|
||||||
|
colors (I use the gruvbox color scheme) are hard-coded.
|
|
@ -0,0 +1,24 @@
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Text.Printf
|
||||||
|
import System.Environment
|
||||||
|
|
||||||
|
import Monitors.Battery (queryBattery)
|
||||||
|
import Monitors.Date (queryDate)
|
||||||
|
import Monitors.Net (queryNet)
|
||||||
|
import Monitors.Volume (queryVolume)
|
||||||
|
|
||||||
|
usage :: IO String
|
||||||
|
usage = printf "%s battery | volume | net" <$> getProgName
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
output <- case args of
|
||||||
|
[ "bat" ] -> queryBattery
|
||||||
|
[ "vol" ] -> queryVolume
|
||||||
|
[ "net" ] -> queryNet
|
||||||
|
[ "date" ] -> queryDate True
|
||||||
|
[ "date-min" ] -> queryDate False
|
||||||
|
_ -> usage
|
||||||
|
putStrLn output
|
|
@ -0,0 +1,36 @@
|
||||||
|
name: hmonitors
|
||||||
|
version: 0.1.0.0
|
||||||
|
license: BSD3
|
||||||
|
author: "Ricard Illa"
|
||||||
|
maintainer: "r.illa.pujagut@gmail.com"
|
||||||
|
copyright: "2020 Ricard Illa Pujagut"
|
||||||
|
|
||||||
|
extra-source-files:
|
||||||
|
- README.md
|
||||||
|
|
||||||
|
description: Please see README.md
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- containers
|
||||||
|
- process
|
||||||
|
- regex-compat
|
||||||
|
- split
|
||||||
|
- time
|
||||||
|
|
||||||
|
library:
|
||||||
|
source-dirs: src
|
||||||
|
|
||||||
|
executables:
|
||||||
|
hmonitors-query:
|
||||||
|
main: Main.hs
|
||||||
|
source-dirs: app
|
||||||
|
ghc-options:
|
||||||
|
- -Wall
|
||||||
|
- -Werror
|
||||||
|
- -O2
|
||||||
|
- -threaded
|
||||||
|
- -rtsopts
|
||||||
|
- -with-rtsopts=-N
|
||||||
|
dependencies:
|
||||||
|
- hmonitors
|
|
@ -0,0 +1,134 @@
|
||||||
|
module Monitors.Battery (queryBattery) where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import Text.Printf
|
||||||
|
import Text.Regex
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
import Monitors.Common
|
||||||
|
|
||||||
|
data BatteryData = BatteryData
|
||||||
|
{ battery :: String
|
||||||
|
, state :: String
|
||||||
|
, percent :: Int
|
||||||
|
, time :: Maybe (Int,Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
dischargingIcons :: M.Map Int String
|
||||||
|
dischargingIcons = M.map buildIcon $ M.fromList
|
||||||
|
[ ( 0 , 62861 )
|
||||||
|
, ( 10 , 62841 )
|
||||||
|
, ( 20 , 62842 )
|
||||||
|
, ( 30 , 62843 )
|
||||||
|
, ( 40 , 62844 )
|
||||||
|
, ( 50 , 62845 )
|
||||||
|
, ( 60 , 62846 )
|
||||||
|
, ( 70 , 62847 )
|
||||||
|
, ( 80 , 62848 )
|
||||||
|
, ( 90 , 62849 )
|
||||||
|
, ( 100 , 62840 )
|
||||||
|
]
|
||||||
|
|
||||||
|
chargingIcons :: M.Map Int String
|
||||||
|
chargingIcons = M.map buildIcon $ M.fromList
|
||||||
|
[ ( 20 , 62853 )
|
||||||
|
, ( 30 , 62854 )
|
||||||
|
, ( 40 , 62855 )
|
||||||
|
, ( 60 , 62856 )
|
||||||
|
, ( 80 , 62857 )
|
||||||
|
, ( 90 , 62858 )
|
||||||
|
, ( 100 , 62852 )
|
||||||
|
]
|
||||||
|
|
||||||
|
alertIcon :: String
|
||||||
|
alertIcon = buildIcon 62850
|
||||||
|
|
||||||
|
unknownIcon :: String
|
||||||
|
unknownIcon = buildIcon 62864
|
||||||
|
|
||||||
|
fullIcon :: String
|
||||||
|
fullIcon = buildIcon 62851
|
||||||
|
|
||||||
|
getColor :: Int -> String
|
||||||
|
getColor charge = fromMaybe def (M.lookup getKey colors)
|
||||||
|
where
|
||||||
|
getKey
|
||||||
|
| charge < 20 = "red"
|
||||||
|
| charge >= 20 && charge < 80 = "yellow"
|
||||||
|
| charge >= 80 && charge <= 100 = "green"
|
||||||
|
| otherwise = "active"
|
||||||
|
def = "#ffffff"
|
||||||
|
|
||||||
|
iconSel :: Ord a => a -> M.Map a String -> String
|
||||||
|
iconSel a xs = fromMaybe def $ findKey a xs >>= lookupKey xs
|
||||||
|
where
|
||||||
|
findKey b ys = getNextAbove b (M.keys ys)
|
||||||
|
lookupKey = flip M.lookup
|
||||||
|
getNextAbove a = listToMaybe . dropWhile (< a) . sort
|
||||||
|
def = unknownIcon
|
||||||
|
|
||||||
|
getIcon :: BatteryData -> String
|
||||||
|
getIcon BatteryData { state = "Charging", percent = charge } = iconSel charge chargingIcons
|
||||||
|
getIcon BatteryData { state = "Discharging", percent = charge } = iconSel charge dischargingIcons
|
||||||
|
getIcon BatteryData { state = "Full" } = fullIcon
|
||||||
|
getIcon BatteryData { state = "Unknown" } = unknownIcon
|
||||||
|
getIcon _ = unknownIcon
|
||||||
|
|
||||||
|
parseBatteryInfo :: String -> Maybe BatteryData
|
||||||
|
parseBatteryInfo = fmap fmtBattery . matchRegex regex
|
||||||
|
where
|
||||||
|
regex = mkRegex "^(.+): (.+), ([0-9]+)%(, ([0-9]+):([0-9]+):[0-9]+ .+)?"
|
||||||
|
fmtBattery [b,s,p,_,h,m] =
|
||||||
|
let
|
||||||
|
t = if h /= "" && m /= ""
|
||||||
|
then Just (read h,read m)
|
||||||
|
else Nothing
|
||||||
|
in BatteryData
|
||||||
|
{ battery = b
|
||||||
|
, state = s
|
||||||
|
, percent = read p
|
||||||
|
, time = t
|
||||||
|
}
|
||||||
|
|
||||||
|
getColorIcon :: BatteryData -> String
|
||||||
|
getColorIcon BatteryData { state = "Full" } = colorize (colors M.! "active") fullIcon
|
||||||
|
getColorIcon x@BatteryData { percent = charge } = colorize (getColor charge) (getIcon x)
|
||||||
|
|
||||||
|
getStatus :: BatteryData -> String
|
||||||
|
getStatus x@BatteryData { state = "Full" } = getColorIcon x
|
||||||
|
|
||||||
|
getStatus x@BatteryData { state = "Unknown", percent = charge }
|
||||||
|
| charge >= 95 = getStatus x{ state="Full" }
|
||||||
|
| otherwise =
|
||||||
|
let
|
||||||
|
fmt = "%s %d"
|
||||||
|
colorIcon = getColorIcon x
|
||||||
|
in printf fmt colorIcon charge
|
||||||
|
|
||||||
|
getStatus x@BatteryData { state = "Charging", percent = charge } =
|
||||||
|
let
|
||||||
|
fmt = "%s %d"
|
||||||
|
colorIcon = getColorIcon x
|
||||||
|
in printf fmt colorIcon charge
|
||||||
|
|
||||||
|
getStatus x@BatteryData { state = "Discharging", percent = charge, time = (Just (h,m)) } =
|
||||||
|
let
|
||||||
|
fmt = "%s %d (%d:%02d)"
|
||||||
|
colorIcon = getColorIcon x
|
||||||
|
in printf fmt colorIcon charge h m
|
||||||
|
|
||||||
|
getStatus _ = colorize (colors M.! "active") unknownIcon
|
||||||
|
|
||||||
|
parseAcpiStdout :: String -> [BatteryData]
|
||||||
|
parseAcpiStdout = mapMaybe parseBatteryInfo . lines
|
||||||
|
|
||||||
|
fmtData :: [BatteryData] -> String
|
||||||
|
fmtData = (separator ++ ) . intercalate separator . map getStatus
|
||||||
|
|
||||||
|
runAcpi :: IO String
|
||||||
|
runAcpi = readProcess "acpi" ["-b"] ""
|
||||||
|
|
||||||
|
queryBattery :: IO String
|
||||||
|
queryBattery = fmtData . parseAcpiStdout <$> runAcpi
|
|
@ -0,0 +1,29 @@
|
||||||
|
module Monitors.Common
|
||||||
|
( colors
|
||||||
|
, colorize
|
||||||
|
, separator
|
||||||
|
, buildIcon
|
||||||
|
) where
|
||||||
|
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Text.Printf
|
||||||
|
import Data.Char
|
||||||
|
|
||||||
|
colors :: M.Map String String
|
||||||
|
colors = M.fromList
|
||||||
|
[ ( "active" , "#ebdbb2" )
|
||||||
|
, ( "inactive" , "#a89974" )
|
||||||
|
, ( "red" , "#fb4944" )
|
||||||
|
, ( "yellow" , "#fabd2f" )
|
||||||
|
, ( "green" , "#b8bb26" )
|
||||||
|
, ( "blue" , "#83a587" )
|
||||||
|
]
|
||||||
|
|
||||||
|
colorize :: String -> String -> String
|
||||||
|
colorize = printf "<fc=%s>%s</fc>"
|
||||||
|
|
||||||
|
separator :: String
|
||||||
|
separator = printf " %s " . colorize (colors M.! "inactive") $ "|"
|
||||||
|
|
||||||
|
buildIcon :: Int -> String
|
||||||
|
buildIcon = printf "<fn=1>%c</fn>" . chr
|
|
@ -0,0 +1,31 @@
|
||||||
|
module Monitors.Date (queryDate) where
|
||||||
|
|
||||||
|
import Data.Time
|
||||||
|
import Text.Printf
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
import Monitors.Common
|
||||||
|
|
||||||
|
|
||||||
|
icons :: M.Map String String
|
||||||
|
icons = M.map buildIcon $ M.fromList
|
||||||
|
[ ( "clock" , 63055 )
|
||||||
|
, ( "calendar" , 62957 )
|
||||||
|
]
|
||||||
|
|
||||||
|
fullFmtter :: Bool -> String -> String -> String
|
||||||
|
fullFmtter True icon x = printf "%s %s" (icons M.! icon) x
|
||||||
|
fullFmtter _ _ x = x
|
||||||
|
|
||||||
|
fmtter :: FormatTime t => Bool -> String -> String -> t -> String
|
||||||
|
fmtter full icon fmt = (fullFmtter full icon) . (formatTime defaultTimeLocale fmt)
|
||||||
|
|
||||||
|
fmtTime :: FormatTime t => Bool -> t -> String
|
||||||
|
fmtTime full time =
|
||||||
|
let
|
||||||
|
date = fmtter full "calendar" "%m/%d" time
|
||||||
|
hour = fmtter full "clock" "%H:%M" time
|
||||||
|
in printf "%s%s%s%s " separator date separator hour
|
||||||
|
|
||||||
|
queryDate :: Bool -> IO String
|
||||||
|
queryDate full = fmtTime full <$> getZonedTime
|
|
@ -0,0 +1,124 @@
|
||||||
|
module Monitors.Net (queryNet) where
|
||||||
|
|
||||||
|
import Data.List.Split
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import Text.Printf
|
||||||
|
import Text.Regex
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
import Monitors.Common
|
||||||
|
|
||||||
|
data DeviceType = Wifi | Ethernet | OtherDevice
|
||||||
|
|
||||||
|
data DeviceData = DeviceData
|
||||||
|
{ deviceName :: String
|
||||||
|
, deviceType :: DeviceType
|
||||||
|
, connected :: Bool
|
||||||
|
, signal :: Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
icons :: M.Map String String
|
||||||
|
icons = M.map buildIcon $ M.fromList
|
||||||
|
[ ( "wifi" , 64168 )
|
||||||
|
, ( "wifi-off" , 64169 )
|
||||||
|
, ( "ethernet" , 63231 )
|
||||||
|
]
|
||||||
|
|
||||||
|
offline :: String
|
||||||
|
offline = colorize (colors M.! "inactive") (icons M.! "wifi-off")
|
||||||
|
|
||||||
|
getConnectivity :: IO String
|
||||||
|
getConnectivity = head . lines <$> readProcess "nmcli" args ""
|
||||||
|
where args = ["networking", "connectivity", "check"]
|
||||||
|
|
||||||
|
getDevStatus :: IO [DeviceData]
|
||||||
|
getDevStatus = readProcess "nmcli" args "" >>= mapM buildDeviceData . lines
|
||||||
|
where
|
||||||
|
args = ["--terse", "--fields", "device,type,state", "device", "status"]
|
||||||
|
|
||||||
|
buildDeviceData :: String -> IO DeviceData
|
||||||
|
buildDeviceData x =
|
||||||
|
let
|
||||||
|
[d,t,state] = splitOn ":" x
|
||||||
|
deviceType = readDeviceType t
|
||||||
|
connected = state == "connected"
|
||||||
|
in do
|
||||||
|
signal <- getWifiSignal deviceType d
|
||||||
|
return DeviceData
|
||||||
|
{ deviceName = d
|
||||||
|
, deviceType = deviceType
|
||||||
|
, connected = connected
|
||||||
|
, signal = signal
|
||||||
|
}
|
||||||
|
|
||||||
|
readDeviceType :: String -> DeviceType
|
||||||
|
readDeviceType "wifi" = Wifi
|
||||||
|
readDeviceType "ethernet" = Ethernet
|
||||||
|
readDeviceType _ = OtherDevice
|
||||||
|
|
||||||
|
getWifiSignal :: DeviceType -> String -> IO (Maybe Int)
|
||||||
|
getWifiSignal Wifi dev = parseStdout <$> readProcess "nmcli" args ""
|
||||||
|
where
|
||||||
|
args =
|
||||||
|
[ "--terse"
|
||||||
|
, "--fields", "in-use,signal"
|
||||||
|
, "device", "wifi", "list"
|
||||||
|
, "ifname", dev
|
||||||
|
]
|
||||||
|
splt = splitOn ":"
|
||||||
|
isActive = (=="*") . head . splt
|
||||||
|
readVal = read . (!! 1) . splt
|
||||||
|
parseStdout = fmap readVal . find isActive . lines
|
||||||
|
getWifiSignal _ _ = return Nothing
|
||||||
|
|
||||||
|
getActiveDevs :: IO [DeviceData]
|
||||||
|
getActiveDevs = filter activeDev <$> getDevStatus
|
||||||
|
where
|
||||||
|
activeDev :: DeviceData -> Bool
|
||||||
|
activeDev DeviceData { deviceType = Wifi, connected = True } = True
|
||||||
|
activeDev DeviceData { deviceType = Ethernet, connected = True } = True
|
||||||
|
activeDev _ = False
|
||||||
|
|
||||||
|
getSignalColor :: Maybe Int -> String
|
||||||
|
getSignalColor x = fromMaybe (colors M.! def) (M.lookup (getKey x) colors)
|
||||||
|
where
|
||||||
|
def = "active"
|
||||||
|
getKey Nothing = def
|
||||||
|
getKey (Just x)
|
||||||
|
| x < 30 = "red"
|
||||||
|
| x >= 30 && x < 60 = "yellow"
|
||||||
|
| x >= 60 && x <= 100 = "green"
|
||||||
|
| otherwise = def
|
||||||
|
|
||||||
|
|
||||||
|
makeDevIcon :: DeviceData -> String
|
||||||
|
|
||||||
|
makeDevIcon DeviceData { deviceType = Ethernet } =
|
||||||
|
colorize (colors M.! "active") (icons M.! "ethernet")
|
||||||
|
|
||||||
|
makeDevIcon DeviceData { deviceType = Wifi, signal = signal@(Just s) } =
|
||||||
|
let
|
||||||
|
colorIcon = colorize (getSignalColor signal) (icons M.! "wifi")
|
||||||
|
txt = colorize (colors M.! "active") (show s)
|
||||||
|
in printf "%s %s" colorIcon txt
|
||||||
|
|
||||||
|
makeDevIcon DeviceData { deviceType = Wifi, signal = Nothing } =
|
||||||
|
colorize (getSignalColor Nothing) (icons M.! "wifi")
|
||||||
|
|
||||||
|
makeDevIcon _ = offline
|
||||||
|
|
||||||
|
|
||||||
|
getStatus :: [DeviceData] -> String
|
||||||
|
getStatus = intercalate separator . map makeDevIcon
|
||||||
|
|
||||||
|
|
||||||
|
queryNet :: IO String
|
||||||
|
queryNet = do
|
||||||
|
connectivity <- getConnectivity
|
||||||
|
--icon <- if connectivity `elem` ["none", "limited"]
|
||||||
|
icon <- if connectivity == "none"
|
||||||
|
then return offline
|
||||||
|
else getStatus <$> getActiveDevs
|
||||||
|
return $ separator ++ icon
|
|
@ -0,0 +1,86 @@
|
||||||
|
module Monitors.Volume (queryVolume) where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Process
|
||||||
|
import Text.Printf
|
||||||
|
import Text.Regex
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
import Monitors.Common
|
||||||
|
|
||||||
|
mixer :: String
|
||||||
|
mixer = "Master"
|
||||||
|
|
||||||
|
icons :: M.Map String String
|
||||||
|
icons = M.map buildIcon $ M.fromList
|
||||||
|
[ ( "high" , 64125 )
|
||||||
|
, ( "low" , 64126 )
|
||||||
|
, ( "mid" , 64127 )
|
||||||
|
, ( "off" , 64128 )
|
||||||
|
, ( "mute" , 64605 )
|
||||||
|
]
|
||||||
|
|
||||||
|
data MixerData = MixerData
|
||||||
|
{ mute :: Bool
|
||||||
|
, vol :: Int
|
||||||
|
}
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
getIcon :: MixerData -> String
|
||||||
|
getIcon x = fromMaybe (icons M.! def) $ M.lookup (iconKey x) icons
|
||||||
|
where
|
||||||
|
iconKey MixerData { mute = True } = "mute"
|
||||||
|
iconKey MixerData { vol = vol }
|
||||||
|
| vol >= 80 = "high"
|
||||||
|
| vol >= 10 && vol < 80 = "mid"
|
||||||
|
| vol < 10 = "low"
|
||||||
|
| otherwise = def
|
||||||
|
def = "high"
|
||||||
|
|
||||||
|
getColor :: MixerData -> String
|
||||||
|
getColor x = fromMaybe (colors M.! def) $ M.lookup (colorKey x) colors
|
||||||
|
where
|
||||||
|
colorKey MixerData { mute = True } = "inactive"
|
||||||
|
colorKey MixerData { vol = vol }
|
||||||
|
| vol > 110 = "red"
|
||||||
|
| vol > 100 && vol <= 110 = "yellow"
|
||||||
|
| vol > 70 && vol <= 100 = "green"
|
||||||
|
| vol > 0 && vol <= 70 = "blue"
|
||||||
|
| vol == 0 = "inactive"
|
||||||
|
| otherwise = def
|
||||||
|
def = "active"
|
||||||
|
|
||||||
|
getStatus :: MixerData -> String
|
||||||
|
getStatus x@MixerData { mute = mute, vol = vol } =
|
||||||
|
let
|
||||||
|
txtColor = if mute then colors M.! "inactive" else colors M.! "active"
|
||||||
|
icon = colorize (getColor x) (getIcon x)
|
||||||
|
txt = colorize txtColor $ show vol
|
||||||
|
in printf "%s %s" icon txt
|
||||||
|
|
||||||
|
parseMixerInfo :: String -> Maybe MixerData
|
||||||
|
parseMixerInfo = fmap fmtMixer . matchRegex regex
|
||||||
|
where
|
||||||
|
regex = mkRegex ".+: Playback [0-9]+ \\[([0-9]+)%\\] (\\[.+dB\\] )?\\[(on|off)\\]"
|
||||||
|
fmtMixer [volume,_,state] =
|
||||||
|
let
|
||||||
|
mute = case state of
|
||||||
|
"on" -> False
|
||||||
|
"off" -> True
|
||||||
|
_ -> False
|
||||||
|
vol = read volume
|
||||||
|
in MixerData { mute = mute, vol = vol }
|
||||||
|
|
||||||
|
parseAmixerStdout :: String -> [MixerData]
|
||||||
|
parseAmixerStdout = nub . mapMaybe parseMixerInfo . lines
|
||||||
|
|
||||||
|
fmtData :: [MixerData] -> String
|
||||||
|
fmtData = (separator ++ ) . intercalate separator . map getStatus
|
||||||
|
|
||||||
|
runAmixer :: String -> IO String
|
||||||
|
runAmixer mixer = readProcess "amixer" ["get", mixer] ""
|
||||||
|
|
||||||
|
|
||||||
|
queryVolume :: IO String
|
||||||
|
queryVolume = fmtData . parseAmixerStdout <$> runAmixer mixer
|
|
@ -0,0 +1,67 @@
|
||||||
|
# This file was automatically generated by 'stack init'
|
||||||
|
#
|
||||||
|
# Some commonly used options have been documented as comments in this file.
|
||||||
|
# For advanced use and comprehensive documentation of the format, please see:
|
||||||
|
# https://docs.haskellstack.org/en/stable/yaml_configuration/
|
||||||
|
|
||||||
|
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
|
||||||
|
# A snapshot resolver dictates the compiler version and the set of packages
|
||||||
|
# to be used for project dependencies. For example:
|
||||||
|
#
|
||||||
|
# resolver: lts-3.5
|
||||||
|
# resolver: nightly-2015-09-21
|
||||||
|
# resolver: ghc-7.10.2
|
||||||
|
#
|
||||||
|
# The location of a snapshot can be provided as a file or url. Stack assumes
|
||||||
|
# a snapshot provided as a file might change, whereas a url resource does not.
|
||||||
|
#
|
||||||
|
# resolver: ./custom-snapshot.yaml
|
||||||
|
# resolver: https://example.com/snapshots/2018-01-01.yaml
|
||||||
|
resolver:
|
||||||
|
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/20.yaml
|
||||||
|
|
||||||
|
# User packages to be built.
|
||||||
|
# Various formats can be used as shown in the example below.
|
||||||
|
#
|
||||||
|
# packages:
|
||||||
|
# - some-directory
|
||||||
|
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
|
||||||
|
# subdirs:
|
||||||
|
# - auto-update
|
||||||
|
# - wai
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
# Dependency packages to be pulled from upstream that are not in the resolver.
|
||||||
|
# These entries can reference officially published versions as well as
|
||||||
|
# forks / in-progress versions pinned to a git hash. For example:
|
||||||
|
#
|
||||||
|
# extra-deps:
|
||||||
|
# - acme-missiles-0.3
|
||||||
|
# - git: https://github.com/commercialhaskell/stack.git
|
||||||
|
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
|
||||||
|
#
|
||||||
|
# extra-deps: []
|
||||||
|
|
||||||
|
# Override default flag values for local packages and extra-deps
|
||||||
|
# flags: {}
|
||||||
|
|
||||||
|
# Extra package databases containing global packages
|
||||||
|
# extra-package-dbs: []
|
||||||
|
|
||||||
|
# Control whether we use the GHC we find on the path
|
||||||
|
# system-ghc: true
|
||||||
|
#
|
||||||
|
# Require a specific version of stack, using version ranges
|
||||||
|
# require-stack-version: -any # Default
|
||||||
|
# require-stack-version: ">=2.5"
|
||||||
|
#
|
||||||
|
# Override the architecture used by stack, especially useful on Windows
|
||||||
|
# arch: i386
|
||||||
|
# arch: x86_64
|
||||||
|
#
|
||||||
|
# Extra directories used by stack for building
|
||||||
|
# extra-include-dirs: [/path/to/dir]
|
||||||
|
# extra-lib-dirs: [/path/to/dir]
|
||||||
|
#
|
||||||
|
# Allow a newer minor version of GHC than the snapshot specifies
|
||||||
|
# compiler-check: newer-minor
|
Loading…
Reference in New Issue