Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • jens/qbar
  • jktr/qbar
  • snowball/qbar
3 results
Show changes
Commits on Source (64)
.stack-work/
stack.yaml.lock
qbar.cabal
*~ *~
.*.swp
/result
/result-*
/dist-newstyle
/TODO /TODO
# Changelog for qbar
## Unreleased changes
...@@ -6,31 +6,45 @@ qbar is a status command for [sway](https://swaywm.org/) and [i3](https://i3wm.o ...@@ -6,31 +6,45 @@ qbar is a status command for [sway](https://swaywm.org/) and [i3](https://i3wm.o
### Compiling from Source ### Compiling from Source
Building qbar requires [stack](https://haskellstack.org/). To build it run: Building qbar requires nix. To build it run:
``` ```
stack build nix build
``` ```
You can also use the scripts in the `bin`-directory (all scripts will rebuild the project if required): You can also use the scripts in the `bin`-directory (all scripts will rebuild the project if required):
``` ```
# Launch as swaybar status command while redirecting stderr to shell # Launch as swaybar status command while redirecting stderr to shell
./bin/run-sway default ./bin/run-sway default
```
## Configuration
All configuration is currently done with command line arguments. The executable uses a command-style interface (e.g. `qbar theme rainbow`) which supports `--help` at every level of the tree. It also provides bash, zsh and fish tab completions.
# Run the binary directly (mostly used to control the bar via rpc) ### Sway
./bin/run --help
# Install the binary to ~/.local/bin Use the following `status_command`:
./bin/install
```
qbar server swaybar default
``` ```
## Configuration You can specify a custom set of blocks:
```
qbar server swaybar date cpu network script ~/bin/my_script
```
### i3
i3 runs the status command for every screen that shows a bar. To reuse the output of your primary display you can use mirror mode:
```
# Configure for primary display
qbar server i3bar default
Custom configuration is currently only possible from Haskell: # Configure for other displays
qbar mirror i3bar
``` ```
myConfig :: BarIO ()
myConfig = do
addBlock dateBlock
main :: IO () Theming is not supported on mirrored servers.
main = parseOptions >>= runQBar myConfig
```
\ No newline at end of file
import Distribution.Simple
main = defaultMain
#!/usr/bin/env zsh
set -e
set -u
readonly executable_name=qbar
readonly local_bin=`stack path --local-bin`
readonly executable_path=$local_bin/$executable_name
stack install
echo >&2
if [[ -n "${LOCAL_ZSH_COMPLETION_PATH+set}" ]]
then
if [[ -d "$LOCAL_ZSH_COMPLETION_PATH" ]]
then
ZSH_COMPLETION_SCRIPT_PATH=$LOCAL_ZSH_COMPLETION_PATH/_$executable_name
$executable_path --zsh-completion-script $executable_path > $ZSH_COMPLETION_SCRIPT_PATH
echo "Installed zsh completions for $executable_name to $ZSH_COMPLETION_SCRIPT_PATH" >&2
else
echo "Not installing completions, LOCAL_ZSH_COMPLETION_PATH is set but isn't a directory ($LOCAL_ZSH_COMPLETION_PATH)." >&2
fi
else
echo "Not installing completions, set LOCAL_ZSH_COMPLETION_PATH to install them." >&2
fi
#!/bin/sh
set -e
set -u
readonly executable_name=qbar
stack build && stack exec $executable_name -- "$@"
#!/bin/sh #!/usr/bin/env bash
set -e set -e
set -u set -u
set -o pipefail set -o pipefail
readonly executable_name=qbar
readonly sway_bar_id=bar-0 readonly sway_bar_id=bar-0
readonly default_bar_command="$(swaymsg -t get_bar_config "$sway_bar_id" | jq .status_command)" readonly default_bar_command="$(swaymsg -t get_bar_config "$sway_bar_id" | jq .status_command)"
if [[ -z "$default_bar_command" ]]; then
echo "Cannot get the default bar command" >&2
exit 1
fi
stack build stack build
readonly temp_dir=$(mktemp -d) readonly temp_dir=$(mktemp -d)
...@@ -17,7 +21,7 @@ mkfifo $stderr ...@@ -17,7 +21,7 @@ mkfifo $stderr
trap "swaymsg bar $sway_bar_id status_command '$default_bar_command'; rm -rf $temp_dir" EXIT INT HUP TERM trap "swaymsg bar $sway_bar_id status_command '$default_bar_command'; rm -rf $temp_dir" EXIT INT HUP TERM
swaymsg bar $sway_bar_id status_command "exec $(stack path --local-install-root)/bin/$executable_name server swaybar $* 2> $stderr" swaymsg bar $sway_bar_id status_command "exec nix run . -- server swaybar default $* 2> $stderr"
# show output and run forever (use Ctrl-C to stop) # show output and run forever (use Ctrl-C to stop)
cat $stderr cat $stderr
packages: */*.cabal
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1681303793,
"narHash": "sha256-JEdQHsYuCfRL2PICHlOiH/2ue3DwoxUX7DJ6zZxZXFk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fe2ecaf706a5907b5e54d979fbde4924d84b65fc",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"quasar": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1682474992,
"narHash": "sha256-tyN64/4weIGmlYMOOrVwFaBVyqvt8du9APhCO9vkV14=",
"owner": "queezle42",
"repo": "quasar",
"rev": "76faa881ae2e3a5575fba9f9e374931dbb8e6495",
"type": "github"
},
"original": {
"owner": "queezle42",
"repo": "quasar",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"quasar": "quasar"
}
}
},
"root": "root",
"version": 7
}
{
inputs = {
nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
quasar.url = github:queezle42/quasar;
quasar.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, quasar }:
with nixpkgs.lib;
let
systems = platforms.unix;
forAllSystems = genAttrs systems;
getHaskellPackages = pkgs: pattern: pipe pkgs.haskell.packages [
attrNames
(filter (x: !isNull (strings.match pattern x)))
(sort (x: y: x>y))
(map (x: pkgs.haskell.packages.${x}))
head
];
in {
packages = forAllSystems (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [
self.overlays.default
quasar.overlays.default
];
};
haskellPackages = getHaskellPackages pkgs "ghc94.";
results = {
qbar = haskellPackages.qbar;
};
in results // {
default = pkgs.linkFarm "qbar-all" (results // mapAttrs' (k: v: nameValuePair "${k}-doc" (v.doc or pkgs.emptyDirectory)) results);
}
);
apps = forAllSystems (system: {
default = {
type = "app";
program = "${self.packages.${system}.qbar}/bin/qbar";
};
});
overlays.default = final: prev: {
haskell = prev.haskell // {
packageOverrides = hfinal: hprev: prev.haskell.packageOverrides hfinal hprev // {
qbar = hfinal.generateOptparseApplicativeCompletions ["qbar"]
(hfinal.callCabal2nix "qbar" ./qbar {});
};
};
};
devShells = forAllSystems (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [
self.overlays.default
quasar.overlays.default
];
};
haskellPackages = getHaskellPackages pkgs "ghc94.";
in rec {
default = haskellPackages.shellFor {
packages = hpkgs: [
hpkgs.qbar
];
nativeBuildInputs = [
haskellPackages.haskell-language-server
pkgs.cabal-install
pkgs.hlint
# in addition, for ghcid-wrapper
pkgs.entr
pkgs.ghcid
pkgs.zsh
];
};
}
);
};
}
#!/usr/bin/env -S nix develop -L -c zsh
set -euo pipefail
readonly target=${1:-lib:qbar}
readonly executable=${2:-}
if [[ -n $executable ]]
then
run_executable="cabal run \
--disable-optimisation \
--ghc-option -fdiagnostics-color=always \
$executable \
"
else
run_executable=true
fi
print -P %F{yellow}Cleaning repository%f
cabal clean
(
git ls-files 'examples/*' '*/test' '*/*.cabal' 'flake.*'
echo "ghcid-wrapper"
) | \
entr -r \
nix develop -L -c \
ghcid \
--warnings \
"--command=cabal repl $target" \
"--test=:! \
cabal test \
--disable-optimisation \
--test-show-details=direct \
--ghc-option -fdiagnostics-color=always && \
$run_executable && \
zsh -c 'print -P %F{green}Build and tests passed%f' \
"
cradle:
cabal:
name: qbar
version: 0.1.0.0
#github: "githubuser/qbar"
license: BSD3
author: "Jens Nolte"
#maintainer: "example@example.com"
copyright: "2019 Jens Nolte"
extra-source-files:
- README.md
- ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
#description: Please see the README on GitHub at <https://github.com/githubuser/qbar#readme>
dependencies:
- base >= 4.7 && < 5
- aeson
- async
- attoparsec
- bytestring
- colour
- concurrent-extra
- containers
- dbus
- directory
- filepath
- lens
- mtl
- network
- optparse-applicative
- pipes
- pipes-aeson
- pipes-concurrency
- pipes-network
- pipes-parse
- pipes-safe
- random
- sorted-list
- stm
- text
- time
- typed-process
- unix
- unordered-containers
default-extensions:
- OverloadedStrings
- NamedFieldPuns
- LambdaCase
- MultiWayIf
- ScopedTypeVariables
ghc-options:
- -fwarn-unused-do-bind
- -fwarn-tabs
- -Wall
- -Werror
- -Wwarn=deprecations
- -O2
library:
source-dirs: src
other-modules:
- Prelude
- BasePrelude
executables:
qbar:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
- -with-rtsopts=-I0
dependencies:
- qbar
tests:
qbar-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- qbar
module Main where module Main (main) where
import QBar.Cli import QBar.Cli
import QBar.Prelude
main :: IO () main :: IO ()
main = runQBar main = runQBar
cabal-version: 3.0
name: qbar
version: 0.1.0.0
author: Jens Nolte
maintainer: Jens Nolte
copyright: 2019 Jens Nolte
license: BSD-3-Clause
build-type: Simple
source-repository head
type: git
location: https://github.com/queezle42/qbar
common shared-properties
default-language: GHC2021
default-extensions:
ApplicativeDo
DuplicateRecordFields
LambdaCase
MultiWayIf
NoImplicitPrelude
OverloadedStrings
ghc-options:
-Weverything
-Wno-all-missed-specialisations
-Wno-missing-deriving-strategies
-Wno-implicit-prelude
-Wno-missing-import-lists
-Wno-missing-kind-signatures
-Wno-missing-safe-haskell-mode
-Wno-unsafe
-Werror=incomplete-patterns
-Werror=missing-fields
-Werror=missing-home-modules
-Werror=missing-methods
library
import: shared-properties
build-depends:
aeson,
async,
attoparsec,
base >=4.7 && <5,
binary,
bytestring,
colour,
concurrent-extra,
containers,
dbus,
directory,
filepath,
gitrev,
hostname,
lens,
mtl,
network,
optparse-applicative,
pipes,
pipes-aeson,
pipes-concurrency,
pipes-network,
pipes-parse,
pipes-safe,
quasar,
quasar-timer,
random,
sorted-list,
stm,
text,
time,
typed-process,
unix,
unordered-containers,
exposed-modules:
QBar.BlockHelper
QBar.BlockOutput
QBar.Blocks
QBar.Blocks.Battery
QBar.Blocks.CpuUsage
QBar.Blocks.Date
QBar.Blocks.DiskUsage
QBar.Blocks.NetworkManager
QBar.Blocks.Pipe
QBar.Blocks.Qubes
QBar.Blocks.Script
QBar.Blocks.Squeekboard
QBar.Blocks.Utils
QBar.Cli
QBar.Color
QBar.ControlSocket
QBar.Core
QBar.DefaultConfig
QBar.Host
QBar.Pango
QBar.Prelude
QBar.Qubes.AdminAPI
QBar.Server
QBar.TagParser
QBar.Theme
QBar.Time
QBar.Utils
other-modules:
hs-source-dirs:
src
executable qbar
import: shared-properties
ghc-options:
-threaded
-rtsopts
"-with-rtsopts=-N -I0"
build-depends:
qbar,
hs-source-dirs:
app
main-is: Main.hs
test-suite qbar-test
import: shared-properties
type: exitcode-stdio-1.0
ghc-options:
-threaded
-rtsopts
"-with-rtsopts=-N -I0"
main-is: Spec.hs
other-modules:
Paths_qbar
hs-source-dirs:
test
build-depends:
base >=4.7 && <5,
{-# LANGUAGE RankNTypes #-} module QBar.BlockHelper (
PollBlock',
module QBar.BlockHelper where PollBlock,
PollSignal,
Signal(..),
SignalBlock,
SignalBlockConfiguration(..),
respondBlockUpdate,
respondEmptyBlockUpdate,
runPollBlock',
runPollBlock,
runSignalBlock,
runSignalBlockConfiguration,
runSignalBlockFn',
runSignalBlockFn,
yieldBlockUpdate,
yieldEmptyBlockUpdate,
) where
import QBar.BlockOutput import QBar.BlockOutput
import QBar.Core import QBar.Core
import QBar.Prelude
import QBar.Time import QBar.Time
import Control.Concurrent.Async import Control.Concurrent.Async
import qualified Control.Concurrent.Event as Event import Control.Concurrent.Event qualified as Event
import Control.Concurrent.STM.TChan import Control.Concurrent.STM.TChan
import Control.Concurrent.STM.TVar import Control.Concurrent.STM.TVar
import Control.Monad.Reader (ReaderT) import Control.Monad.Reader (ReaderT)
import Control.Monad.State (StateT, evalStateT, get, put) import Control.Lens
import Data.Either (isRight) import Data.Either (isRight)
import Pipes import Pipes
import Pipes.Concurrent import Pipes.Concurrent
...@@ -93,15 +109,33 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre ...@@ -93,15 +109,33 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre
-- Initialize -- Initialize
signalChan <- liftIO newTChanIO signalChan <- liftIO newTChanIO
signalEvent <- liftIO Event.new signalEvent <- liftIO Event.new
isInvalidatedVar <- liftIO $ newTVarIO False -- renderStateVar: (current BlockUpdate or Nothing when signal block terminated, invalidated)
renderStateVar <- liftIO $ newTVarIO (Just (Nothing, PollUpdate), False)
-- renderEvent: Signals an update to renderStateVar
renderEvent <- liftIO Event.new
runSignalBlockWithThreadInternal signalChan signalEvent isInvalidatedVar runSignalBlockWithThreadInternal signalChan signalEvent renderStateVar renderEvent
where where
runSignalBlockWithThreadInternal :: TChan (Signal p) -> Event.Event -> TVar Bool -> Block runSignalBlockWithThreadInternal :: TChan (Signal p) -> Event.Event -> TVar (Maybe BlockUpdate, Bool) -> Event.Event -> Block
runSignalBlockWithThreadInternal signalChan signalEvent isInvalidatedVar = do runSignalBlockWithThreadInternal signalChan signalEvent renderStateVar renderEvent = do
bracket aquire' release' (\(context, _, _) -> void (signalBlock context +>> signalPipe)) generatorTask <- barAsync $ bracket aquire' release' (\(context, _, _) -> runEffect $ void (signalBlock context +>> signalPipe))
exitBlock liftIO $ link generatorTask
renderer
where where
renderer :: Block
renderer = do
liftIO $ Event.wait renderEvent
liftIO $ Event.clear renderEvent
currentState <- liftIO (readTVarIO renderStateVar)
renderer' currentState
where
renderer' :: (Maybe BlockUpdate, Bool) -> Block
renderer' (Just (blockState, reason), invalidated) = do
yield $ if invalidated then (invalidateBlockState blockState, reason) else (blockState, reason)
renderer
renderer' (Nothing, _) = exitBlock
aquire' :: ReaderT Bar IO (c, Async (), Async ()) aquire' :: ReaderT Bar IO (c, Async (), Async ())
aquire' = runSafeT $ do aquire' = runSafeT $ do
context <- aquire userSignalAction context <- aquire userSignalAction
...@@ -118,6 +152,9 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre ...@@ -118,6 +152,9 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre
release' :: (c, Async (), Async ()) -> ReaderT Bar IO () release' :: (c, Async (), Async ()) -> ReaderT Bar IO ()
release' (context, userTask, intervalTask) = do release' (context, userTask, intervalTask) = do
-- Signal block termination to render thread
liftIO . atomically $ modifyTVar renderStateVar (_1 .~ Nothing)
liftIO $ do liftIO $ do
cancel userTask cancel userTask
cancel intervalTask cancel intervalTask
...@@ -130,63 +167,41 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre ...@@ -130,63 +167,41 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre
atomically $ writeTChan signalChan $ UserSignal value atomically $ writeTChan signalChan $ UserSignal value
Event.set signalEvent Event.set signalEvent
signalPipe :: Proxy (Signal p) (Maybe BlockOutput) () BlockUpdate BarIO ExitBlock
signalPipe = do
initial <- request RegularSignal
let initialUpdate = (mkBlockStateWithHandler initial, PollUpdate)
yield initialUpdate
evalStateT stateSignalPipe initialUpdate
mkBlockStateWithHandler :: Maybe BlockOutput -> BlockState mkBlockStateWithHandler :: Maybe BlockOutput -> BlockState
mkBlockStateWithHandler Nothing = Nothing mkBlockStateWithHandler Nothing = Nothing
mkBlockStateWithHandler (Just output) = Just (output, Just signalEventHandler) mkBlockStateWithHandler (Just output) = Just (output, Just signalEventHandler)
stateSignalPipe :: StateT BlockUpdate (Proxy (Signal p) (Maybe BlockOutput) () BlockUpdate BarIO) ExitBlock signalPipe :: Client (Signal p) (Maybe BlockOutput) BarIO ExitBlock
stateSignalPipe = forever $ do signalPipe = forever $ do
-- Handle all queued events -- Handle all queued events
eventHandled <- sendQueuedEvents eventHandled <- sendQueuedSignals
-- If there was no queued event signal a regular event -- If there was no queued event signal a regular event
unless eventHandled $ outputAndStore RegularSignal unless eventHandled $ sendSignal RegularSignal
-- Wait for next event -- Wait for next event
liftIO $ Event.wait signalEvent liftIO $ Event.wait signalEvent
liftIO $ Event.clear signalEvent liftIO $ Event.clear signalEvent
where where
sendQueuedEvents :: StateT BlockUpdate (Proxy (Signal p) (Maybe BlockOutput) () BlockUpdate BarIO) Bool sendQueuedSignals :: Client (Signal p) (Maybe BlockOutput) BarIO Bool
sendQueuedEvents = do sendQueuedSignals = do
maybeSignal <- liftIO . atomically $ tryReadTChan signalChan maybeSignal <- liftIO . atomically $ tryReadTChan signalChan
case maybeSignal of case maybeSignal of
Just signal -> do Just signal -> sendSignal signal >> sendQueuedSignals >> return True
case signal of
EventSignal _ -> do
(state, _) <- get
lift $ yield (invalidateBlockState state, EventUpdate)
_ -> return ()
outputAndStore signal
void sendQueuedEvents
return True
Nothing -> return False Nothing -> return False
outputAndStore :: Signal p -> StateT BlockUpdate (Proxy (Signal p) (Maybe BlockOutput) () BlockUpdate BarIO) () sendSignal :: Signal p -> Client (Signal p) (Maybe BlockOutput) BarIO ()
outputAndStore signal = do sendSignal signal = do
maybeOutput <- lift $ request signal maybeOutput <- request signal
invalidate <- if isEventSignal signal
then do
-- Reset invalidate flag
liftIO . atomically $ writeTVar isInvalidatedVar False
return False
else
liftIO . atomically $ readTVar isInvalidatedVar
let state = mkBlockStateWithHandler maybeOutput let
let state' = if invalidate then invalidateBlockState state else state updateInvalidatedState :: (Maybe BlockUpdate, Bool) -> (Maybe BlockUpdate, Bool)
updateInvalidatedState = if isEventSignal signal then _2 .~ False else id
let update = (state', signalToReason signal) let blockUpdate = (mkBlockStateWithHandler maybeOutput, signalToReason signal)
put update liftIO . atomically $ modifyTVar renderStateVar ((_1 . _Just .~ blockUpdate) . updateInvalidatedState)
lift $ yield update liftIO $ Event.set renderEvent
signalToReason :: Signal a -> BlockUpdateReason signalToReason :: Signal a -> BlockUpdateReason
signalToReason (UserSignal _) = DefaultUpdate signalToReason (UserSignal _) = DefaultUpdate
...@@ -209,14 +224,16 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre ...@@ -209,14 +224,16 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre
signalEventHandler :: BlockEventHandler signalEventHandler :: BlockEventHandler
signalEventHandler event = do signalEventHandler event = do
wasInvalidated' <- liftIO . atomically $ do wasInvalidatedBefore' <- liftIO . atomically $ do
wasInvalidated <- readTVar isInvalidatedVar (_, wasInvalidatedBefore) <- readTVar renderStateVar
unless wasInvalidated $ do unless wasInvalidatedBefore $ do
writeTChan signalChan $ EventSignal event writeTChan signalChan $ EventSignal event
writeTVar isInvalidatedVar True modifyTVar renderStateVar ((_2 .~ True) . (_1 . _Just . _2 .~ EventUpdate))
return wasInvalidated return wasInvalidatedBefore
unless wasInvalidated' $ liftIO $ Event.set signalEvent unless wasInvalidatedBefore' $ liftIO $ do
Event.set renderEvent
Event.set signalEvent
-- |Converts a 'PollBlock' to a 'Block' by running it whenever the 'defaultInterval' is triggered. -- |Converts a 'PollBlock' to a 'Block' by running it whenever the 'defaultInterval' is triggered.
......
{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -Wno-partial-fields #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
module QBar.BlockOutput where module QBar.BlockOutput (
BlockOutput(..),
BlockText(..),
BlockTextSegment(..),
Importance(..),
activeImportantText,
activeText,
addIcon,
blockName,
criticalImportant',
criticalImportant,
emptyBlock,
errorImportant',
errorImportant,
fullText,
importantText,
invalid,
invalidateBlock,
isCritical,
isError,
isNormal,
isWarning,
mkBlockOutput',
mkBlockOutput,
mkErrorOutput,
mkStyledText,
mkText,
normalImportant',
normalImportant,
normalText,
printedLength,
rawText,
shortText,
surroundWith,
toImportance,
warnImportant',
warnImportant,
) where
import QBar.Color import QBar.Color
import QBar.Prelude
import Control.Lens import Control.Lens
import Data.Aeson import Data.Aeson
import Data.Aeson.TH import Data.Char
import Data.Int (Int64) import Data.Int (Int64)
import qualified Data.Text.Lazy as T import Data.Text.Lazy qualified as T
import GHC.Generics
data BlockOutput = BlockOutput { data BlockOutput = BlockOutput {
...@@ -18,11 +57,10 @@ data BlockOutput = BlockOutput { ...@@ -18,11 +57,10 @@ data BlockOutput = BlockOutput {
_blockName :: Maybe T.Text, _blockName :: Maybe T.Text,
_invalid :: Bool _invalid :: Bool
} }
deriving (Eq, Show) deriving (Eq, Show, Generic)
newtype BlockText = BlockText [BlockTextSegment] newtype BlockText = BlockText [BlockTextSegment]
deriving (Eq, Show) deriving (Eq, Show, Generic)
instance Semigroup BlockText where instance Semigroup BlockText where
(BlockText a) <> (BlockText b) = BlockText (a <> b) (BlockText a) <> (BlockText b) = BlockText (a <> b)
instance Monoid BlockText where instance Monoid BlockText where
...@@ -38,18 +76,24 @@ data BlockTextSegment = BlockTextSegment { ...@@ -38,18 +76,24 @@ data BlockTextSegment = BlockTextSegment {
color :: Maybe Color, color :: Maybe Color,
backgroundColor :: Maybe Color backgroundColor :: Maybe Color
} }
deriving (Eq, Show) deriving (Eq, Show, Generic)
data Importance = NormalImportant Float | WarnImportant Float | ErrorImportant Float | CriticalImportant Float data Importance = NormalImportant Float | WarnImportant Float | ErrorImportant Float | CriticalImportant Float
deriving (Eq, Show) deriving (Eq, Show, Generic)
instance FromJSON BlockOutput
instance ToJSON BlockOutput
$(deriveJSON defaultOptions ''BlockOutput) instance FromJSON BlockText
makeLenses ''BlockOutput instance ToJSON BlockText
$(deriveJSON defaultOptions ''Importance)
$(deriveJSON defaultOptions ''BlockTextSegment) instance FromJSON BlockTextSegment
$(deriveJSON defaultOptions ''BlockText) instance ToJSON BlockTextSegment
instance FromJSON Importance
instance ToJSON Importance
makeLenses ''BlockOutput
mkBlockOutput :: BlockText -> BlockOutput mkBlockOutput :: BlockText -> BlockOutput
mkBlockOutput text = BlockOutput { mkBlockOutput text = BlockOutput {
...@@ -166,8 +210,31 @@ printedLength (BlockText b) = sum . map segmentLength $ b ...@@ -166,8 +210,31 @@ printedLength (BlockText b) = sum . map segmentLength $ b
mkText :: Bool -> Importance -> T.Text -> BlockText mkText :: Bool -> Importance -> T.Text -> BlockText
mkText active importance segmentText = BlockText [BlockTextSegment { segmentText = pangoFriendly segmentText, active, importance }] mkText active importance segmentText = BlockText [BlockTextSegment { segmentText = pangoFriendly segmentText, active, importance }]
where where
toHex :: Int -> T.Text
toHex 1 = "1"
toHex 2 = "2"
toHex 3 = "3"
toHex 4 = "4"
toHex 5 = "5"
toHex 6 = "6"
toHex 7 = "7"
toHex 8 = "8"
toHex 9 = "9"
toHex 10 = "A"
toHex 11 = "B"
toHex 12 = "C"
toHex 13 = "D"
toHex 14 = "E"
toHex 15 = "F"
toHex x
| x <= 0 = "0"
| otherwise = toHex (div x 16) <> toHex (mod x 16)
pangoFriendlyIcon :: Char -> T.Text
pangoFriendlyIcon x
| isAlphaNum x || isAscii x = T.singleton x
| otherwise = "&#x" <> toHex (ord x) <> ";"
pangoFriendly :: T.Text -> T.Text pangoFriendly :: T.Text -> T.Text
pangoFriendly = T.replace "<" "&lt;" . T.replace ">" "&gt;" . T.replace "&" "&amp;" pangoFriendly = T.concatMap pangoFriendlyIcon . T.replace "<" "&lt;" . T.replace ">" "&gt;" . T.replace "&" "&amp;"
activeImportantText :: Importance -> T.Text -> BlockText activeImportantText :: Importance -> T.Text -> BlockText
activeImportantText = mkText True activeImportantText = mkText True
......
module QBar.Blocks (
QBar.Blocks.Battery.batteryBlock,
QBar.Blocks.CpuUsage.cpuUsageBlock,
QBar.Blocks.Date.dateBlock,
QBar.Blocks.DiskUsage.diskUsageBlock,
QBar.Blocks.NetworkManager.networkManagerBlock,
QBar.Blocks.Qubes.diskUsageQubesBlock,
QBar.Blocks.Qubes.qubesMonitorPropertyBlock,
QBar.Blocks.Qubes.qubesVMCountBlock,
QBar.Blocks.Script.scriptBlock,
QBar.Blocks.Script.pollScriptBlock,
QBar.Blocks.Squeekboard.squeekboardBlock,
) where
import QBar.Blocks.Battery qualified
import QBar.Blocks.CpuUsage qualified
import QBar.Blocks.Date qualified
import QBar.Blocks.DiskUsage qualified
import QBar.Blocks.NetworkManager qualified
import QBar.Blocks.Qubes qualified
import QBar.Blocks.Script qualified
import QBar.Blocks.Squeekboard qualified
{-# LANGUAGE MultiWayIf #-} module QBar.Blocks.Battery (
{-# LANGUAGE ScopedTypeVariables #-} batteryBlock,
) where
module QBar.Blocks.Battery where
import QBar.BlockHelper import QBar.BlockHelper
import QBar.Core import QBar.Core
import QBar.Blocks.Utils import QBar.Blocks.Utils
import QBar.BlockOutput import QBar.BlockOutput
import QBar.Prelude
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as TIO
import System.Directory
import Data.Maybe
import Control.Lens import Control.Lens
import Data.Maybe (catMaybes, mapMaybe)
import Data.Text.Lazy qualified as T
import Data.Text.Lazy.IO qualified as TIO
import System.Directory
data BatteryStatus = BatteryCharging | BatteryDischarging | BatteryOther data BatteryStatus = BatteryCharging | BatteryDischarging | BatteryOther
deriving (Show) deriving (Eq, Show)
data BatteryState = BatteryState {
data BatteryState = BatteryState _status :: BatteryStatus,
{ _status :: BatteryStatus _powerNow :: Maybe Int,
, _powerNow :: Maybe Int _energyNow :: Int,
, _energyNow :: Int _energyFull :: Int
, _energyFull :: Int
} deriving (Show) } deriving (Show)
getBatteryState :: FilePath -> IO (Maybe BatteryState) getBatteryState :: FilePath -> IO (Maybe BatteryState)
getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatteryStateEnergy getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatteryStateEnergy
where where
...@@ -40,12 +35,12 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter ...@@ -40,12 +35,12 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter
energyNow' <- readIO =<< readFile (path <> "/energy_now") energyNow' <- readIO =<< readFile (path <> "/energy_now")
energyFull' <- readIO =<< readFile (path <> "/energy_full") energyFull' <- readIO =<< readFile (path <> "/energy_full")
powerNow' <- batteryPower getVoltage powerNow' <- batteryPower getVoltage
return BatteryState return BatteryState {
{ _status = status' _status = status',
, _powerNow = powerNow' _powerNow = powerNow',
, _energyNow = energyNow' _energyNow = energyNow',
, _energyFull = energyFull' _energyFull = energyFull'
} }
getBatteryStateCharge :: IO (Maybe BatteryState) getBatteryStateCharge :: IO (Maybe BatteryState)
getBatteryStateCharge = tryMaybe $ do getBatteryStateCharge = tryMaybe $ do
status' <- batteryStatus status' <- batteryStatus
...@@ -53,12 +48,12 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter ...@@ -53,12 +48,12 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter
powerNow' <- batteryPower (return voltageNow') powerNow' <- batteryPower (return voltageNow')
chargeNow' <- readIO =<< readFile (path <> "/charge_now") chargeNow' <- readIO =<< readFile (path <> "/charge_now")
chargeFull' <- readIO =<< readFile (path <> "/charge_full") chargeFull' <- readIO =<< readFile (path <> "/charge_full")
return BatteryState return BatteryState {
{ _status = status' _status = status',
, _powerNow = powerNow' _powerNow = powerNow',
, _energyNow = round $ voltageNow' * chargeNow' / 1000000 _energyNow = round $ voltageNow' * chargeNow' / 1000000,
, _energyFull = round $ voltageNow' * chargeFull' / 1000000 _energyFull = round $ voltageNow' * chargeFull' / 1000000
} }
batteryPower :: IO Double -> IO (Maybe Int) batteryPower :: IO Double -> IO (Maybe Int)
batteryPower getVoltage' = do batteryPower getVoltage' = do
power' <- tryMaybe $ readIO =<< readFile (path <> "/power_now") power' <- tryMaybe $ readIO =<< readFile (path <> "/power_now")
...@@ -71,10 +66,10 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter ...@@ -71,10 +66,10 @@ getBatteryState path = maybe getBatteryStateCharge (return . Just) =<< getBatter
batteryStatus :: IO BatteryStatus batteryStatus :: IO BatteryStatus
batteryStatus = do batteryStatus = do
statusText <- tryMaybe $ T.strip <$> TIO.readFile (path <> "/status") statusText <- tryMaybe $ T.strip <$> TIO.readFile (path <> "/status")
return $ if return $
| statusText == Just "Charging" -> BatteryCharging if | statusText == Just "Charging" -> BatteryCharging
| statusText == Just "Discharging" -> BatteryDischarging | statusText == Just "Discharging" -> BatteryDischarging
| otherwise -> BatteryOther | otherwise -> BatteryOther
batteryBlock :: Block batteryBlock :: Block
...@@ -102,7 +97,7 @@ batteryBlock = runPollBlock $ forever $ do ...@@ -102,7 +97,7 @@ batteryBlock = runPollBlock $ forever $ do
updateBatteryBlock :: Bool -> [BatteryState] -> PollBlock' () updateBatteryBlock :: Bool -> [BatteryState] -> PollBlock' ()
updateBatteryBlock _ [] = yieldEmptyBlockUpdate updateBatteryBlock _ [] = yieldEmptyBlockUpdate
updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ mkBlockOutput fullText' updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText .~ shortText') $ mkBlockOutput fullText'
where where
fullText' :: BlockText fullText' :: BlockText
fullText' = overallPercentage <> optionalEachBattery <> optionalOverallEstimate fullText' = overallPercentage <> optionalEachBattery <> optionalOverallEstimate
...@@ -118,6 +113,7 @@ updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ m ...@@ -118,6 +113,7 @@ updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ m
optionalEachBattery :: BlockText optionalEachBattery :: BlockText
optionalEachBattery optionalEachBattery
| length bs < 2 = mempty | length bs < 2 = mempty
| batteryIsFull bs = mempty
| otherwise = normalText " " <> eachBattery | otherwise = normalText " " <> eachBattery
eachBattery :: BlockText eachBattery :: BlockText
...@@ -142,7 +138,6 @@ updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ m ...@@ -142,7 +138,6 @@ updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ m
batteryImportance :: [BatteryState] -> Importance batteryImportance :: [BatteryState] -> Importance
batteryImportance = toImportance (0, 60, 80, 90, 100) . (100 -) . batteryPercentage batteryImportance = toImportance (0, 60, 80, 90, 100) . (100 -) . batteryPercentage
batteryPercentage :: [BatteryState] -> Float batteryPercentage :: [BatteryState] -> Float
batteryPercentage batteryStates batteryPercentage batteryStates
| batteryEnergyFull == 0 = 0 | batteryEnergyFull == 0 = 0
...@@ -164,6 +159,17 @@ batteryEstimateFormated batteryStates = do ...@@ -164,6 +159,17 @@ batteryEstimateFormated batteryStates = do
return $ normalText $ (T.pack . show $ allHours) <> ":" <> (T.justifyRight 2 '0' . T.pack . show $ minutes) return $ normalText $ (T.pack . show $ allHours) <> ":" <> (T.justifyRight 2 '0' . T.pack . show $ minutes)
batteryIsFull :: [BatteryState] -> Bool
batteryIsFull = all singleBatteryIsFull
where
singleBatteryIsFull :: BatteryState -> Bool
singleBatteryIsFull bs@BatteryState{_status}
| _status == BatteryCharging = False
| _status == BatteryDischarging = False
| 95 >= batteryPercentage [bs] = False
| otherwise = True
batteryIsCharging :: [BatteryState] -> Bool batteryIsCharging :: [BatteryState] -> Bool
batteryIsCharging = any (singleBatteryIsCharging . _status) batteryIsCharging = any (singleBatteryIsCharging . _status)
where where
...@@ -183,8 +189,8 @@ batteryIsDischarging = any (singleBatteryIsDischarging . _status) ...@@ -183,8 +189,8 @@ batteryIsDischarging = any (singleBatteryIsDischarging . _status)
batteryEstimate :: [BatteryState] -> Maybe Int batteryEstimate :: [BatteryState] -> Maybe Int
batteryEstimate batteryStates batteryEstimate batteryStates
| batteryPowerNow == 0 = Nothing | batteryPowerNow == 0 = Nothing
| isCharging, not isDischarging = ensure (>0) batteryEstimateCharging | isCharging, not isDischarging = ensure (> 0) batteryEstimateCharging
| isDischarging, not isCharging = ensure (>0) batteryEstimateDischarging | isDischarging, not isCharging = ensure (> 0) batteryEstimateDischarging
| otherwise = Nothing | otherwise = Nothing
where where
isCharging :: Bool isCharging :: Bool
......
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
module QBar.Blocks.CpuUsage where module QBar.Blocks.CpuUsage (
cpuUsageBlock,
) where
import QBar.BlockHelper import QBar.BlockHelper
import QBar.BlockOutput
import QBar.Blocks.Utils
import QBar.Core
import QBar.Prelude
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Control.Lens import Control.Lens
import Control.Monad.State (StateT, evalStateT, lift) import Control.Monad.State (StateT, evalStateT, lift)
import qualified Data.Attoparsec.Text.Lazy as AT import Data.Attoparsec.Text.Lazy qualified as AT
import qualified Data.Text.Lazy as T import Data.Text.Lazy qualified as T
import QBar.BlockOutput
import QBar.Blocks.Utils
import QBar.Core
{- {-
For time accounting the guest fields need to be ignored according to the kernel source code For time accounting the guest fields need to be ignored according to the kernel source code
...@@ -19,18 +22,16 @@ import QBar.Core ...@@ -19,18 +22,16 @@ import QBar.Core
the accounting also counts the guest time to user or nice respectively so applications the accounting also counts the guest time to user or nice respectively so applications
that are not aware of the new fields do not loose time. that are not aware of the new fields do not loose time.
-} -}
data CpuStat data CpuStat = CpuStat {
= CpuStat userTime :: Int,
{ userTime :: Int, niceTime :: Int,
niceTime :: Int, systemTime :: Int,
systemTime :: Int, idleTime :: Int,
idleTime :: Int, iowaitTime :: Int,
iowaitTime :: Int, irqTime :: Int,
irqTime :: Int, softirqTime :: Int,
softirqTime :: Int, stealTime :: Int
stealTime :: Int } deriving (Show)
}
deriving (Show)
getCpuStat :: IO (Maybe CpuStat) getCpuStat :: IO (Maybe CpuStat)
getCpuStat = parseFile "/proc/stat" cpuStat getCpuStat = parseFile "/proc/stat" cpuStat
...@@ -49,53 +50,48 @@ getCpuStat = parseFile "/proc/stat" cpuStat ...@@ -49,53 +50,48 @@ getCpuStat = parseFile "/proc/stat" cpuStat
irqTime' <- AT.skipSpace *> AT.decimal irqTime' <- AT.skipSpace *> AT.decimal
softirqTime' <- AT.skipSpace *> AT.decimal softirqTime' <- AT.skipSpace *> AT.decimal
stealTime' <- AT.skipSpace *> AT.decimal stealTime' <- AT.skipSpace *> AT.decimal
return $ return $ CpuStat {
CpuStat userTime = userTime',
{ userTime = userTime', niceTime = niceTime',
niceTime = niceTime', systemTime = systemTime',
systemTime = systemTime', idleTime = idleTime',
idleTime = idleTime', iowaitTime = iowaitTime',
iowaitTime = iowaitTime', irqTime = irqTime',
irqTime = irqTime', softirqTime = softirqTime',
softirqTime = softirqTime', stealTime = stealTime'
stealTime = stealTime' }
}
differenceCpuStat :: CpuStat -> CpuStat -> CpuStat differenceCpuStat :: CpuStat -> CpuStat -> CpuStat
differenceCpuStat a b = differenceCpuStat a b = CpuStat {
CpuStat userTime = userTime a - userTime b,
{ userTime = userTime a - userTime b, niceTime = niceTime a - niceTime b,
niceTime = niceTime a - niceTime b, systemTime = systemTime a - systemTime b,
systemTime = systemTime a - systemTime b, idleTime = idleTime a - idleTime b,
idleTime = idleTime a - idleTime b, iowaitTime = iowaitTime a - iowaitTime b,
iowaitTime = iowaitTime a - iowaitTime b, irqTime = irqTime a - irqTime b,
irqTime = irqTime a - irqTime b, softirqTime = softirqTime a - softirqTime b,
softirqTime = softirqTime a - softirqTime b, stealTime = stealTime a - stealTime b
stealTime = stealTime a - stealTime b }
}
cpuTotalTime :: Num a => CpuStat -> a cpuTotalTime :: Num a => CpuStat -> a
cpuTotalTime cpuTotalTime
CpuStat CpuStat { userTime,
{ userTime, niceTime,
niceTime, systemTime,
systemTime, idleTime,
idleTime, iowaitTime,
iowaitTime, irqTime,
irqTime, softirqTime,
softirqTime, stealTime
stealTime } = fromIntegral . sum $ [ userTime,
} = niceTime,
fromIntegral . sum $ systemTime,
[ userTime, idleTime,
niceTime, iowaitTime,
systemTime, irqTime,
idleTime, softirqTime,
iowaitTime, stealTime
irqTime, ]
softirqTime,
stealTime
]
cpuUsage :: CpuStat -> Float cpuUsage :: CpuStat -> Float
cpuUsage stat@CpuStat {idleTime, iowaitTime} = 1 - (totalIdleTime / totalTime) cpuUsage stat@CpuStat {idleTime, iowaitTime} = 1 - (totalIdleTime / totalTime)
...@@ -105,12 +101,10 @@ cpuUsage stat@CpuStat {idleTime, iowaitTime} = 1 - (totalIdleTime / totalTime) ...@@ -105,12 +101,10 @@ cpuUsage stat@CpuStat {idleTime, iowaitTime} = 1 - (totalIdleTime / totalTime)
totalIdleTime :: Num a => a totalIdleTime :: Num a => a
totalIdleTime = fromIntegral $ idleTime + iowaitTime totalIdleTime = fromIntegral $ idleTime + iowaitTime
data CpuBlockState data CpuBlockState = CpuBlockState {
= CpuBlockState _lastCpuStat :: CpuStat,
{ _lastCpuStat :: CpuStat, _lastCpuUsage :: Float
_lastCpuUsage :: Float } deriving (Show)
}
deriving (Show)
makeLenses ''CpuBlockState makeLenses ''CpuBlockState
...@@ -124,13 +118,12 @@ cpuUsageBlock decimalPlaces = runPollBlock $ evalStateT cpuUsageBlock' createSta ...@@ -124,13 +118,12 @@ cpuUsageBlock decimalPlaces = runPollBlock $ evalStateT cpuUsageBlock' createSta
text <- ("💻\xFE0E " <>) <$> cpuUsageText text <- ("💻\xFE0E " <>) <$> cpuUsageText
lift $ yieldBlockUpdate $ mkBlockOutput $ importantText importance text lift $ yieldBlockUpdate $ mkBlockOutput $ importantText importance text
createState :: CpuBlockState createState :: CpuBlockState
createState = createState = CpuBlockState {
CpuBlockState _lastCpuStat = CpuStat 0 0 0 0 0 0 0 0,
{ _lastCpuStat = CpuStat 0 0 0 0 0 0 0 0, _lastCpuUsage = 0
_lastCpuUsage = 0 }
}
cpuUsageImportance :: Monad m => StateT CpuBlockState m Importance cpuUsageImportance :: Monad m => StateT CpuBlockState m Importance
cpuUsageImportance = toImportance (0, 60, 80, 90 ,100) <$> use lastCpuUsage cpuUsageImportance = toImportance (0, 60, 80, 90, 100) <$> use lastCpuUsage
cpuUsageTextWidth :: Num a => a cpuUsageTextWidth :: Num a => a
cpuUsageTextWidth cpuUsageTextWidth
| decimalPlaces == 0 = 3 | decimalPlaces == 0 = 3
......