summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif/taffybar
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/gkleen@sif/taffybar')
-rw-r--r--accounts/gkleen@sif/taffybar/default.nix2
-rw-r--r--accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal31
-rw-r--r--accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs111
-rw-r--r--accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs101
-rw-r--r--accounts/gkleen@sif/taffybar/src/taffybar.hs83
-rw-r--r--accounts/gkleen@sif/taffybar/taffybar.css146
6 files changed, 474 insertions, 0 deletions
diff --git a/accounts/gkleen@sif/taffybar/default.nix b/accounts/gkleen@sif/taffybar/default.nix
new file mode 100644
index 00000000..98366d8f
--- /dev/null
+++ b/accounts/gkleen@sif/taffybar/default.nix
@@ -0,0 +1,2 @@
1{ haskellPackages ? (import <nixpkgs> {}).haskellPackages }:
2haskellPackages.callCabal2nix "gkleen-sif-taffybar" ./. {}
diff --git a/accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal b/accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal
new file mode 100644
index 00000000..7f56dece
--- /dev/null
+++ b/accounts/gkleen@sif/taffybar/gkleen-sif-taffybar.cabal
@@ -0,0 +1,31 @@
1name: gkleen-sif-taffybar
2version: 0.0.0
3build-type: Simple
4cabal-version: >=1.10
5
6data-files: taffybar.css
7
8executable taffybar
9 hs-source-dirs: src
10 main-is: taffybar.hs
11 ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 -Wall
12 build-depends: base
13 , containers
14 , directory
15 , filepath
16 , gtk3
17 , taffybar
18 , X11>=1.8
19 , transformers
20 , gi-gtk
21 , time, time-locale-compat
22 , text
23 , HStringTemplate
24 , gtk-sni-tray
25 other-modules: Paths_gkleen_sif_taffybar
26 , System.Taffybar.Widget.Clock
27 , System.Taffybar.Widget.TooltipBattery
28 default-language: Haskell2010
29 default-extensions: ScopedTypeVariables
30 , LambdaCase
31 , NamedFieldPuns \ No newline at end of file
diff --git a/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs b/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs
new file mode 100644
index 00000000..e8dc480f
--- /dev/null
+++ b/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/Clock.hs
@@ -0,0 +1,111 @@
1{-# LANGUAGE OverloadedStrings #-}
2module System.Taffybar.Widget.Clock
3 ( textClockNew
4 , textClockNewWith
5 , defaultClockConfig
6 , ClockConfig(..)
7 , ClockUpdateStrategy(..)
8 ) where
9
10import Control.Monad.IO.Class
11import Data.Maybe
12import qualified Data.Text as T
13import qualified Data.Time.Clock as Clock
14import Data.Time.Format
15import Data.Time.LocalTime
16import qualified Data.Time.Locale.Compat as L
17import GI.Gtk
18import System.Taffybar.Widget.Generic.PollingLabel
19
20type ClockFormat = L.TimeLocale -> ZonedTime -> T.Text
21
22-- | Create the widget. I recommend passing @Nothing@ for the TimeLocale
23-- parameter. The format string can include Pango markup
24-- (<http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>).
25textClockNew ::
26 MonadIO m => Maybe L.TimeLocale -> ClockFormat -> Double -> m GI.Gtk.Widget
27textClockNew userLocale format interval =
28 textClockNewWith cfg
29 where
30 cfg = defaultClockConfig { clockTimeLocale = userLocale
31 , clockFormat = format
32 , clockUpdateStrategy = ConstantInterval interval
33 }
34
35data ClockUpdateStrategy
36 = ConstantInterval Double
37 | RoundedTargetInterval Int Double
38 deriving (Eq, Ord, Show)
39
40data ClockConfig = ClockConfig
41 { clockTimeZone :: Maybe TimeZone
42 , clockTimeLocale :: Maybe L.TimeLocale
43 , clockFormat :: ClockFormat
44 , clockUpdateStrategy :: ClockUpdateStrategy
45 }
46
47-- | A clock configuration that defaults to the current locale
48defaultClockConfig :: ClockConfig
49defaultClockConfig = ClockConfig
50 { clockTimeZone = Nothing
51 , clockTimeLocale = Nothing
52 , clockFormat = \locale zonedTime -> T.pack $ formatTime locale "%a %b %_d %r" zonedTime
53 , clockUpdateStrategy = RoundedTargetInterval 5 0.0
54 }
55
56systemGetTZ :: IO TimeZone
57systemGetTZ = getCurrentTimeZone
58
59-- | A configurable text-based clock widget. It currently allows for
60-- a configurable time zone through the 'ClockConfig'.
61--
62-- See also 'textClockNew'.
63textClockNewWith :: MonadIO m => ClockConfig -> m Widget
64textClockNewWith ClockConfig
65 { clockTimeZone = userZone
66 , clockTimeLocale = userLocale
67 , clockFormat = format
68 , clockUpdateStrategy = updateStrategy
69 } = liftIO $ do
70 let getTZ = maybe systemGetTZ return userZone
71 locale = fromMaybe L.defaultTimeLocale userLocale
72
73 let getUserZonedTime =
74 utcToZonedTime <$> getTZ <*> Clock.getCurrentTime
75
76 doTimeFormat = format locale
77
78 getRoundedTimeAndNextTarget = do
79 zonedTime <- getUserZonedTime
80 return $ case updateStrategy of
81 ConstantInterval interval ->
82 (doTimeFormat zonedTime, Nothing, interval)
83 RoundedTargetInterval roundSeconds offset ->
84 let roundSecondsDiffTime = fromIntegral roundSeconds
85 addTheRound = addLocalTime roundSecondsDiffTime
86 localTime = zonedTimeToLocalTime zonedTime
87 ourLocalTimeOfDay = localTimeOfDay localTime
88 seconds = round $ todSec ourLocalTimeOfDay
89 secondsFactor = seconds `div` roundSeconds
90 displaySeconds = secondsFactor * roundSeconds
91 baseLocalTimeOfDay =
92 ourLocalTimeOfDay { todSec = fromIntegral displaySeconds }
93 ourLocalTime =
94 localTime { localTimeOfDay = baseLocalTimeOfDay }
95 roundedLocalTime =
96 if seconds `mod` roundSeconds > roundSeconds `div` 2
97 then addTheRound ourLocalTime
98 else ourLocalTime
99 roundedZonedTime =
100 zonedTime { zonedTimeToLocalTime = roundedLocalTime }
101 nextTarget = addTheRound ourLocalTime
102 amountToWait = realToFrac $ diffLocalTime nextTarget localTime
103 in (doTimeFormat roundedZonedTime, Nothing, amountToWait - offset)
104
105 label <- pollingLabelWithVariableDelay getRoundedTimeAndNextTarget
106 ebox <- eventBoxNew
107 containerAdd ebox label
108 eventBoxSetVisibleWindow ebox False
109 widgetShowAll ebox
110 toWidget ebox
111
diff --git a/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs b/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs
new file mode 100644
index 00000000..9dc52774
--- /dev/null
+++ b/accounts/gkleen@sif/taffybar/src/System/Taffybar/Widget/TooltipBattery.hs
@@ -0,0 +1,101 @@
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3module System.Taffybar.Widget.TooltipBattery ( batteryIconTooltipNew ) where
4
5import Control.Applicative
6import Control.Monad
7import Control.Monad.IO.Class
8import Control.Monad.Trans.Reader
9import Data.Int (Int64)
10import qualified Data.Text as T
11import GI.Gtk
12import Prelude
13import StatusNotifier.Tray (scalePixbufToSize)
14import System.Taffybar.Context
15import System.Taffybar.Information.Battery
16import System.Taffybar.Util
17import System.Taffybar.Widget.Generic.AutoSizeImage
18import System.Taffybar.Widget.Generic.ChannelWidget
19import Text.Printf
20import Text.StringTemplate
21import Data.Function ((&))
22
23-- | Just the battery info that will be used for display (this makes combining
24-- several easier).
25data BatteryWidgetInfo = BWI
26 { seconds :: Maybe Int64
27 , percent :: Double
28 , status :: String
29 , rate :: Maybe Double
30 } deriving (Eq, Show)
31
32-- | Format a duration expressed as seconds to hours and minutes
33formatDuration :: Int64 -> String
34formatDuration secs = let minutes, hours, minutes' :: Int64
35 minutes = secs `div` 60
36 (hours, minutes') = minutes `divMod` 60
37 in printf "%02d:%02d" hours minutes'
38
39getBatteryWidgetInfo :: BatteryInfo -> BatteryWidgetInfo
40getBatteryWidgetInfo info =
41 let battPctNum :: Double
42 battPctNum = batteryPercentage info
43 battTime :: Maybe Int64
44 battTime =
45 case batteryState info of
46 BatteryStateCharging -> Just $ batteryTimeToFull info
47 BatteryStateDischarging -> Just $ batteryTimeToEmpty info
48 _ -> Nothing
49 battStatus :: String
50 battStatus =
51 case batteryState info of
52 BatteryStateCharging -> "↑"
53 BatteryStateDischarging -> "↓"
54 BatteryStateEmpty -> "⤓"
55 BatteryStateFullyCharged -> "⤒"
56 _ -> "?"
57 battRate :: Maybe Double
58 battRate | rawRate < 0.1 = Nothing
59 | otherwise = Just rawRate
60 where rawRate = batteryEnergyRate info
61 in BWI{ seconds = battTime, percent = battPctNum, status = battStatus, rate = battRate }
62
63-- | Given (maybe summarized) battery info and format: provides the string to display
64formatBattInfo :: BatteryWidgetInfo -> String -> T.Text
65formatBattInfo info fmt =
66 let tpl = newSTMP fmt
67 tpl' = tpl
68 & setManyAttrib [ ("percentage", printf "%.0f" $ percent info)
69 , ("status", status info)
70 ]
71 & setManyAttrib [ ("time", formatDuration <$> seconds info)
72 , ("rate", printf "%.0f" <$> rate info)
73 ]
74 in render tpl'
75
76themeLoadFlags :: [IconLookupFlags]
77themeLoadFlags = [IconLookupFlagsGenericFallback, IconLookupFlagsUseBuiltin]
78
79batteryIconTooltipNew :: String -> TaffyIO Widget
80batteryIconTooltipNew format = do
81 DisplayBatteryChanVar (chan, _) <- setupDisplayBatteryChanVar ["IconName", "State", "Percentage", "TimeToFull", "TimeToEmpty", "EnergyRate"]
82 ctx <- ask
83 liftIO $ do
84 image <- imageNew
85 styleCtx <- widgetGetStyleContext =<< toWidget image
86 defaultTheme <- iconThemeGetDefault
87 let getCurrentBatteryIconNameStringTooltip = do
88 info <- runReaderT getDisplayBatteryInfo ctx
89 let iconNameString = T.pack $ batteryIconName info
90 tooltip = formatBattInfo (getBatteryWidgetInfo info) format
91 return (iconNameString, tooltip)
92 extractPixbuf info =
93 fst <$> iconInfoLoadSymbolicForContext info styleCtx
94 setIconForSize size = do
95 (name, tooltip) <- getCurrentBatteryIconNameStringTooltip
96 widgetSetTooltipMarkup image $ Just tooltip
97 iconThemeLookupIcon defaultTheme name size themeLoadFlags >>=
98 traverse extractPixbuf >>=
99 traverse (scalePixbufToSize size OrientationHorizontal)
100 updateImage <- autoSizeImage image setIconForSize OrientationHorizontal
101 toWidget =<< channelWidgetNew image chan (const $ postGUIASync updateImage)
diff --git a/accounts/gkleen@sif/taffybar/src/taffybar.hs b/accounts/gkleen@sif/taffybar/src/taffybar.hs
new file mode 100644
index 00000000..dd713ea7
--- /dev/null
+++ b/accounts/gkleen@sif/taffybar/src/taffybar.hs
@@ -0,0 +1,83 @@
1{-# LANGUAGE OverloadedStrings #-}
2
3module Main where
4
5import System.Taffybar (startTaffybar)
6import System.Taffybar.Context (TaffybarConfig(..))
7import System.Taffybar.Hooks
8import System.Taffybar.SimpleConfig hiding (SimpleTaffyConfig(cssPath))
9import System.Taffybar.Widget
10import qualified System.Taffybar.Widget.Clock as MyClock
11import System.Taffybar.Widget.TooltipBattery
12
13import Data.Time.Format
14import Data.Time.LocalTime
15import Data.Time.Calendar.WeekDate
16
17import qualified Data.Text as T
18
19import Control.Exception (SomeException, try)
20import Control.Monad.Trans.Reader (mapReaderT)
21
22import Paths_gkleen_sif_taffybar
23
24
25main :: IO ()
26main = do
27 myCssPath <- getDataFileName "taffybar.css"
28 startTaffybar exampleTaffybarConfig{ cssPath = Just myCssPath }
29
30
31exampleTaffybarConfig :: TaffybarConfig
32exampleTaffybarConfig =
33 let myWorkspacesConfig =
34 defaultWorkspacesConfig
35 { maxIcons = Just 0
36 , widgetGap = 7
37 , showWorkspaceFn = \case
38 -- Workspace{ workspaceState = Empty } -> False
39 Workspace{ workspaceName } | workspaceName == "NSP" -> False
40 _other -> True
41 , getWindowIconPixbuf = \i d -> either (\(_ :: SomeException) -> Nothing) id <$> mapReaderT try (defaultGetWindowIconPixbuf i d)
42 }
43 workspaces = workspacesNew myWorkspacesConfig
44 clock = MyClock.textClockNewWith MyClock.defaultClockConfig
45 { MyClock.clockUpdateStrategy = MyClock.RoundedTargetInterval 1 0.0
46 , MyClock.clockFormat = \tl zt@ZonedTime{ zonedTimeToLocalTime = LocalTime{ localDay } }
47 -> let date = formatTime tl "%Y-%m-%d" localDay
48 weekdate = "W" <> show2 woy <> "-" <> show dow
49 where (_, woy, dow) = toWeekDate localDay
50 show2 :: Int -> String
51 show2 x = replicate (2 - length s) '0' ++ s
52 where s = show x
53 time = formatTime tl "%H:%M:%S%Ez" zt
54 in T.intercalate " " $ map T.pack [date, weekdate, time]
55 }
56 layout = layoutNew defaultLayoutConfig
57 windowsW = windowsNew defaultWindowsConfig
58 { getMenuLabel = truncatedGetMenuLabel 80
59 , getActiveLabel = truncatedGetActiveLabel 80
60 }
61 worktime = commandRunnerNew 150 "worktime" [] "worktime"
62 worktimeToday = commandRunnerNew 150 "worktime" ["today"] "worktime today"
63 -- See https://github.com/taffybar/gtk-sni-tray#statusnotifierwatcher
64 -- for a better way to set up the sni tray
65 -- tray = sniTrayThatStartsWatcherEvenThoughThisIsABadWayToDoIt
66 tray = sniTrayNew
67 myConfig = defaultSimpleTaffyConfig
68 { startWidgets =
69 workspaces : map (>>= buildContentsBox) [ layout, windowsW ]
70 , endWidgets = map (>>= buildContentsBox) $ reverse
71 -- , mpris2New
72 [ worktime, worktimeToday
73 , clock
74 , tray
75 , batteryIconTooltipNew "$status$ $percentage$%$if(time)$$if(rate)$ ($rate$W $time$)$else$ ($time$)$endif$$elseif(rate)$ ($rate$W)$endif$"
76 ]
77 , barPosition = Top
78 , barPadding = 2
79 , barHeight = 28
80 , widgetSpacing = 10
81 }
82 in withBatteryRefresh $ withLogServer $
83 withToggleServer $ toTaffyConfig myConfig
diff --git a/accounts/gkleen@sif/taffybar/taffybar.css b/accounts/gkleen@sif/taffybar/taffybar.css
new file mode 100644
index 00000000..7a297465
--- /dev/null
+++ b/accounts/gkleen@sif/taffybar/taffybar.css
@@ -0,0 +1,146 @@
1@define-color transparent rgba(0.0, 0.0, 0.0, 0.0);
2@define-color white #808080;
3@define-color gray #202020;
4@define-color green #008000;
5@define-color yellow #808000;
6@define-color blue #000080;
7@define-color red #800000;
8@define-color black #000000;
9/* @define-color taffy-blue #0c7cd5; */
10@define-color taffy-blue @blue;
11
12@define-color active-window-color @white;
13@define-color urgent-window-color @taffy-blue;
14@define-color font-color @white;
15@define-color menu-background-color @black;
16@define-color menu-font-color @white;
17
18/* Top level styling */
19
20.taffy-window * {
21 /*
22 This removes any existing styling from UI elements. Taffybar will not
23 cohere with your gtk theme.
24 */
25 all: unset;
26
27 font-family: "Fira Sans", sans-serif;
28 font-size: 21px;
29 color: @font-color;
30}
31
32.taffy-box {
33 /* border-radius: 10px; */
34 background-color: @black;
35}
36
37.inner-pad {
38 /* padding-bottom: 5px; */
39 /* padding-top: 5px; */
40 padding-left: 2px;
41 padding-right: 2px;
42}
43
44.contents {
45 /* padding-bottom: 4px; */
46 /* padding-top: 4px; */
47 padding-right: 2px;
48 padding-left: 2px;
49 transition: background-color .5s;
50 border-radius: 5px;
51}
52
53/* Workspaces styling */
54
55.workspace-label {
56 padding-right: 3px;
57 padding-left: 2px;
58 font-size: 21px;
59}
60
61.workspace-label.active {
62 color: @green;
63}
64.workspace-label.visible {
65 color: @yellow;
66}
67.workspace-label.empty {
68 color: @gray;
69}
70.workspace-label.urgent {
71 color: @red;
72}
73
74.active .contents {
75 background-color: rgba(0.0, 0.0, 0.0, 0.5);
76}
77
78.visible .contents {
79 background-color: rgba(0.0, 0.0, 0.0, 0.2);
80}
81
82.window-icon-container {
83 transition: opacity .5s, box-shadow .5s;
84 opacity: 1;
85}
86
87/* This gives space for the box-shadow (they look like underlines) that follow.
88 This will actually affect all widgets, (not just the workspace icons), but
89 that is what we want since we want the icons to look the same. */
90.auto-size-image, .sni-tray {
91 padding-top: 3px;
92 padding-bottom: 3px;
93}
94
95.window-icon-container.active {
96 box-shadow: inset 0 -3px @white;
97}
98
99.window-icon-container.urgent {
100 box-shadow: inset 0 -3px @urgent-window-color;
101}
102
103.window-icon-container.inactive .window-icon {
104 padding: 0px;
105}
106
107.window-icon-container.minimized .window-icon {
108 opacity: .3;
109}
110
111.window-icon {
112 opacity: 1;
113 transition: opacity .5s;
114}
115
116/* Button styling */
117
118button {
119 background-color: @transparent;
120 border-width: 0px;
121 border-radius: 0px;
122}
123
124button:checked, button:hover .Contents:hover {
125 box-shadow: inset 0 -3px @taffy-blue;
126}
127
128/* Menu styling */
129
130/* The ".taffy-window" prefixed selectors are needed because if they aren't present,
131 the top level .Taffybar selector takes precedence */
132.taffy-window menuitem *, menuitem * {
133 color: @menu-font-color;
134}
135
136.taffy-window menuitem, menuitem {
137 background-color: @menu-background-color;
138}
139
140.taffy-window menuitem:hover, menuitem:hover {
141 background-color: @taffy-blue;
142}
143
144.taffy-window menuitem:hover > label, menuitem:hover > label {
145 color: @white;
146}