Skip to content

Instantly share code, notes, and snippets.

@danihodovic
Created May 22, 2019 22:37
Show Gist options
  • Save danihodovic/6a9aa65e0da234a0f44c1d406879ca5f to your computer and use it in GitHub Desktop.
Save danihodovic/6a9aa65e0da234a0f44c1d406879ca5f to your computer and use it in GitHub Desktop.
Barista code
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// simple demonstrates a simpler i3bar built using barista.
// Serves as a good starting point for building custom bars.
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"os/user"
"path/filepath"
"time"
"barista.run"
"barista.run/bar"
"barista.run/base/click"
"barista.run/base/watchers/netlink"
"barista.run/colors"
"barista.run/format"
"barista.run/group/collapsing"
"barista.run/modules/battery"
"barista.run/modules/clock"
"barista.run/modules/cputemp"
"barista.run/modules/gsuite/calendar"
"barista.run/modules/media"
"barista.run/modules/meminfo"
"barista.run/modules/netspeed"
"barista.run/modules/sysinfo"
"barista.run/modules/volume"
"barista.run/oauth"
"barista.run/outputs"
"barista.run/pango"
"barista.run/pango/icons/fontawesome"
"barista.run/pango/icons/ionicons"
"barista.run/pango/icons/material"
"barista.run/pango/icons/mdi"
"barista.run/pango/icons/typicons"
colorful "github.com/lucasb-eyer/go-colorful"
"github.com/martinlindhe/unit"
keyring "github.com/zalando/go-keyring"
)
var spacer = pango.Text(" ").XXSmall()
func truncate(in string, l int) string {
if len([]rune(in)) <= l {
return in
}
return string([]rune(in)[:l-1]) + "⋯"
}
func hms(d time.Duration) (h int, m int, s int) {
h = int(d.Hours())
m = int(d.Minutes()) % 60
s = int(d.Seconds()) % 60
return
}
func formatMediaTime(d time.Duration) string {
h, m, s := hms(d)
if h > 0 {
return fmt.Sprintf("%d:%02d:%02d", h, m, s)
}
return fmt.Sprintf("%d:%02d", m, s)
}
func mediaFormatFunc(m media.Info) bar.Output {
if m.PlaybackStatus == media.Stopped || m.PlaybackStatus == media.Disconnected {
return nil
}
artist := truncate(m.Artist, 20)
title := truncate(m.Title, 40-len(artist))
if len(title) < 20 {
artist = truncate(m.Artist, 40-len(title))
}
iconAndPosition := pango.Icon("fa-music").Color(colors.Hex("#f70"))
if m.PlaybackStatus == media.Playing {
iconAndPosition.Append(
spacer, pango.Textf("%s/%s",
formatMediaTime(m.Position()),
formatMediaTime(m.Length)),
)
}
return outputs.Pango(iconAndPosition, spacer, title, " - ", artist)
}
var startTaskManager = click.RunLeft("xfce4-taskmanager")
func home(path string) string {
usr, err := user.Current()
if err != nil {
panic(err)
}
return filepath.Join(usr.HomeDir, path)
}
func calendarNotifyHandler(e calendar.Event) func(bar.Event) {
notifyBody := e.Start.Format("15:04")
if !e.End.Equal(e.Start) {
notifyBody += " - " + e.End.Format("15:04")
}
if e.Location != "" {
notifyBody += "\n" + e.Location
}
return click.RunLeft("notify-send", e.Summary, notifyBody)
}
func setupOauthEncryption() error {
const service = "barista-sample-bar"
var username string
if u, err := user.Current(); err == nil {
username = u.Username
} else {
username = fmt.Sprintf("user-%d", os.Getuid())
}
var secretBytes []byte
// IMPORTANT: The oauth tokens used by some modules are very sensitive, so
// we encrypt them with a random key and store that random key using
// libsecret (gnome-keyring or equivalent). If no secret provider is
// available, there is no way to store tokens (since the version of
// sample-bar used for setup-oauth will have a different key from the one
// running in i3bar). See also https://github.com/zalando/go-keyring#linux.
secret, err := keyring.Get(service, username)
if err == nil {
secretBytes, err = base64.RawURLEncoding.DecodeString(secret)
}
if err != nil {
secretBytes = make([]byte, 64)
_, err := rand.Read(secretBytes)
if err != nil {
return err
}
secret = base64.RawURLEncoding.EncodeToString(secretBytes)
err = keyring.Set(service, username, secret)
if err != nil {
panic(err)
}
}
oauth.SetEncryptionKey(secretBytes)
return nil
}
func main() {
gsuiteConfig := map[string]interface{}{
"installed": map[string]interface{}{
"client_id": os.Getenv("GOOGLE_CLIENT_ID"),
"client_secret": os.Getenv("GOOGLE_CLIENT_SECRET"),
"project_id": "i3-barista",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://www.googleapis.com/oauth2/v3/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"redirect_uris": []string{"urn:ietf:wg:oauth:2.0:oob", "http://localhost"},
},
}
conf := gsuiteConfig["installed"].(map[string]interface{})
if _, ok := conf["client_id"]; !ok {
log.Fatalln("Provide $GOOGLE_CLIENT_ID")
}
if _, ok := conf["client_secret"]; !ok {
log.Fatalln("Provide $GOOGLE_CLIENT_SECRET")
}
gsuiteJSON, err := json.Marshal(gsuiteConfig)
if err != nil {
panic(err)
}
if err := material.Load(home("Github/material-design-icons")); err != nil {
panic(err)
}
if err := mdi.Load(home("Github/MaterialDesign-Webfont")); err != nil {
panic(err)
}
if err := typicons.Load(home("Github/typicons.font")); err != nil {
panic(err)
}
if err := ionicons.LoadMd(home("Github/ionicons")); err != nil {
panic(err)
}
if err := fontawesome.Load(home("Github/Font-Awesome")); err != nil {
panic(err)
}
colors.LoadBarConfig()
bg := colors.Scheme("background")
fg := colors.Scheme("statusline")
if fg != nil && bg != nil {
iconColor := fg.Colorful().BlendHcl(bg.Colorful(), 0.5).Clamped()
colors.Set("dim-icon", iconColor)
_, _, v := fg.Colorful().Hsv()
if v < 0.3 {
v = 0.3
}
colors.Set("bad", colorful.Hcl(40, 1.0, v).Clamped())
colors.Set("degraded", colorful.Hcl(90, 1.0, v).Clamped())
colors.Set("good", colorful.Hcl(120, 1.0, v).Clamped())
}
if err := setupOauthEncryption(); err != nil {
panic(fmt.Sprintf("Could not setup oauth token encryption: %v", err))
}
localtime := clock.Local().
Output(time.Second, func(now time.Time) bar.Output {
return outputs.Pango(
pango.Icon("material-today").Color(colors.Scheme("dim-icon")),
now.Format("Mon Jan 2 "),
pango.Icon("material-access-time").Color(colors.Scheme("dim-icon")),
now.Format("15:04:05"),
).OnClick(click.RunLeft("gsimplecal"))
})
buildBattOutput := func(i battery.Info, disp *pango.Node) *bar.Segment {
if i.Status == battery.Disconnected || i.Status == battery.Unknown {
return nil
}
iconName := "battery"
if i.Status == battery.Charging {
iconName += "-charging"
}
tenth := i.RemainingPct() / 10
switch {
case tenth == 0:
iconName += "-outline"
case tenth < 10:
iconName += fmt.Sprintf("-%d0", tenth)
}
out := outputs.Pango(pango.Icon("mdi-"+iconName), disp)
switch {
case i.RemainingPct() <= 5:
out.Urgent(true)
case i.RemainingPct() <= 15:
out.Color(colors.Scheme("bad"))
case i.RemainingPct() <= 25:
out.Color(colors.Scheme("degraded"))
}
return out
}
var showBattPct, showBattTime func(battery.Info) bar.Output
batt := battery.All()
showBattPct = func(i battery.Info) bar.Output {
out := buildBattOutput(i, pango.Textf("%d%%", i.RemainingPct()))
if out == nil {
return nil
}
return out.OnClick(click.Left(func() {
batt.Output(showBattTime)
}))
}
showBattTime = func(i battery.Info) bar.Output {
rem := i.RemainingTime()
out := buildBattOutput(i, pango.Textf(
"%d:%02d", int(rem.Hours()), int(rem.Minutes())%60))
if out == nil {
return nil
}
return out.OnClick(click.Left(func() {
batt.Output(showBattPct)
}))
}
batt.Output(showBattPct)
vol := volume.DefaultMixer().Output(func(v volume.Volume) bar.Output {
if v.Mute {
return outputs.
Pango(pango.Icon("ion-volume-off"), "MUT").
Color(colors.Scheme("degraded"))
}
iconName := "mute"
pct := v.Pct()
if pct > 66 {
iconName = "high"
} else if pct > 33 {
iconName = "low"
}
return outputs.Pango(
pango.Icon("ion-volume-"+iconName),
spacer,
pango.Textf("%2d%%", pct),
)
})
loadAvg := sysinfo.New().Output(func(s sysinfo.Info) bar.Output {
out := outputs.Textf("%0.2f %0.2f", s.Loads[0], s.Loads[2])
// Load averages are unusually high for a few minutes after boot.
if s.Uptime < 10*time.Minute {
// so don't add colours until 10 minutes after system start.
return out
}
switch {
case s.Loads[0] > 128, s.Loads[2] > 64:
out.Urgent(true)
case s.Loads[0] > 64, s.Loads[2] > 32:
out.Color(colors.Scheme("bad"))
case s.Loads[0] > 32, s.Loads[2] > 16:
out.Color(colors.Scheme("degraded"))
}
out.OnClick(startTaskManager)
return out
})
freeMem := meminfo.New().Output(func(m meminfo.Info) bar.Output {
out := outputs.Pango(pango.Icon("material-memory"), format.IBytesize(m.Available()))
freeGigs := m.Available().Gigabytes()
switch {
case freeGigs < 0.5:
out.Urgent(true)
case freeGigs < 1:
out.Color(colors.Scheme("bad"))
case freeGigs < 2:
out.Color(colors.Scheme("degraded"))
case freeGigs > 12:
out.Color(colors.Scheme("good"))
}
out.OnClick(startTaskManager)
return out
})
temp := cputemp.New().
RefreshInterval(2 * time.Second).
Output(func(temp unit.Temperature) bar.Output {
out := outputs.Pango(
pango.Icon("mdi-fan"), spacer,
pango.Textf("%2d℃", int(temp.Celsius())),
)
switch {
case temp.Celsius() > 90:
out.Urgent(true)
case temp.Celsius() > 70:
out.Color(colors.Scheme("bad"))
case temp.Celsius() > 60:
out.Color(colors.Scheme("degraded"))
}
return out
})
sub := netlink.Any()
iface := sub.Get().Name
sub.Unsubscribe()
net := netspeed.New(iface).
RefreshInterval(2 * time.Second).
Output(func(s netspeed.Speeds) bar.Output {
return outputs.Pango(
pango.Icon("fa-upload"), spacer, pango.Textf("%7s", format.Byterate(s.Tx)),
pango.Text(" ").Small(),
pango.Icon("fa-download"), spacer, pango.Textf("%7s", format.Byterate(s.Rx)),
)
})
rhythmbox := media.New("rhythmbox").Output(mediaFormatFunc)
grp, _ := collapsing.Group(net, temp, freeMem, loadAvg)
cal := calendar.New(gsuiteJSON).
Output(func(evts calendar.EventList) bar.Output {
evtsOfInterest := append(evts.InProgress, evts.Alerting...)
if len(evtsOfInterest) == 0 && len(evts.Upcoming) > 0 {
evtsOfInterest = append(evtsOfInterest, evts.Upcoming[0])
}
if len(evtsOfInterest) == 0 {
return nil
}
out := outputs.Group().InnerSeparators(false)
out.Append(pango.Icon("mdi-calendar"))
for _, e := range evtsOfInterest {
out.Append(outputs.Textf("%s", e.Start.Format("15:04")).
OnClick(calendarNotifyHandler(e)))
}
return out
})
panic(barista.Run(
rhythmbox,
grp,
cal,
vol,
batt,
localtime,
))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment