diff --git a/ChangeLog.md b/ChangeLog.md deleted file mode 100644 index 26a2a1a9294076bcba354b833189edc594d2214d..0000000000000000000000000000000000000000 --- a/ChangeLog.md +++ /dev/null @@ -1,3 +0,0 @@ -# Changelog for qbar - -## Unreleased changes diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000000000000000000000000000000000000..f44a24c63747ed4eb654da9ca84338cf1e223380 --- /dev/null +++ b/cabal.project @@ -0,0 +1 @@ +packages: */*.cabal diff --git a/flake.lock b/flake.lock index 35b657e4658a528143693aad6920d6f020c6d802..a24b15b2964169b6d762803f27508d02e10f9d9e 100644 --- a/flake.lock +++ b/flake.lock @@ -16,25 +16,11 @@ "type": "github" } }, - "nixpkgs_2": { - "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_2" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1682474992, diff --git a/flake.nix b/flake.nix index 3a8d4b95966b946083a50b64ac878a1d244d22c4..7895b1dd5ee8825cbcb32cdd1dd93044226da216 100644 --- a/flake.nix +++ b/flake.nix @@ -2,6 +2,7 @@ inputs = { nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; quasar.url = github:queezle42/quasar; + quasar.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = { self, nixpkgs, quasar }: @@ -19,10 +20,13 @@ in { packages = forAllSystems (system: let - pkgs = import nixpkgs { inherit system; overlays = [ - self.overlays.default - quasar.overlays.default - ]; }; + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + quasar.overlays.default + ]; + }; haskellPackages = getHaskellPackages pkgs "ghc94."; results = { qbar = haskellPackages.qbar; @@ -36,17 +40,20 @@ haskell = prev.haskell // { packageOverrides = hfinal: hprev: prev.haskell.packageOverrides hfinal hprev // { qbar = hfinal.generateOptparseApplicativeCompletions ["qbar"] - (hfinal.callCabal2nix "qbar" ./. {}); + (hfinal.callCabal2nix "qbar" ./qbar {}); }; }; }; devShells = forAllSystems (system: let - pkgs = import nixpkgs { inherit system; overlays = [ - self.overlays.default - quasar.overlays.default - ]; }; + pkgs = import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + quasar.overlays.default + ]; + }; haskellPackages = getHaskellPackages pkgs "ghc94."; in rec { default = haskellPackages.shellFor { @@ -54,16 +61,17 @@ hpkgs.qbar ]; nativeBuildInputs = [ + haskellPackages.haskell-language-server pkgs.cabal-install - pkgs.zsh + pkgs.hlint + + # in addition, for ghcid-wrapper pkgs.entr pkgs.ghcid - haskellPackages.haskell-language-server - pkgs.hlint + pkgs.zsh ]; }; } ); }; } - diff --git a/ghcid b/ghcid deleted file mode 100755 index c3d50ccb6cab4f1a767993720c0832b6f8b6c2b5..0000000000000000000000000000000000000000 --- a/ghcid +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env -S nix develop . -c zsh - -print -P %F{yellow}Cleaning repository%f -nix develop -c cabal clean - -(git ls-files test; git ls-files '*.cabal'; git ls-files 'flake.*') | \ - entr -r \ - nix develop -c \ - ghcid \ - --warnings \ - "--command=cabal repl lib:qbar" \ - "--test=:! \ - cabal test --disable-optimisation --enable-debug-info=2 --test-show-details=direct --ghc-option -fdiagnostics-color=always && \ - cabal build --disable-optimisation --enable-debug-info=2 --ghc-option -fdiagnostics-color=always && \ - zsh -c 'print -P %F{green}Build and tests passed%f' \ - " diff --git a/ghcid-wrapper b/ghcid-wrapper new file mode 100755 index 0000000000000000000000000000000000000000..dbee7e5b31ad6fb1af93191ab2db846c6de3bb7f --- /dev/null +++ b/ghcid-wrapper @@ -0,0 +1,38 @@ +#!/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' \ + " diff --git a/hie.yaml b/hie.yaml new file mode 100644 index 0000000000000000000000000000000000000000..04cd24395e4d108febbd22b6ce41a92f7fe0d065 --- /dev/null +++ b/hie.yaml @@ -0,0 +1,2 @@ +cradle: + cabal: diff --git a/app/Main.hs b/qbar/app/Main.hs similarity index 72% rename from app/Main.hs rename to qbar/app/Main.hs index c0e6c83e0cec08954c428283893755a185739d33..3631ce6f3c4515b040237ce205e0b30f1bd17abb 100644 --- a/app/Main.hs +++ b/qbar/app/Main.hs @@ -1,4 +1,4 @@ -module Main where +module Main (main) where import QBar.Cli import QBar.Prelude diff --git a/qbar.cabal b/qbar/qbar.cabal similarity index 68% rename from qbar.cabal rename to qbar/qbar.cabal index 4fc91965916e8c41f3256bdcd3cb5ab43702a3dd..ef2477f16a48f200d083afea92e73d74413a6886 100644 --- a/qbar.cabal +++ b/qbar/qbar.cabal @@ -1,80 +1,41 @@ 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 -license-file: LICENSE build-type: Simple -extra-source-files: - README.md - ChangeLog.md --source-repository head -- type: git --- location: https://git.c3pb.de/jens/qbar.git +-- location: https:////git.c3pb.de/jens/qbar.git common shared-properties + default-language: GHC2021 default-extensions: - AllowAmbiguousTypes - BangPatterns - BlockArguments - ConstraintKinds - DataKinds - DefaultSignatures - DeriveAnyClass - DeriveGeneric - DerivingStrategies - DisambiguateRecordFields + ApplicativeDo DuplicateRecordFields - ExistentialQuantification - FlexibleContexts - FlexibleInstances - FunctionalDependencies - GADTs - GeneralizedNewtypeDeriving - ImportQualifiedPost - InstanceSigs LambdaCase - -- Enable once 9.0.1 is required - --LexicalNegation - MultiParamTypeClasses - NamedFieldPuns + MultiWayIf NoImplicitPrelude - NumericUnderscores OverloadedStrings - PolyKinds - RankNTypes - ScopedTypeVariables - StandaloneDeriving - TemplateHaskell - TupleSections - TypeApplications - TypeFamilies - TypeOperators - ViewPatterns - default-language: Haskell2010 ghc-options: -Weverything -Wno-all-missed-specialisations - -Wno-missing-safe-haskell-mode - -Wno-missing-kind-signatures + -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 -common shared-executable-properties - import: shared-properties - ghc-options: - -threaded - -rtsopts - "-with-rtsopts=-N -I0" - library import: shared-properties build-depends: @@ -140,24 +101,29 @@ library QBar.Theme QBar.Time QBar.Utils + other-modules: hs-source-dirs: src - default-extensions: - MultiWayIf executable qbar - import: shared-executable-properties - main-is: Main.hs - other-modules: - Paths_qbar - hs-source-dirs: - app + 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-executable-properties + import: shared-properties type: exitcode-stdio-1.0 + ghc-options: + -threaded + -rtsopts + "-with-rtsopts=-N -I0" main-is: Spec.hs other-modules: Paths_qbar @@ -165,4 +131,3 @@ test-suite qbar-test test build-depends: base >=4.7 && <5, - qbar, diff --git a/src/QBar/BlockHelper.hs b/qbar/src/QBar/BlockHelper.hs similarity index 94% rename from src/QBar/BlockHelper.hs rename to qbar/src/QBar/BlockHelper.hs index 6707c2a1f36de18472b13df63d660083d2225964..102ec89a440216459fa49ad07e8dd6d3bcd7ee54 100644 --- a/src/QBar/BlockHelper.hs +++ b/qbar/src/QBar/BlockHelper.hs @@ -1,4 +1,21 @@ -module QBar.BlockHelper where +module QBar.BlockHelper ( + PollBlock', + PollBlock, + PollSignal, + Signal(..), + SignalBlock, + SignalBlockConfiguration(..), + respondBlockUpdate, + respondEmptyBlockUpdate, + runPollBlock', + runPollBlock, + runSignalBlock, + runSignalBlockConfiguration, + runSignalBlockFn', + runSignalBlockFn, + yieldBlockUpdate, + yieldEmptyBlockUpdate, +) where import QBar.BlockOutput import QBar.Core @@ -6,7 +23,7 @@ import QBar.Prelude import QBar.Time 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.TVar import Control.Monad.Reader (ReaderT) @@ -110,7 +127,7 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre liftIO $ Event.wait renderEvent liftIO $ Event.clear renderEvent - currentState <- liftIO . atomically $ readTVar renderStateVar + currentState <- liftIO (readTVarIO renderStateVar) renderer' currentState where renderer' :: (Maybe BlockUpdate, Bool) -> Block @@ -178,7 +195,9 @@ runSignalBlockConfiguration SignalBlockConfiguration{aquire, release, signalThre sendSignal signal = do maybeOutput <- request signal - let updateInvalidatedState = if isEventSignal signal then (_2 .~ False) else id + let + updateInvalidatedState :: (Maybe BlockUpdate, Bool) -> (Maybe BlockUpdate, Bool) + updateInvalidatedState = if isEventSignal signal then _2 .~ False else id let blockUpdate = (mkBlockStateWithHandler maybeOutput, signalToReason signal) liftIO . atomically $ modifyTVar renderStateVar ((_1 . _Just .~ blockUpdate) . updateInvalidatedState) diff --git a/src/QBar/BlockOutput.hs b/qbar/src/QBar/BlockOutput.hs similarity index 83% rename from src/QBar/BlockOutput.hs rename to qbar/src/QBar/BlockOutput.hs index 35b1016775e2125fcbe11dbcf362951a3c51d8fb..f1ece12691d21c79a2688c20a6ee84c9bc117ec3 100644 --- a/src/QBar/BlockOutput.hs +++ b/qbar/src/QBar/BlockOutput.hs @@ -1,15 +1,53 @@ +{-# OPTIONS_GHC -Wno-partial-fields #-} {-# 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.Prelude import Control.Lens import Data.Aeson -import Data.Aeson.TH import Data.Int (Int64) -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T +import GHC.Generics data BlockOutput = BlockOutput { @@ -18,11 +56,10 @@ data BlockOutput = BlockOutput { _blockName :: Maybe T.Text, _invalid :: Bool } - deriving (Eq, Show) - + deriving (Eq, Show, Generic) newtype BlockText = BlockText [BlockTextSegment] - deriving (Eq, Show) + deriving (Eq, Show, Generic) instance Semigroup BlockText where (BlockText a) <> (BlockText b) = BlockText (a <> b) instance Monoid BlockText where @@ -38,18 +75,24 @@ data BlockTextSegment = BlockTextSegment { color :: Maybe Color, backgroundColor :: Maybe Color } - deriving (Eq, Show) + deriving (Eq, Show, Generic) 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) -makeLenses ''BlockOutput -$(deriveJSON defaultOptions ''Importance) -$(deriveJSON defaultOptions ''BlockTextSegment) -$(deriveJSON defaultOptions ''BlockText) +instance FromJSON BlockText +instance ToJSON BlockText + +instance FromJSON BlockTextSegment +instance ToJSON BlockTextSegment +instance FromJSON Importance +instance ToJSON Importance + +makeLenses ''BlockOutput mkBlockOutput :: BlockText -> BlockOutput mkBlockOutput text = BlockOutput { diff --git a/qbar/src/QBar/Blocks.hs b/qbar/src/QBar/Blocks.hs new file mode 100644 index 0000000000000000000000000000000000000000..0fb55d9f58dd1509f68c56db8ee73e9e5791619f --- /dev/null +++ b/qbar/src/QBar/Blocks.hs @@ -0,0 +1,22 @@ +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 diff --git a/src/QBar/Blocks/Battery.hs b/qbar/src/QBar/Blocks/Battery.hs similarity index 96% rename from src/QBar/Blocks/Battery.hs rename to qbar/src/QBar/Blocks/Battery.hs index a93b6f83104972024da6fafaea3641d1c149be33..3779bbc5be5ea2de7382aed9572afeaf5eb19326 100644 --- a/src/QBar/Blocks/Battery.hs +++ b/qbar/src/QBar/Blocks/Battery.hs @@ -1,4 +1,6 @@ -module QBar.Blocks.Battery where +module QBar.Blocks.Battery ( + batteryBlock, +) where import QBar.BlockHelper import QBar.Core @@ -8,8 +10,8 @@ import QBar.Prelude import Control.Lens import Data.Maybe (catMaybes, mapMaybe) -import qualified Data.Text.Lazy as T -import qualified Data.Text.Lazy.IO as TIO +import Data.Text.Lazy qualified as T +import Data.Text.Lazy.IO qualified as TIO import System.Directory data BatteryStatus = BatteryCharging | BatteryDischarging | BatteryOther @@ -95,7 +97,7 @@ batteryBlock = runPollBlock $ forever $ do updateBatteryBlock :: Bool -> [BatteryState] -> PollBlock' () updateBatteryBlock _ [] = yieldEmptyBlockUpdate -updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText.~shortText') $ mkBlockOutput fullText' +updateBatteryBlock isPlugged bs = yieldBlockUpdate $ (shortText .~ shortText') $ mkBlockOutput fullText' where fullText' :: BlockText fullText' = overallPercentage <> optionalEachBattery <> optionalOverallEstimate diff --git a/src/QBar/Blocks/CpuUsage.hs b/qbar/src/QBar/Blocks/CpuUsage.hs similarity index 97% rename from src/QBar/Blocks/CpuUsage.hs rename to qbar/src/QBar/Blocks/CpuUsage.hs index f74b05b9a1f1f8f4f4d7bdedd7cc6288f957d35b..ad54bc9d1912d7c2cb7be1e1b4118c3f9a63ff78 100644 --- a/src/QBar/Blocks/CpuUsage.hs +++ b/qbar/src/QBar/Blocks/CpuUsage.hs @@ -1,6 +1,8 @@ {-# LANGUAGE TemplateHaskell #-} -module QBar.Blocks.CpuUsage where +module QBar.Blocks.CpuUsage ( + cpuUsageBlock, +) where import QBar.BlockHelper import QBar.BlockOutput @@ -11,8 +13,8 @@ import QBar.Prelude import Control.Applicative ((<|>)) import Control.Lens import Control.Monad.State (StateT, evalStateT, lift) -import qualified Data.Attoparsec.Text.Lazy as AT -import qualified Data.Text.Lazy as T +import Data.Attoparsec.Text.Lazy qualified as AT +import Data.Text.Lazy qualified as T {- For time accounting the guest fields need to be ignored according to the kernel source code diff --git a/src/QBar/Blocks/Date.hs b/qbar/src/QBar/Blocks/Date.hs similarity index 83% rename from src/QBar/Blocks/Date.hs rename to qbar/src/QBar/Blocks/Date.hs index b60119e3f5186c2103778c1dcb68dba8186d7277..29d46fe576e2f3437cf71da749ce47f7836e8ae2 100644 --- a/src/QBar/Blocks/Date.hs +++ b/qbar/src/QBar/Blocks/Date.hs @@ -1,4 +1,6 @@ -module QBar.Blocks.Date where +module QBar.Blocks.Date ( + dateBlock, +) where import QBar.BlockHelper import QBar.BlockOutput @@ -6,15 +8,15 @@ import QBar.Core import QBar.Prelude import QBar.Time -import qualified Data.Text.Lazy as T import Data.Time.Format import Data.Time.LocalTime +import Data.Text.Lazy qualified as T dateBlock :: Block dateBlock = runPollBlock' (everyNSeconds 60) $ forever $ do zonedTime <- liftIO getZonedTime - let logo = "📅\xFE0E " + let logo :: Text = "📅\xFE0E " let date = T.pack (formatTime defaultTimeLocale "%a %F" zonedTime) let time = T.pack (formatTime defaultTimeLocale "%R" zonedTime) let text = normalText (logo <> date <> " ") <> activeText time diff --git a/src/QBar/Blocks/DiskUsage.hs b/qbar/src/QBar/Blocks/DiskUsage.hs similarity index 77% rename from src/QBar/Blocks/DiskUsage.hs rename to qbar/src/QBar/Blocks/DiskUsage.hs index e93adc89111d3cf91472113c5db1e0f8319ab5ce..2417f35dcb971130ef5067b83192bb6a19056c81 100644 --- a/src/QBar/Blocks/DiskUsage.hs +++ b/qbar/src/QBar/Blocks/DiskUsage.hs @@ -1,13 +1,15 @@ -module QBar.Blocks.DiskUsage where +module QBar.Blocks.DiskUsage ( + diskUsageBlock, +) where import QBar.BlockHelper import QBar.BlockOutput import QBar.Core import QBar.Prelude -import qualified Data.ByteString.Lazy.Char8 as C8 -import qualified Data.Text.Lazy as T -import qualified Data.Text.Lazy.Encoding as T +import Data.ByteString.Lazy.Char8 qualified as C8 +import Data.Text.Lazy qualified as T +import Data.Text.Lazy.Encoding qualified as T import System.Exit import System.Process.Typed (shell, readProcessStdout) @@ -27,6 +29,6 @@ diskUsageBlock path = runPollBlock $ forever $ do (ExitFailure nr) -> mkErrorOutput $ "exit code " <> T.pack (show nr) <> "" createBlockOutput :: C8.ByteString -> BlockOutput createBlockOutput output = case map T.decodeUtf8 (C8.lines output) of - [] -> mkErrorOutput $ "no output" - [_header] -> mkErrorOutput $ "invalid output" + [] -> mkErrorOutput "no output" + [_header] -> mkErrorOutput "invalid output" (_header:values) -> mkBlockOutput $ normalText $ T.intercalate " " $ map T.strip values diff --git a/src/QBar/Blocks/NetworkManager.hs b/qbar/src/QBar/Blocks/NetworkManager.hs similarity index 94% rename from src/QBar/Blocks/NetworkManager.hs rename to qbar/src/QBar/Blocks/NetworkManager.hs index 7aca6e6a375e8077be0ecb0937baff379456fa09..dd9eeb5d08aa4780aa35af50e99835323c7bfab5 100644 --- a/src/QBar/Blocks/NetworkManager.hs +++ b/qbar/src/QBar/Blocks/NetworkManager.hs @@ -1,10 +1,14 @@ -module QBar.Blocks.NetworkManager where +module QBar.Blocks.NetworkManager ( + getDBusProperty, + networkManagerBlock, + runExceptT_, +) where import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError) -import qualified DBus -import qualified DBus.Client as DBus -import qualified Data.Map as Map -import qualified Data.Text.Lazy as T +import DBus qualified +import DBus.Client qualified as DBus +import Data.Map qualified as Map +import Data.Text.Lazy qualified as T import Data.Word (Word32, Word8) import QBar.BlockHelper import QBar.BlockOutput @@ -70,8 +74,13 @@ networkManagerBlock = runSignalBlockConfiguration $ SignalBlockConfiguration { return client release :: DBus.Client -> BarIO () release = liftIO . DBus.disconnect + networkManagerBlock' :: DBus.Client -> SignalBlock () - networkManagerBlock' client = (liftBarIO . networkManagerBlock'' client) >=> respondBlockUpdate >=> networkManagerBlock' client + networkManagerBlock' client + = (liftBarIO . networkManagerBlock'' client) + >=> (\x -> respondBlockUpdate x) -- why doesn't this type check without \->? + >=> networkManagerBlock' client + networkManagerBlock'' :: DBus.Client -> Signal () -> BarIO BlockOutput networkManagerBlock'' client _ = do primaryConnection <- runExceptT_ $ getPrimaryConnectionPath client diff --git a/src/QBar/Blocks/Pipe.hs b/qbar/src/QBar/Blocks/Pipe.hs similarity index 73% rename from src/QBar/Blocks/Pipe.hs rename to qbar/src/QBar/Blocks/Pipe.hs index 05b26a98ee1712a56fa88ef1efb5c8f608573a9e..f485be37c4da4b1f62491cf8db0fbe4fdc4edc15 100644 --- a/src/QBar/Blocks/Pipe.hs +++ b/qbar/src/QBar/Blocks/Pipe.hs @@ -1,4 +1,6 @@ -module QBar.Blocks.Pipe where +module QBar.Blocks.Pipe ( + runPipeClient, +) where import QBar.ControlSocket import QBar.Core @@ -7,11 +9,11 @@ import QBar.TagParser import Control.Concurrent.Async import Data.Aeson (encode) -import qualified Data.ByteString.Lazy.Char8 as BSC -import qualified Data.Text.Lazy as T +import Data.ByteString.Lazy.Char8 qualified as BSC +import Data.Text.Lazy qualified as T import Pipes import Pipes.Concurrent -import qualified Pipes.Prelude as PP +import Pipes.Prelude qualified as PP import System.IO runPipeClient :: Bool -> MainOptions -> IO () @@ -21,7 +23,7 @@ runPipeClient enableEvents mainOptions = do inputTask <- async $ runEffect $ PP.stdinLn >-> toOutput output void $ waitEitherCancel hostTask inputTask where - -- |Special block that reads the processes stdin line-by-line and shows the latest line in the block. Must never be used in a 'server' process or when stdin/stdout is used in another way. + -- Special block that reads the processes stdin line-by-line and shows the latest line in the block. Must never be used in a 'server' process or when stdin/stdout is used in another way. pipeBlock :: Producer String BarIO () -> Block pipeBlock source = ExitBlock <$ source >-> pack where diff --git a/src/QBar/Blocks/Qubes.hs b/qbar/src/QBar/Blocks/Qubes.hs similarity index 67% rename from src/QBar/Blocks/Qubes.hs rename to qbar/src/QBar/Blocks/Qubes.hs index 092907a43c720c82441f6daf07303d9ce550660c..45c88a9aed096be5dbf6647854fdb52c00a97d01 100644 --- a/src/QBar/Blocks/Qubes.hs +++ b/qbar/src/QBar/Blocks/Qubes.hs @@ -1,16 +1,31 @@ -module QBar.Blocks.Qubes where +module QBar.Blocks.Qubes ( + diskUsageQubesBlock, + qubesMonitorPropertyBlock, + qubesVMCountBlock, +) where import QBar.BlockHelper import QBar.BlockOutput import QBar.Core import QBar.Prelude -import QBar.Qubes.AdminAPI (qubesUsageOfDefaultPool, qubesMonitorProperty, qubesGetProperty, qubesEvents, QubesPropertyInfo (..), qubesListVMs, qubesListVMsP, QubesVMState (..), vmState) +import QBar.Qubes.AdminAPI ( + QubesPropertyInfo(..), + QubesVMState(..), + QubesVMInfo(..), + qubesEvents, + qubesGetProperty, + qubesListVMs, + qubesListVMsP, + qubesMonitorProperty, + qubesUsageOfDefaultPool, + vmState, + ) -import qualified Data.ByteString.Lazy as BL -import qualified Data.Map as M -import qualified Data.Text.Lazy as T -import Data.Text.Lazy.Encoding (decodeUtf8With) +import Data.ByteString.Lazy qualified as BL +import Data.Map qualified as M import Data.Text.Encoding.Error (lenientDecode) +import Data.Text.Lazy qualified as T +import Data.Text.Lazy.Encoding (decodeUtf8With) import Pipes as P import Pipes.Core as P @@ -26,12 +41,17 @@ diskUsageQubesBlock = runPollBlock $ forever $ do action = liftIO qubesUsageOfDefaultPool >>= \case (Just usage, Just size) -> return $ createBlockOutput $ size - usage _ -> return $ mkErrorOutput "unknown" + createBlockOutput :: Int -> BlockOutput createBlockOutput free = mkBlockOutput $ chooseColor free $ formatSize free + + chooseColor :: Int -> Text -> BlockText chooseColor free = if free < 40 * 1024*1024*1024 then activeText else normalText + + sizeUnits :: [(Text, Int)] sizeUnits = [ ("T", 1024*1024*1024*1024), ("G", 1024*1024*1024), @@ -39,7 +59,7 @@ diskUsageQubesBlock = runPollBlock $ forever $ do ("k", 1024), (" bytes", 1) ] - formatSize size = case filter ((<size) . snd) sizeUnits of + formatSize size = case filter ((< size) . snd) sizeUnits of ((unit, factor) : _) -> T.pack (show $ size `div` factor) <> unit _ -> T.pack (show size) <> " bytes" @@ -48,8 +68,10 @@ pipeBlockWithEvents prod block = runSignalBlock Nothing (Just produce) sblock where produce :: (a -> IO ()) -> BarIO () produce yield' = runEffect $ prod >-> forever (await >>= liftIO . yield') + sblock :: Signal a -> P.Server (Signal a) (Maybe BlockOutput) BarIO ExitBlock sblock = lift . sblock' >=> respond >=> sblock + sblock' :: Signal a -> BarIO (Maybe BlockOutput) sblock' RegularSignal = return Nothing -- ignore timer sblock' (UserSignal x) = block $ Right x @@ -58,12 +80,20 @@ pipeBlockWithEvents prod block = runSignalBlock Nothing (Just produce) sblock qubesMonitorPropertyBlock :: BL.ByteString -> Block qubesMonitorPropertyBlock name = pipeBlockWithEvents (qubesMonitorProperty qubesEvents name) handle where + handle :: Either a QubesPropertyInfo -> BarIO (Maybe BlockOutput) handle = fmap handle' . either (const $ liftIO $ qubesGetProperty name) return + handle' QubesPropertyInfo {propValue, propIsDefault} = Just $ mkBlockOutput $ normalText $ decode propValue <> (if propIsDefault then " (D)" else "") decode = decodeUtf8With lenientDecode qubesVMCountBlock :: Block -qubesVMCountBlock = pipeBlockWithEvents qubesListVMsP $ fmap countVMs . either (const $ liftIO $ qubesListVMs) return where - countVMs = Just . format . M.size . M.filterWithKey isRunningVM - isRunningVM name x = vmState x == VMRunning && name /= "dom0" - format n = mkBlockOutput $ normalText $ T.pack (show n) <> " Qube" <> (if n /= 1 then "s" else "") +qubesVMCountBlock = pipeBlockWithEvents qubesListVMsP $ fmap countVMs . either (const $ liftIO qubesListVMs) return + where + countVMs :: M.Map BL.ByteString QubesVMInfo -> Maybe BlockOutput + countVMs = Just . format . M.size . M.filterWithKey isRunningVM + + isRunningVM :: BL.ByteString -> QubesVMInfo -> Bool + isRunningVM name x = vmState x == VMRunning && name /= "dom0" + + format :: Int -> BlockOutput + format n = mkBlockOutput $ normalText $ T.pack (show n) <> " Qube" <> (if n /= 1 then "s" else "") diff --git a/src/QBar/Blocks/Script.hs b/qbar/src/QBar/Blocks/Script.hs similarity index 84% rename from src/QBar/Blocks/Script.hs rename to qbar/src/QBar/Blocks/Script.hs index 3d1ffbd5c52c788596b08c7c77c4a38b91fe0295..3f5d2ac7f243e953e318d00bd446c1f914d21508 100644 --- a/src/QBar/Blocks/Script.hs +++ b/qbar/src/QBar/Blocks/Script.hs @@ -1,4 +1,7 @@ -module QBar.Blocks.Script where +module QBar.Blocks.Script ( + pollScriptBlock, + scriptBlock, +) where import QBar.BlockHelper import QBar.BlockOutput @@ -7,25 +10,42 @@ import QBar.Prelude import QBar.TagParser import QBar.Time -import Control.Exception (IOException, handle) import Control.Concurrent (threadDelay) import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar) +import Control.Exception (IOException, handle) import Data.Aeson (encode) -import qualified Data.ByteString.Lazy.Char8 as C8 -import qualified Data.Text.Lazy as T -import qualified Data.Text.Lazy.Encoding as E -import qualified Data.Text.Lazy.IO as TIO +import Data.ByteString.Lazy.Char8 qualified as C8 +import Data.Text.Lazy qualified as T +import Data.Text.Lazy.Encoding qualified as E +import Data.Text.Lazy.IO qualified as TIO import Pipes import Pipes.Safe (catchP) import System.Exit import System.IO hiding (stdin, stdout) import System.IO.Error (isEOFError) -import System.Process.Typed (Process, shell, setStdin, setStdout, - getStdin, getStdout, closed, createPipe, readProcessStdout, startProcess, stopProcess, getExitCode) +import System.Process.Typed ( + Process, + ProcessConfig, + closed, + createPipe, + getExitCode, + getStdin, + getStdout, + readProcessStdout, + setStdin, + setStdout, + shell, + startProcess, + stopProcess, + ) pollScriptBlock :: Interval -> FilePath -> Block -pollScriptBlock interval path = runPollBlock' interval $ forever $ yieldBlockUpdate =<< (lift blockScriptAction) +pollScriptBlock interval path = runPollBlock' interval $ forever $ do + -- Why doesn't this typecheck when using >>= instead? + x <- lift blockScriptAction + yieldBlockUpdate x + where blockScriptAction :: BarIO BlockOutput blockScriptAction = do @@ -55,6 +75,7 @@ scriptBlock clickEvents path = startScriptProcess startScriptProcess Right x -> x where + result :: Either Text Block result = case (isEOFError exc, exitCode) of (True, Just ExitSuccess) -> Right exitBlock (True, Just (ExitFailure nr)) -> @@ -62,11 +83,11 @@ scriptBlock clickEvents path = startScriptProcess (True, Nothing) -> -- This will happen if we hit the race condition (see below) -- or the process closes its stdout without exiting. - Left $ "exit code unavailable" + Left "exit code unavailable" _ -> Left $ T.pack (show exc) ignoreIOException :: a -> IO a -> IO a ignoreIOException errValue = handle $ \(_ :: IOException) -> return errValue - handleErrorWithProcess :: (Process i o e) -> IOException -> Block + handleErrorWithProcess :: Process i o e -> IOException -> Block handleErrorWithProcess process exc = do -- We want to know whether the process has already exited or we are -- killing it because of some other error. stopProcess determines @@ -89,7 +110,9 @@ scriptBlock clickEvents path = startScriptProcess else startScriptProcessNoEvents startScriptProcessNoEvents :: Block startScriptProcessNoEvents = do - let processConfig = setStdin closed $ setStdout createPipe $ shell path + let + processConfig :: ProcessConfig () Handle () + processConfig = setStdin closed $ setStdout createPipe $ shell path process <- startProcess processConfig -- The inner catchP catches errors that happen after the process has been created -- This handler will also make sure the process is stopped diff --git a/src/QBar/Blocks/Squeekboard.hs b/qbar/src/QBar/Blocks/Squeekboard.hs similarity index 96% rename from src/QBar/Blocks/Squeekboard.hs rename to qbar/src/QBar/Blocks/Squeekboard.hs index 9b6b89617ffba1fe72b846d37f1289978a964cea..b96b0690576f169837fc7fcbbb23ced742808049 100644 --- a/src/QBar/Blocks/Squeekboard.hs +++ b/qbar/src/QBar/Blocks/Squeekboard.hs @@ -1,17 +1,20 @@ -module QBar.Blocks.Squeekboard where +module QBar.Blocks.Squeekboard ( + squeekboardBlock, +) where -import Control.Monad.Except (MonadError) -import Data.Either (isRight) -import qualified DBus -import qualified DBus.Client as DBus -import DBus.Internal.Message (signalBody) -import Pipes.Core import QBar.BlockHelper import QBar.BlockOutput import QBar.Blocks.NetworkManager (getDBusProperty, runExceptT_) import QBar.Core import QBar.Prelude +import Control.Monad.Except (MonadError) +import DBus qualified +import DBus.Client qualified as DBus +import DBus.Internal.Message (signalBody) +import Data.Either (isRight) +import Pipes.Core + squeekboardBlock :: Bool -> Block squeekboardBlock autoHide = runSignalBlockConfiguration $ SignalBlockConfiguration { aquire, diff --git a/src/QBar/Blocks/Utils.hs b/qbar/src/QBar/Blocks/Utils.hs similarity index 79% rename from src/QBar/Blocks/Utils.hs rename to qbar/src/QBar/Blocks/Utils.hs index c894acc833566b7e8f29fb789767cef65d467a5a..23c04992d80610959bc44eee0ff6ad6bd3a9d521 100644 --- a/src/QBar/Blocks/Utils.hs +++ b/qbar/src/QBar/Blocks/Utils.hs @@ -1,11 +1,17 @@ -module QBar.Blocks.Utils where +module QBar.Blocks.Utils ( + ensure, + formatFloatN, + parseFile, + tryMaybe', + tryMaybe, +) where import QBar.Prelude import Control.Exception (SomeException, catch) -import qualified Data.Attoparsec.Text.Lazy as AT -import qualified Data.Text.Lazy as T -import qualified Data.Text.Lazy.IO as TIO +import Data.Attoparsec.Text.Lazy qualified as AT +import Data.Text.Lazy qualified as T +import Data.Text.Lazy.IO qualified as TIO import Numeric (showFFloat) formatFloatN :: RealFloat a => Int -> a -> T.Text diff --git a/src/QBar/Cli.hs b/qbar/src/QBar/Cli.hs similarity index 97% rename from src/QBar/Cli.hs rename to qbar/src/QBar/Cli.hs index 4689340241e0321cf5cbf2824d82694a26c02161..26a9d9bbe4ca1589db66224b1dfdfa24244d600b 100644 --- a/src/QBar/Cli.hs +++ b/qbar/src/QBar/Cli.hs @@ -1,7 +1,8 @@ -{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE TemplateHaskell #-} -module QBar.Cli where +module QBar.Cli ( + runQBar, +) where import QBar.Blocks import QBar.Blocks.Pipe @@ -9,14 +10,14 @@ import QBar.ControlSocket import QBar.Core import QBar.DefaultConfig import QBar.Prelude +import QBar.Qubes.AdminAPI (printEvents, qubesVMStats, qubesEvents) import QBar.Server import QBar.Theme import QBar.Time -import QBar.Qubes.AdminAPI (printEvents, qubesVMStats, qubesEvents) import Control.Monad (join) import Data.Maybe (fromMaybe) -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T import Development.GitRev import Options.Applicative @@ -120,7 +121,7 @@ scriptBlockParser = helper <*> do -- HACK optparse-applicative does not support options of style --poll[=INTERVAL], -- so we add a second option to specify the interval explicitly instead -- https://github.com/pcapriotti/optparse-applicative/issues/243 - pollInterval <- fromMaybe defaultInterval <$> (optional $ IntervalSeconds <$> option auto ( + pollInterval <- fromMaybe defaultInterval <$> optional (IntervalSeconds <$> option auto ( long "interval" <> short 'i' <> metavar "SECONDS" <> diff --git a/src/QBar/Color.hs b/qbar/src/QBar/Color.hs similarity index 66% rename from src/QBar/Color.hs rename to qbar/src/QBar/Color.hs index 1b79b237549db8a7d28d73bf5e4320f1e2c3cdf4..806803a15d523728ab27cc97768bb2386251a687 100644 --- a/src/QBar/Color.hs +++ b/qbar/src/QBar/Color.hs @@ -1,19 +1,27 @@ -module QBar.Color where +module QBar.Color ( + Color(..), + colorParser, + hexColorText, +) where import QBar.Prelude import Data.Aeson +import Data.Aeson.Types qualified as AT +import Data.Attoparsec.Text.Lazy as A import Data.Bits ((.|.), shiftL) import Data.Char (ord) -import Data.Attoparsec.Text.Lazy as A import Data.Colour.RGBSpace -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T import Numeric (showHex) data Color = ColorRGB (RGB Double) | ColorRGBA (RGB Double) Double deriving (Eq, Show) + instance FromJSON Color where - parseJSON = withText "Color" $ either fail pure . parseOnly (colorParser <* endOfInput) + parseJSON :: Value -> AT.Parser Color + parseJSON = withText "Color" $ either fail pure . A.parseOnly (colorParser <* endOfInput) . T.fromStrict + instance ToJSON Color where toJSON = String . T.toStrict . hexColorText @@ -35,27 +43,29 @@ hexColorText = hexColor' paddedHexComponent :: Text -> Text paddedHexComponent hex = let len = 2 - T.length hex - padding = if len == 1 then "0" else "" + padding :: Text = if len == 1 then "0" else "" in padding <> hex -colorParser :: Parser Color +colorParser :: A.Parser Color colorParser = do void $ char '#' rgb <- RGB <$> doubleFromHex2 <*> doubleFromHex2 <*> doubleFromHex2 option (ColorRGB rgb) (ColorRGBA rgb <$> doubleFromHex2) where - doubleFromHex2 :: Parser Double + doubleFromHex2 :: A.Parser Double doubleFromHex2 = (/ 256) . fromIntegral <$> hexadecimal'' 2 - -- |Variant of 'Data.Attoparsec.Text.hexadecimal' that parses a fixed amount of digits. - hexadecimal'' :: Int -> Parser Int + -- Variant of 'Data.Attoparsec.Text.hexadecimal' that parses a fixed amount of digits. + hexadecimal'' :: Int -> A.Parser Int hexadecimal'' digits = foldl step 0 <$> count digits (satisfy isHexDigit) where isHexDigit c = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') - step a c | w >= 48 && w <= 57 = (a `shiftL` 4) .|. fromIntegral (w - 48) - | w >= 97 = (a `shiftL` 4) .|. fromIntegral (w - 87) - | otherwise = (a `shiftL` 4) .|. fromIntegral (w - 55) + + step :: Int -> Char -> Int + step a c | w >= 48 && w <= 57 = (a `shiftL` 4) .|. (w - 48) + | w >= 97 = (a `shiftL` 4) .|. (w - 87) + | otherwise = (a `shiftL` 4) .|. (w - 55) where w = ord c diff --git a/src/QBar/ControlSocket.hs b/qbar/src/QBar/ControlSocket.hs similarity index 94% rename from src/QBar/ControlSocket.hs rename to qbar/src/QBar/ControlSocket.hs index 4986dec267e07eba65134ff4f8a035e7683897a6..80ee0204b29862a4a7b12063393e29afbc8c26da 100644 --- a/src/QBar/ControlSocket.hs +++ b/qbar/src/QBar/ControlSocket.hs @@ -1,7 +1,17 @@ {-# LANGUAGE CPP #-} -{-# LANGUAGE TemplateHaskell #-} - -module QBar.ControlSocket where +{-# LANGUAGE TypeFamilies #-} + +module QBar.ControlSocket ( + Command(..), + CommandResult(..), + Down, + Up, + addServerMirrorStream, + listenUnixSocketAsync, + sendBlockStream, + sendBlockStreamStdio, + sendIpc, +) where import QBar.BlockOutput import QBar.Core @@ -14,22 +24,22 @@ import Control.Concurrent (forkFinally) import Control.Concurrent.Async import Control.Exception (SomeException, IOException, handle, onException) import Data.Aeson (FromJSON, ToJSON) -import Data.Aeson.TH -import qualified Data.ByteString as BS -import qualified Data.ByteString.Char8 as BSC +import Data.ByteString qualified as BS +import Data.ByteString.Char8 qualified as BSC import Data.Text.Lazy (pack) +import Data.Text.Lazy qualified as T +import Data.Text.Lazy.IO qualified as T import Data.Time.Clock (getCurrentTime, addUTCTime) -import qualified Data.Text.Lazy as T -import qualified Data.Text.Lazy.IO as T +import GHC.Generics import Network.Socket import Pipes -import Pipes.Concurrent as PC (Output, spawn, spawn', unbounded, newest, toOutput, fromInput, send, atomically) -import Pipes.Parse -import qualified Pipes.Prelude as PP -import Pipes.Safe (catch) import Pipes.Aeson (decode, DecodingError) import Pipes.Aeson.Unchecked (encode) +import Pipes.Concurrent as PC (Output, spawn, spawn', unbounded, newest, toOutput, fromInput, send, atomically) import Pipes.Network.TCP (fromSocket, toSocket) +import Pipes.Parse +import Pipes.Prelude qualified as PP +import Pipes.Safe (catch) import System.Directory (removeFile, doesFileExist) import System.Environment (getEnv) import System.Exit (exitSuccess) @@ -134,13 +144,19 @@ decodeStreamSafe MainOptions{verbose} inputStream = decodeStream inputStream >-> Right v -> yield v >> failOnDecodingError' -data StreamType = BlockStreamType BlockStream | MirrorStreamType MirrorStream +data StreamType + = BlockStreamType BlockStream + | MirrorStreamType MirrorStream + deriving Generic + mapStreamType :: StreamType -> (forall a. IsStream a => a -> b) -> b mapStreamType (BlockStreamType a) f = f a mapStreamType (MirrorStreamType a) f = f a data BlockStream = BlockStream + deriving Generic + instance IsStream BlockStream where type Up BlockStream = [BlockOutput] type Down BlockStream = BlockEvent @@ -180,6 +196,8 @@ instance IsStream BlockStream where data MirrorStream = MirrorStream + deriving Generic + instance IsStream MirrorStream where type Up MirrorStream = BlockEvent type Down MirrorStream = [BlockOutput] @@ -195,12 +213,13 @@ instance IsStream MirrorStream where data Request = Command Command | StartStream StreamType + deriving Generic data Command = SetTheme T.Text | CheckServer - deriving Show + deriving (Show, Generic) data CommandResult = Success | Error Text - deriving Show + deriving (Show, Generic) ipcSocketAddress :: MainOptions -> IO FilePath @@ -389,10 +408,20 @@ listenUnixSocket options@MainOptions{verbose} bar commandHandler = do errorResponse :: Text -> Producer ByteString IO () errorResponse message = encode $ Error message +instance FromJSON BlockStream +instance ToJSON BlockStream + +instance FromJSON Command +instance ToJSON Command + +instance FromJSON CommandResult +instance ToJSON CommandResult + +instance FromJSON MirrorStream +instance ToJSON MirrorStream + +instance FromJSON Request +instance ToJSON Request -$(deriveJSON defaultOptions ''Request) -$(deriveJSON defaultOptions ''Command) -$(deriveJSON defaultOptions ''CommandResult) -$(deriveJSON defaultOptions ''StreamType) -$(deriveJSON defaultOptions ''BlockStream) -$(deriveJSON defaultOptions ''MirrorStream) +instance FromJSON StreamType +instance ToJSON StreamType diff --git a/src/QBar/Core.hs b/qbar/src/QBar/Core.hs similarity index 88% rename from src/QBar/Core.hs rename to qbar/src/QBar/Core.hs index 945c47994179d62014c888e4066810fdcd6e5fe4..5ccc60cbf38e0688071d78718ab9bf36fdb8b0d7 100644 --- a/src/QBar/Core.hs +++ b/qbar/src/QBar/Core.hs @@ -1,6 +1,45 @@ -{-# LANGUAGE TemplateHaskell #-} - -module QBar.Core where +module QBar.Core ( + Bar(..), + BarIO, + BarUpdateChannel(..), + BarUpdateEvent, + Block', + Block, + BlockCache, + BlockEvent(..), + BlockEventHandler, + BlockState, + BlockUpdate, + BlockUpdateReason(..), + ExitBlock(..), + IsCachable(..), + MainOptions(..), + MonadBarIO(..), + addBlock, + addBlockCache, + askBar, + autoPadding, + barAsync, + defaultInterval, + exitBlock, + hasEventHandler, + invalidateBlockState, + mkBlockState', + mkBlockState, + modify, + newCache', + newCache, + newCacheIO, + pushBlockUpdate', + pushBlockUpdate, + pushEmptyBlockUpdate, + runBarIO, + updateBar', + updateBar, + updateBarDefault', + updateBarDefault, + updateEventHandler, +) where import QBar.BlockOutput import QBar.Prelude @@ -8,21 +47,22 @@ import QBar.Time import QBar.Utils import Control.Concurrent.Async -import qualified Control.Concurrent.Event as Event +import Control.Concurrent.Event qualified as Event import Control.Concurrent.MVar import Control.Concurrent.STM.TChan import Control.Lens import Control.Monad.Reader (ReaderT, runReaderT, ask) import Control.Monad.State (StateT) import Control.Monad.Writer (WriterT) -import Data.Aeson.TH +import Data.Aeson import Data.Int (Int64) import Data.Maybe (fromMaybe) -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T +import GHC.Generics import Pipes import Pipes.Concurrent +import Pipes.Prelude qualified as PP import Pipes.Safe (SafeT, runSafeT) -import qualified Pipes.Prelude as PP data MainOptions = MainOptions { verbose :: Bool, @@ -33,9 +73,10 @@ data MainOptions = MainOptions { data BlockEvent = Click { name :: T.Text, button :: Int -} deriving (Eq, Show) -$(deriveJSON defaultOptions ''BlockEvent) +} deriving (Eq, Show, Generic) +instance FromJSON BlockEvent +instance ToJSON BlockEvent data ExitBlock = ExitBlock @@ -207,7 +248,7 @@ newCache'' = do -- |Creates a cache from a block. cacheBlock :: Block -> BlockCache -- 'Block's 'yield' an update whenever they want to update the cache. -cacheBlock pushBlock = newCache $ () <$ (pushBlock >-> updateBarP >-> addBlockName >-> PP.map (\a -> [a])) +cacheBlock pushBlock = newCache $ void $ pushBlock >-> updateBarP >-> addBlockName >-> PP.map (: []) where updateBarP :: Pipe BlockUpdate BlockState BarIO r updateBarP = forever $ do @@ -215,7 +256,7 @@ cacheBlock pushBlock = newCache $ () <$ (pushBlock >-> updateBarP >-> addBlockNa yield state updateBar reason - -- |Sets 'blockName' to a random (but static) identifier if an event handler is set but the 'blockName' is not set. + -- Sets 'blockName' to a random (but static) identifier if an event handler is set but the 'blockName' is not set. addBlockName :: Pipe BlockState BlockState BarIO r addBlockName = do defaultBlockName <- randomIdentifier @@ -235,9 +276,9 @@ autoPadding = autoPadding' 0 0 maybeBlock <- await case maybeBlock of (Just (block, eventHandler), reason) -> do - let fullLength' = max fullLength . printedLength $ block^.fullText - let shortLength' = max shortLength . printedLength $ block^.shortText._Just - yield $ (Just (padFullText fullLength' . padShortText shortLength' $ block, eventHandler), reason) + let fullLength' = max fullLength . printedLength $ block ^. fullText + let shortLength' = max shortLength . printedLength $ block ^. shortText._Just + yield (Just (padFullText fullLength' . padShortText shortLength' $ block, eventHandler), reason) autoPadding' fullLength' shortLength' (Nothing, reason) -> do yield (Nothing, reason) diff --git a/src/QBar/DefaultConfig.hs b/qbar/src/QBar/DefaultConfig.hs similarity index 88% rename from src/QBar/DefaultConfig.hs rename to qbar/src/QBar/DefaultConfig.hs index 8743526ff6aa33bbfa269d2f89fe8c440d04b4eb..0193190f30f9d41c894104859eb010093a51b635 100644 --- a/src/QBar/DefaultConfig.hs +++ b/qbar/src/QBar/DefaultConfig.hs @@ -1,4 +1,6 @@ -module QBar.DefaultConfig where +module QBar.DefaultConfig ( + defaultBarConfig +) where import QBar.Blocks import QBar.Core diff --git a/src/QBar/Host.hs b/qbar/src/QBar/Host.hs similarity index 93% rename from src/QBar/Host.hs rename to qbar/src/QBar/Host.hs index db471003f3ae89f2493c34f5fd124b7324067cd2..4bfa6f1bb26fc2517f7e79d7eb839bed1cec5a8a 100644 --- a/src/QBar/Host.hs +++ b/qbar/src/QBar/Host.hs @@ -1,4 +1,14 @@ -module QBar.Host where +module QBar.Host ( + HostHandle(..), + attachBarOutput, + eventDispatcher, + filterDuplicates, + installSignalHandlers, + requestBarUpdateHandler, + runBarHost', + runBarHost, + runBlocks, +) where import QBar.BlockOutput import QBar.Core @@ -8,7 +18,7 @@ import QBar.Utils import Control.Concurrent (forkIO, forkFinally, threadDelay) import Control.Concurrent.Async (async, wait, waitAny) -import qualified Control.Concurrent.Event as Event +import Control.Concurrent.Event qualified as Event import Control.Concurrent.MVar import Control.Concurrent.STM.TChan import Control.Exception (SomeException, catch, fromException) @@ -16,7 +26,7 @@ import Control.Lens hiding (each, (.=)) import Control.Monad.STM (atomically) import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.Maybe (catMaybes, mapMaybe) -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T import Pipes import Pipes.Concurrent (spawn, unbounded, toOutput, fromInput) import System.Exit (ExitCode, exitWith) @@ -113,13 +123,13 @@ runBlocks bar HostHandle{barUpdateEvent, barUpdatedEvent, followupEventWaitTimeM writeIORef eventHandlerListIORef eventHandlerList where eventHandlerList :: [(T.Text, BlockEventHandler)] - eventHandlerList = mapMaybe getEventHandler $ blockStates + eventHandlerList = mapMaybe getEventHandler blockStates getEventHandler :: BlockState -> Maybe (T.Text, BlockEventHandler) getEventHandler Nothing = Nothing getEventHandler (Just (_, Nothing)) = Nothing getEventHandler (Just (blockOutput, Just eventHandler)) = do - blockName' <- blockOutput^.blockName + blockName' <- blockOutput ^. blockName return (blockName', eventHandler) @@ -230,11 +240,14 @@ runBarHost' initializeBarAction = do attachBarOutputImpl :: MVar ExitCode -> Producer [BlockOutput] IO () -> Consumer BlockEvent IO () -> (Consumer [BlockOutput] IO (), Producer BlockEvent IO ()) -> IO () attachBarOutputImpl exitMVar blockOutputProducer eventConsumer (barOutputConsumer, barEventProducer) = do - - let handleBarEventInput = liftIO $ runEffect $ barEventProducer >-> eventConsumer + let + handleBarEventInput :: IO () + handleBarEventInput = liftIO $ runEffect $ barEventProducer >-> eventConsumer liftIO $ void $ forkFinally handleBarEventInput $ handleOnExitCodeException (\result -> hPutStrLn stderr $ "An event input handler failed: " <> show result) - let handleBarOutput = liftIO $ runEffect $ blockOutputProducer >-> filterDuplicates >-> barOutputConsumer + let + handleBarOutput :: IO () + handleBarOutput = liftIO $ runEffect $ blockOutputProducer >-> filterDuplicates >-> barOutputConsumer liftIO $ void $ forkFinally handleBarOutput $ handleOnExitCodeException (\result -> hPutStrLn stderr $ "A bar output handler failed: " <> show result) where diff --git a/src/QBar/Pango.hs b/qbar/src/QBar/Pango.hs similarity index 93% rename from src/QBar/Pango.hs rename to qbar/src/QBar/Pango.hs index bbfa830fefd7091b2419d050ab11fe27d5fbc839..4be97b74ed8cbb5cd36a6036b7dd169ad4b27d53 100644 --- a/src/QBar/Pango.hs +++ b/qbar/src/QBar/Pango.hs @@ -1,4 +1,7 @@ -module QBar.Pango (PangoText, renderPango) where +module QBar.Pango ( + PangoText, + renderPango, +) where import QBar.Color import QBar.Prelude diff --git a/src/QBar/Prelude.hs b/qbar/src/QBar/Prelude.hs similarity index 70% rename from src/QBar/Prelude.hs rename to qbar/src/QBar/Prelude.hs index efa804626921596bc0eff39fc4edfdccf527d122..ff966db660225353fae1fa6c9fed2de9ee0e1097 100644 --- a/src/QBar/Prelude.hs +++ b/qbar/src/QBar/Prelude.hs @@ -1,32 +1,31 @@ -module QBar.Prelude - ( module Prelude, - ByteString.ByteString, - (>=>), - (<=<), - Control.Monad.forever, - Control.Monad.unless, - Control.Monad.void, - Control.Monad.when, - Control.Monad.IO.Class.MonadIO, - Control.Monad.IO.Class.liftIO, - Text.Text, - Maybe.listToMaybe, - error, - errorWithoutStackTrace, - head, - intercalate, - trace, - traceId, - traceShow, - traceShowId, - traceM, - traceShowM, - traceIO, - traceShowIO, - traceShowIdIO, - undefined, - ) -where +module QBar.Prelude ( + module Prelude, + (<=<), + (>=>), + ByteString.ByteString, + Control.Monad.IO.Class.MonadIO, + Control.Monad.IO.Class.liftIO, + Control.Monad.forever, + Control.Monad.unless, + Control.Monad.void, + Control.Monad.when, + Maybe.listToMaybe, + Text.Text, + error, + errorWithoutStackTrace, + head, + intercalate, + trace, + traceIO, + traceId, + traceM, + traceShow, + traceShowIO, + traceShowId, + traceShowIdIO, + traceShowM, + undefined, +) where import Prelude hiding ( error, @@ -34,15 +33,15 @@ import Prelude hiding head, undefined, ) -import qualified Prelude as P -import qualified Control.Monad import Control.Monad ((>=>), (<=<)) -import qualified Control.Monad.IO.Class -import qualified Data.ByteString as ByteString -import qualified Data.Maybe as Maybe -import qualified Data.Text.Lazy as Text -import qualified Debug.Trace as Trace -import qualified GHC.Stack.Types +import Control.Monad qualified +import Control.Monad.IO.Class qualified +import Data.ByteString qualified as ByteString +import Data.Maybe qualified as Maybe +import Data.Text.Lazy qualified as Text +import Debug.Trace qualified as Trace +import GHC.Stack.Types +import Prelude qualified as P {-# DEPRECATED head "Partial Function." #-} head :: [a] -> a diff --git a/src/QBar/Qubes/AdminAPI.hs b/qbar/src/QBar/Qubes/AdminAPI.hs similarity index 84% rename from src/QBar/Qubes/AdminAPI.hs rename to qbar/src/QBar/Qubes/AdminAPI.hs index 7fcac49795f5d7830176cdbd15cef1a0f18d06d2..e80e0baeae960a206df541e15679231fc7a633bc 100644 --- a/src/QBar/Qubes/AdminAPI.hs +++ b/qbar/src/QBar/Qubes/AdminAPI.hs @@ -1,23 +1,38 @@ -module QBar.Qubes.AdminAPI where +{-# OPTIONS_GHC -Wno-partial-fields #-} + +module QBar.Qubes.AdminAPI ( + QubesPropertyInfo(..), + QubesVMInfo(..), + QubesVMState(..), + printEvents, + qubesEvents, + qubesGetProperty, + qubesListLabelNames, + qubesListProperties, + qubesListVMs, + qubesListVMsP, + qubesMonitorProperty, + qubesUsageOfDefaultPool, + qubesVMStats, +) where import QBar.Prelude -import Control.Monad (forM_, guard) +import Control.Monad (forM_) import Data.Binary import Data.Binary.Get import Data.Binary.Put -import qualified Data.ByteString as BS -import qualified Data.ByteString.Lazy as BL -import qualified Data.ByteString.Lazy.Char8 as BLC +import Data.ByteString qualified as BS +import Data.ByteString.Lazy qualified as BL +import Data.ByteString.Lazy.Char8 qualified as BLC import Data.Char (isAlphaNum) import Data.Function ((&)) -import qualified Data.Map as Map +import Data.Map qualified as Map import Data.Maybe (fromMaybe) import Network.HostName -import Numeric (showHex, readHex) import Pipes -import qualified Pipes.Prelude as P -import qualified Pipes.Safe as P +import Pipes.Prelude qualified as P +import Pipes.Safe qualified as P import System.IO (Handle, hSetBinaryMode) import System.Process.Typed import Text.Read (readMaybe) @@ -64,6 +79,8 @@ instance Binary QubesAdminReturn where where getPairs = untilZeroByte $ (,) <$> getLazyByteStringNul <*> getLazyByteStringNul getFields = untilZeroByte getLazyByteStringNul + + untilZeroByte :: Get a -> Get [a] untilZeroByte inner = lookAhead getWord8 >>= \case 0x00 -> getWord8 >> return [] _ -> inner >>= \x -> (x:) <$> untilZeroByte inner @@ -71,7 +88,7 @@ instance Binary QubesAdminReturn where qubesAdminConnect :: BL.ByteString -> [BL.ByteString] -> IO (Process () Handle ()) qubesAdminConnect serviceName args = do hostname <- getHostName - let concatArgs sep = mconcat (map (sep<>) args) + let concatArgs sep = mconcat (map (sep <>) args) let cmd = if hostname == "dom0" then "qubesd-query dom0 " <> serviceName <> " dom0" <> concatArgs " " else "qrexec-client-vm dom0 " <> serviceName <> concatArgs "+" @@ -95,11 +112,12 @@ qubesTryAdminCall serviceName args = do qubesAdminCall :: BL.ByteString -> [BL.ByteString] -> IO BL.ByteString qubesAdminCall serviceName args = qubesTryAdminCall serviceName args >>= extract where + extract :: QubesAdminReturn -> IO BLC.ByteString extract Ok {okContent} = return okContent extract x@Exception {} = fail $ "service has returned an exception: " <> show x extract Event {} = fail "service has returned events instead of a reply" -qubesAdminCallP :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesAdminCallP :: forall m. (P.MonadSafe m, MonadFail m) => BL.ByteString -> [BL.ByteString] -> Producer QubesAdminReturn m () qubesAdminCallP serviceName args = do process <- liftIO $ qubesAdminConnect serviceName args @@ -122,7 +140,7 @@ qubesAdminCallP serviceName args = do go (runGetIncremental get) `P.finally` stopProcess process -qubesAdminEvents :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesAdminEvents :: forall m. (P.MonadSafe m, MonadFail m) => BL.ByteString -> [BL.ByteString] -> Producer QubesAdminReturn m () qubesAdminEvents serviceName args = qubesAdminCallP serviceName args >-> onlyEvents where @@ -132,14 +150,14 @@ qubesAdminEvents serviceName args = qubesAdminCallP serviceName args >-> onlyEve Exception {} -> fail $ "service has returned an exception: " ++ show reply Event {} -> yield reply -qubesVMStatsRaw :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesVMStatsRaw :: forall m. (P.MonadSafe m, MonadFail m) => Producer QubesAdminReturn m () qubesVMStatsRaw = qubesAdminEvents "admin.vm.Stats" [] data QubesVMStats = QubesVMStats { statsVMName :: BL.ByteString, memoryKB :: Int, cpuTime :: Int, cpuUsageRaw :: Int, cpuUsage :: Int } deriving (Eq, Ord, Show, Read) -qubesVMStats :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesVMStats :: forall m. (P.MonadSafe m, MonadFail m) => Producer QubesVMStats m () qubesVMStats = qubesVMStatsRaw >-> P.mapFoldable parse where parse :: QubesAdminReturn -> Maybe QubesVMStats @@ -149,7 +167,9 @@ qubesVMStats = qubesVMStatsRaw >-> P.mapFoldable parse where | otherwise = Nothing -- shouldn't happen -> report error? parse _ = Nothing -- shouldn't happen -> report error? - absent = (-1) + absent :: Int = -1 + + readBL :: BLC.ByteString -> Int readBL = read . BLC.unpack addProperties :: [(BL.ByteString, BL.ByteString)] -> QubesVMStats -> QubesVMStats @@ -173,11 +193,11 @@ data QubesEvent | PropertyDel { domainName :: BL.ByteString, changedProperty :: BL.ByteString, oldValue :: BL.ByteString } -- reset to default value deriving (Eq, Ord, Show, Read) -qubesEventsRaw :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesEventsRaw :: forall m. (P.MonadSafe m, MonadFail m) => Producer QubesAdminReturn m () qubesEventsRaw = qubesAdminEvents "admin.Events" [] -qubesEvents :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesEvents :: forall m. (P.MonadSafe m, MonadFail m) => Producer QubesEvent m () qubesEvents = qubesEventsRaw >-> P.mapFoldable parse where parse :: QubesAdminReturn -> Maybe QubesEvent @@ -193,7 +213,7 @@ qubesEvents = qubesEventsRaw >-> P.mapFoldable parse where DomainUpdatesAvailable evSubject (boolPropViaInt "value") (boolPropViaInt "oldvalue") "domain-start-failed" -> DomainStartFailed evSubject (fromMaybe "" $ getProp "reason") - _ -> case BLC.break (==':') evEvent of + _ -> case BLC.break (== ':') evEvent of ("property-set", _) -> PropertySet evSubject (fromMaybe "" $ getProp "name") (fromMaybe "" $ getProp "newvalue") (fromMaybe "" $ getProp "oldvalue") ("property-del", _) -> @@ -209,11 +229,11 @@ qubesEvents = qubesEventsRaw >-> P.mapFoldable parse where boolProp :: BL.ByteString -> Maybe Bool boolProp = readProp boolPropViaInt :: BL.ByteString -> Bool - boolPropViaInt = fromMaybe False . fmap (/=0) . intProp + boolPropViaInt = maybe False (/= 0) . intProp parse _ = Nothing -- shouldn't happen -> report error? printEvents :: Show a => Producer a (P.SafeT IO) () -> IO () -printEvents prod = P.runSafeT $ runEffect $ prod >-> (forever $ await >>= liftIO . print) +printEvents prod = P.runSafeT $ runEffect $ prod >-> forever (await >>= liftIO . print) data QubesVMState = VMRunning | VMHalted | UnknownState deriving (Eq, Ord, Enum) @@ -239,19 +259,23 @@ instance Read QubesVMState where qubesAdminCallLines :: BL.ByteString -> [BL.ByteString] -> IO [BL.ByteString] qubesAdminCallLines serviceName args = qubesAdminCall serviceName args >>= parse where + parse :: BLC.ByteString -> IO [BLC.ByteString] parse reply = BLC.split '\n' reply - & filter (/="") + & filter (/= "") & return qubesListVMs :: IO (Map.Map BL.ByteString QubesVMInfo) qubesListVMs = parse <$> qubesAdminCallLines "admin.vm.List" [] where + parse :: [BLC.ByteString] -> Map.Map BLC.ByteString QubesVMInfo parse = Map.fromList . map parseLine + + parseLine :: BLC.ByteString -> (BLC.ByteString, QubesVMInfo) parseLine line = (vmName, QubesVMInfo (readPropEmpty "state") (tryReadProp "class" & fromMaybe UnknownClass)) where (vmName : propsRaw) = BLC.split ' ' line - props = map (fmap BLC.tail . BLC.break (=='=')) propsRaw + props = map (fmap BLC.tail . BLC.break (== '=')) propsRaw getProp :: BL.ByteString -> Maybe BL.ByteString getProp name = lookup name props readPropEmpty :: Read a => BL.ByteString -> a @@ -259,7 +283,7 @@ qubesListVMs = parse <$> qubesAdminCallLines "admin.vm.List" [] tryReadProp :: Read a => BL.ByteString -> Maybe a tryReadProp name = readMaybe . BLC.unpack =<< getProp name -qubesListVMsP :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesListVMsP :: forall m. (P.MonadSafe m, MonadFail m) => Producer (Map.Map BL.ByteString QubesVMInfo) m () qubesListVMsP = liftIO qubesListVMs >>= yield >> qubesEvents >-> P.mapM (const $ liftIO qubesListVMs) @@ -271,12 +295,9 @@ qubesGetProperty name = parse <$> qubesAdminCall "admin.property.Get" [name] where parse reply = QubesPropertyInfo (isDefault == "default=True") (BL.drop 5 typeStr) value where - splitOn ch = fmap BLC.tail . BLC.break (==ch) + splitOn ch = fmap BLC.tail . BLC.break (== ch) (isDefault, (typeStr, value)) = splitOn ' ' reply & fmap (splitOn ' ') -qubesListPropertyNames :: IO [BL.ByteString] -qubesListPropertyNames = qubesAdminCallLines "admin.property.List" [] - qubesListProperties :: IO [(BL.ByteString, QubesPropertyInfo)] qubesListProperties = qubesListLabelNames >>= mapM (toSndM qubesGetProperty) where @@ -289,47 +310,28 @@ qubesGetDefaultPool = propValue <$> qubesGetProperty "default_pool" qubesGetPoolInfo :: BL.ByteString -> IO [(BL.ByteString, BL.ByteString)] qubesGetPoolInfo name = map parseLine <$> qubesAdminCallLines "admin.pool.Info" [name] where - parseLine = fmap BLC.tail . BLC.break (=='=') + parseLine = fmap BLC.tail . BLC.break (== '=') qubesUsageOfDefaultPool :: IO (Maybe Int, Maybe Int) qubesUsageOfDefaultPool = qubesGetDefaultPool >>= qubesGetPoolInfo >>= extract where + extract :: [(BLC.ByteString, BLC.ByteString)] -> IO (Maybe Int, Maybe Int) extract props = return (tryReadProp "usage" props, tryReadProp "size" props) tryReadProp :: Read a => BL.ByteString -> [(BL.ByteString, BL.ByteString)] -> Maybe a tryReadProp name props = readMaybe . BLC.unpack =<< lookup name props -newtype QubesLabelColor = QubesLabelColor { fromQubesLabelColor :: Int } - deriving (Eq, Ord) - -instance Show QubesLabelColor where - showsPrec _ (QubesLabelColor x) = \s -> "0x" <> pad 6 (showHex x "") <> s - where pad l s = replicate (l - length s) '0' <> s - -instance Read QubesLabelColor where - readsPrec _ ('0' : 'x' : xs) = do - let (num, remainder) = splitAt 6 xs - guard $ length num == 6 - (num', []) <- readHex num - [(QubesLabelColor num', remainder)] - readsPrec _ _ = [] - -qubesGetLabelColor :: BL.ByteString -> IO QubesLabelColor -qubesGetLabelColor name = read . BLC.unpack <$> qubesAdminCall "admin.label.Get" [name] qubesListLabelNames :: IO [BL.ByteString] qubesListLabelNames = qubesAdminCallLines "admin.label.List" [] -qubesListLabels :: IO [(BL.ByteString, QubesLabelColor)] -qubesListLabels = qubesListLabelNames >>= mapM (toSndM qubesGetLabelColor) - where - toSndM :: Applicative m => (a -> m b) -> a -> m (a, b) - toSndM f x = sequenceA (x, f x) - -qubesMonitorProperty :: forall m. (P.MonadSafe m, MonadIO m, MonadFail m) +qubesMonitorProperty :: forall m. MonadIO m => Producer QubesEvent m () -> BL.ByteString -> Producer QubesPropertyInfo m () qubesMonitorProperty events name = events >-> P.filter isRelevant >-> fetchValue where + fetchValue :: Proxy () QubesEvent () QubesPropertyInfo m b fetchValue = liftIO (qubesGetProperty name) >>= go + + go :: QubesPropertyInfo -> Proxy () QubesEvent () QubesPropertyInfo m b go x = do yield x ev <- await @@ -337,6 +339,7 @@ qubesMonitorProperty events name = events >-> P.filter isRelevant >-> fetchValue PropertySet {newValue} -> go $ x { propIsDefault = False, propValue = newValue } PropertyDel {} -> fetchValue _ -> go x + isRelevant PropertySet {changedProperty} = name == changedProperty isRelevant PropertyDel {changedProperty} = name == changedProperty isRelevant _ = False diff --git a/src/QBar/Server.hs b/qbar/src/QBar/Server.hs similarity index 92% rename from src/QBar/Server.hs rename to qbar/src/QBar/Server.hs index ff8d4d81a2d0f8b37c095c2c4f049ce3affafcd6..80f816012999a61be3764d17d1d480de17ba625c 100644 --- a/src/QBar/Server.hs +++ b/qbar/src/QBar/Server.hs @@ -1,34 +1,35 @@ -module QBar.Server where +module QBar.Server ( + runBarServer, + runBarServerMirror, +) where import QBar.BlockOutput -import QBar.Core import QBar.ControlSocket +import QBar.Core import QBar.Host -import QBar.Prelude import QBar.Pango +import QBar.Prelude import QBar.Theme import QBar.Utils -import Control.Monad (forM_) import Control.Concurrent.Async (async, link) import Control.Concurrent.Event as Event import Control.Concurrent.MVar (MVar, newMVar, modifyMVar, modifyMVar_) import Control.Exception (throw) +import Control.Monad (forM_) import Data.Aeson (encode, decode, ToJSON, toJSON, object, (.=)) +import Data.Aeson.Types qualified as AT +import Data.ByteString.Char8 qualified as BSSC8 import Data.ByteString.Lazy (hPut) -import qualified Data.ByteString.Char8 as BSSC8 -import qualified Data.ByteString.Lazy as BS -import qualified Data.ByteString.Lazy.Char8 as C8 +import Data.ByteString.Lazy qualified as BS +import Data.ByteString.Lazy.Char8 qualified as C8 import Data.Maybe (fromMaybe) -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T import Pipes import Pipes.Concurrent (Input, spawn, latest, toOutput, fromInput) -import qualified Pipes.Prelude as PP +import Pipes.Prelude qualified as PP import System.IO (stdin, stdout, stderr, hFlush) -data ServerMode = Host | Mirror -data ServerOutput = Sway | Headless - renderIndicators :: [Text] renderIndicators = ["*"] <> cycle ["/", "-", "\\", "|"] @@ -41,9 +42,16 @@ instance ToJSON PangoBlock where toJSON PangoBlock{pangoBlockFullText, pangoBlockShortText, pangoBlockName} = object $ fullText' <> shortText' <> blockName' <> pango' where + fullText' :: [AT.Pair] fullText' = [ "full_text" .= pangoBlockFullText ] + + shortText' :: [AT.Pair] shortText' = fromMaybe (\s -> ["short_text" .= s]) mempty pangoBlockShortText + + blockName' :: [AT.Pair] blockName' = fromMaybe (\s -> ["name" .= s]) mempty pangoBlockName + + pango' :: [AT.Pair] pango' = [ "markup" .= ("pango" :: T.Text) ] @@ -61,7 +69,9 @@ swayBarInput MainOptions{verbose} = swayBarInput' liftIO $ BSSC8.hPutStrLn stderr line hFlush stderr - let maybeBlockEvent = decode $ removeComma $ BS.fromStrict line + let + maybeBlockEvent :: Maybe BlockEvent + maybeBlockEvent = decode $ removeComma $ BS.fromStrict line forM_ maybeBlockEvent yield swayBarInput' @@ -109,7 +119,7 @@ swayBarOutput options@MainOptions{indicator} = do hPut stderr "\n" hFlush stderr encodeOutput :: [ThemedBlockOutput] -> BS.ByteString - encodeOutput blocks = encode $ map renderPangoBlock $ blocks + encodeOutput blocks = encode $ map renderPangoBlock blocks renderPangoBlock :: ThemedBlockOutput -> PangoBlock renderPangoBlock ThemedBlockOutput{_fullText, _shortText, _blockName} = PangoBlock { pangoBlockFullText = renderPango _fullText, diff --git a/src/QBar/TagParser.hs b/qbar/src/QBar/TagParser.hs similarity index 91% rename from src/QBar/TagParser.hs rename to qbar/src/QBar/TagParser.hs index a81c59c518ffa3dd3c5b486dfe729dfe2da19456..f029a097a9edcf7e000ea2eb0add5e089f24499d 100644 --- a/src/QBar/TagParser.hs +++ b/qbar/src/QBar/TagParser.hs @@ -1,4 +1,9 @@ -module QBar.TagParser where +module QBar.TagParser ( + TagState, + parseTags, + parseTags', + parseTags'', +) where import QBar.BlockOutput import QBar.Color @@ -8,8 +13,8 @@ import Control.Applicative ((<|>)) import Data.Attoparsec.Text.Lazy as A import Data.Functor (($>)) import Data.Maybe (catMaybes) -import qualified Data.Text as TS -import qualified Data.Text.Lazy as T +import Data.Text qualified as TS +import Data.Text.Lazy qualified as T type TagState = (Bool, Importance) @@ -55,7 +60,7 @@ tagParser = parser (False, normalImportant) spanParser :: Parser BlockText spanParser = do void $ string "<span" - (colors, backgrounds) <- unzip <$> (many' $ colorAttribute <|> backgroundAttribute) + (colors, backgrounds) <- unzip <$> many' (colorAttribute <|> backgroundAttribute) let color = listToMaybe . catMaybes $ colors let background = listToMaybe . catMaybes $ backgrounds void $ char '>' @@ -85,7 +90,7 @@ tagParser = parser (False, normalImportant) parseTags :: T.Text -> Either String BlockText -parseTags text = parseOnly (tagParser <* endOfInput) (T.toStrict text) +parseTags = parseOnly (tagParser <* endOfInput) parseTags' :: T.Text -> BlockOutput parseTags' = either (mkErrorOutput . T.pack) mkBlockOutput . parseTags diff --git a/src/QBar/Theme.hs b/qbar/src/QBar/Theme.hs similarity index 93% rename from src/QBar/Theme.hs rename to qbar/src/QBar/Theme.hs index cb518506579bc96c5ad3f652e82c571bf0d4360e..cf70679f16bf8454725236e1ba4f6262c46560c8 100644 --- a/src/QBar/Theme.hs +++ b/qbar/src/QBar/Theme.hs @@ -1,4 +1,17 @@ -module QBar.Theme where +module QBar.Theme ( + Theme(..), + ThemedBlockOutput(..), + ThemedBlockText(..), + ThemedBlockTextSegment(..), + defaultTheme, + findTheme, + isAnimated, + mkTheme, + mkThemedBlockOutput, + themeNames, + themes, + whiteThemedBlockOutput, +) where import QBar.BlockOutput import QBar.Color @@ -9,9 +22,9 @@ import Control.Lens ((^.)) import Control.Monad.State.Lazy (State, evalState, get, put) import Data.Colour.RGBSpace import Data.Colour.RGBSpace.HSV (hsv) -import qualified Data.HashMap.Lazy as HM +import Data.HashMap.Lazy qualified as HM import Data.Maybe (fromMaybe) -import qualified Data.Text.Lazy as T +import Data.Text.Lazy qualified as T import Data.Time.Clock.POSIX (getPOSIXTime) import Pipes @@ -37,7 +50,6 @@ data ThemedBlockTextSegment = ThemedBlockTextSegment { } deriving (Eq, Show) - data Theme = StaticTheme StaticTheme | AnimatedTheme AnimatedTheme type StaticTheme = [BlockOutput] -> [ThemedBlockOutput] @@ -65,6 +77,7 @@ themes = HM.fromList themesList findTheme :: Text -> Either Text Theme findTheme themeName = maybe invalidThemeName Right $ HM.lookup themeName themes where + invalidThemeName :: Either Text Theme invalidThemeName = Left $ "Invalid theme: " <> themeName mkTheme :: SimplifiedTheme -> Theme @@ -109,7 +122,6 @@ mkThemedSegment (color, backgroundColor) text = ThemedBlockTextSegment{themedSeg whiteThemedBlockOutput :: Text -> ThemedBlockOutput whiteThemedBlockOutput = mkThemedBlockOutput (ColorRGB (RGB 1 1 1), Nothing) - invalidColor :: Color invalidColor = ColorRGBA (RGB (0x96 / 255) (0x98 / 255) (0x96 / 255)) (0x77 / 255) @@ -129,7 +141,6 @@ defaultTheme = mkTheme defaultTheme' defaultTheme' True (NormalImportant _) = (ColorRGB (RGB 1 1 1), Nothing) defaultTheme' False (NormalImportant _) = (ColorRGB (RGB (0x96 / 255) (0x98 / 255) (0x96 / 255)), Nothing) - rainbowTheme :: Theme rainbowTheme = AnimatedTheme rainbowThemePipe where @@ -146,7 +157,7 @@ rainbowTheme = AnimatedTheme rainbowThemePipe let text = rawText $ block ^. fullText let chars = T.unpack . T.reverse $ text coloredChars <- mapM rainbowChar chars - let rainbowText = reverse $ coloredChars + let rainbowText = reverse coloredChars return $ ThemedBlockOutput { _blockName, _fullText = ThemedBlockText rainbowText, diff --git a/src/QBar/Time.hs b/qbar/src/QBar/Time.hs similarity index 90% rename from src/QBar/Time.hs rename to qbar/src/QBar/Time.hs index c57d2978af80e12893f29c6b5b30ac5c010d40f7..2a97cd15030f8c34fc2de02f1fb91facb2f0fcfb 100644 --- a/src/QBar/Time.hs +++ b/qbar/src/QBar/Time.hs @@ -1,15 +1,28 @@ {-# LANGUAGE OverloadedLists #-} -module QBar.Time (SleepScheduler, HasSleepScheduler(..), Interval(..), createSleepScheduler, sleepUntil, sleepUntil', sleepUntilInterval, sleepUntilInterval', everyMinute, everyNSeconds, nextIntervalTime, humanReadableInterval) where +module QBar.Time ( + HasSleepScheduler(..), + Interval(..), + SleepScheduler, + createSleepScheduler, + everyMinute, + everyNSeconds, + humanReadableInterval, + nextIntervalTime, + sleepUntil', + sleepUntil, + sleepUntilInterval', + sleepUntilInterval, +) where import QBar.Prelude import Control.Concurrent.Async +import Control.Concurrent.Event qualified as Event import Control.Concurrent.MVar -import qualified Control.Concurrent.Event as Event -import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime, utctDayTime, addUTCTime) -import Data.SortedList (SortedList, toSortedList, fromSortedList, singleton, partition, insert) import Data.Ord (comparing) +import Data.SortedList (SortedList, toSortedList, fromSortedList, singleton, partition, insert) +import Data.Time.Clock (UTCTime, getCurrentTime, diffUTCTime, utctDayTime, addUTCTime) newtype Interval = IntervalSeconds Integer deriving (Read, Show) @@ -26,7 +39,7 @@ nextIntervalTime :: MonadIO m => Interval -> m UTCTime nextIntervalTime (IntervalSeconds intervalSeconds) = liftIO $ do now <- getCurrentTime let dayTime = utctDayTime now - let daySeconds = floor dayTime + let daySeconds :: Integer = floor dayTime let intervalId = daySeconds `div` intervalSeconds return now { utctDayTime = fromInteger $ (intervalId + 1) * intervalSeconds @@ -50,7 +63,7 @@ class HasSleepScheduler m where createSleepScheduler :: MonadIO m => m SleepScheduler createSleepScheduler = liftIO $ do scheduler <- SleepScheduler <$> newMVar ([], []) <*> Event.new - link =<< (async $ schedulerThread scheduler) + link =<< async (schedulerThread scheduler) return scheduler where schedulerThread :: SleepScheduler -> IO () @@ -74,7 +87,7 @@ createSleepScheduler = liftIO $ do schedulerThread' start - -- |Waits for the next event, with a timeout. A return value of 'False' indicates a timeout occured. + -- Waits for the next event, with a timeout. A return value of 'False' indicates a timeout occured. waitForEvent :: UTCTime -> IO Bool waitForEvent eventTime = do now <- getCurrentTime @@ -96,7 +109,7 @@ createSleepScheduler = liftIO $ do Event.clear trigger return (futureEvents, []) - -- |Predicate to check if an event should be fired. + -- Predicate to check if an event should be fired. checkEvent :: UTCTime -> ScheduledEvent -> Bool checkEvent now ScheduledEvent{time} = now >= time diff --git a/src/QBar/Utils.hs b/qbar/src/QBar/Utils.hs similarity index 96% rename from src/QBar/Utils.hs rename to qbar/src/QBar/Utils.hs index e00ab4e135761b07a052e0f35d6de822b8eb4501..ca6848f6578c4cbf2e4013e77ae96e77c3272de7 100644 --- a/src/QBar/Utils.hs +++ b/qbar/src/QBar/Utils.hs @@ -1,4 +1,9 @@ -module QBar.Utils where +module QBar.Utils ( + mkBroadcastCacheP, + mkBroadcastP, + randomIdentifier, + signalEventPipe, +) where import QBar.Prelude diff --git a/test/Spec.hs b/qbar/test/Spec.hs similarity index 100% rename from test/Spec.hs rename to qbar/test/Spec.hs diff --git a/src/QBar/Blocks.hs b/src/QBar/Blocks.hs deleted file mode 100644 index 1dccb3a9fced84b13d587f68fb78578812ddb6c7..0000000000000000000000000000000000000000 --- a/src/QBar/Blocks.hs +++ /dev/null @@ -1,23 +0,0 @@ -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 qualified QBar.Blocks.Battery -import qualified QBar.Blocks.CpuUsage -import qualified QBar.Blocks.Date -import qualified QBar.Blocks.DiskUsage -import qualified QBar.Blocks.NetworkManager -import qualified QBar.Blocks.Qubes -import qualified QBar.Blocks.Script -import qualified QBar.Blocks.Squeekboard