Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
711 lines (662 sloc) 19.9 KB
// Copyright 2017 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.
// sample-bar demonstrates a sample i3bar built using barista.
package main
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"time"
"barista.run"
"barista.run/bar"
"barista.run/base/click"
"barista.run/base/watchers/netlink"
"barista.run/colors"
"barista.run/format"
"barista.run/group/modal"
"barista.run/modules/battery"
"barista.run/modules/clock"
"barista.run/modules/cputemp"
"barista.run/modules/diskio"
"barista.run/modules/diskspace"
"barista.run/modules/github"
"barista.run/modules/gsuite/calendar"
"barista.run/modules/gsuite/gmail"
"barista.run/modules/media"
"barista.run/modules/meminfo"
"barista.run/modules/meta/split"
"barista.run/modules/netinfo"
"barista.run/modules/netspeed"
"barista.run/modules/sysinfo"
"barista.run/modules/volume"
"barista.run/modules/weather"
"barista.run/modules/weather/openweathermap"
"barista.run/modules/wlan"
"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()
var mainModalController modal.Controller
func truncate(in string, l int) string {
fromStart := false
if l < 0 {
fromStart = true
l = -l
}
inLen := len([]rune(in))
if inLen <= l {
return in
}
if fromStart {
return "" + string([]rune(in)[inLen-l+1:])
}
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 makeMediaIconAndPosition(m media.Info) *pango.Node {
iconAndPosition := pango.Icon("fa-music").Color(colors.Hex("#f70"))
if m.PlaybackStatus == media.Playing {
iconAndPosition.Append(spacer,
pango.Textf("%s/", formatMediaTime(m.Position())))
}
if m.PlaybackStatus == media.Paused || m.PlaybackStatus == media.Playing {
iconAndPosition.Append(spacer,
pango.Textf("%s", formatMediaTime(m.Length)))
}
return iconAndPosition
}
func mediaFormatFunc(m media.Info) bar.Output {
if m.PlaybackStatus == media.Stopped || m.PlaybackStatus == media.Disconnected {
return nil
}
artist := truncate(m.Artist, 35)
title := truncate(m.Title, 70-len(artist))
if len(title) < 35 {
artist = truncate(m.Artist, 35-len(title))
}
var iconAndPosition bar.Output
if m.PlaybackStatus == media.Playing {
iconAndPosition = outputs.Repeat(func(time.Time) bar.Output {
return makeMediaIconAndPosition(m)
}).Every(time.Second)
} else {
iconAndPosition = makeMediaIconAndPosition(m)
}
return outputs.Group(iconAndPosition, outputs.Pango(title, " - ", artist))
}
func home(path ...string) string {
usr, err := user.Current()
if err != nil {
panic(err)
}
args := append([]string{usr.HomeDir}, path...)
return filepath.Join(args...)
}
func deviceForMountPath(path string) string {
mnt, _ := exec.Command("df", "-P", path).Output()
lines := strings.Split(string(mnt), "\n")
if len(lines) > 1 {
devAlias := strings.Split(lines[1], " ")[0]
dev, _ := exec.Command("realpath", devAlias).Output()
devStr := strings.TrimSpace(string(dev))
if devStr != "" {
return devStr
}
return devAlias
}
return ""
}
type freegeoipResponse struct {
Lat float64 `json:"latitude"`
Lng float64 `json:"longitude"`
}
func whereami() (lat float64, lng float64, err error) {
resp, err := http.Get("https://freegeoip.app/json/")
if err != nil {
return 0, 0, err
}
var res freegeoipResponse
err = json.NewDecoder(resp.Body).Decode(&res)
if err != nil {
return 0, 0, err
}
return res.Lat, res.Lng, nil
}
type autoWeatherProvider struct{}
func (a autoWeatherProvider) GetWeather() (weather.Weather, error) {
lat, lng, err := whereami()
if err != nil {
return weather.Weather{}, err
}
return openweathermap.
New("%%OWM_API_KEY%%").
Coords(lat, lng).
GetWeather()
}
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)
keyring.Set(service, username, secret)
}
oauth.SetEncryptionKey(secretBytes)
return nil
}
func makeIconOutput(key string) *bar.Segment {
return outputs.Pango(spacer, pango.Icon(key), spacer)
}
var gsuiteOauthConfig = []byte(`{"installed": {
"client_id":"%%GOOGLE_CLIENT_ID%%",
"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",
"client_secret":"%%GOOGLE_CLIENT_SECRET%%",
"redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]
}}`)
func threshold(out *bar.Segment, urgent bool, color ...bool) *bar.Segment {
if urgent {
return out.Urgent(true)
}
colorKeys := []string{"bad", "degraded", "good"}
for i, c := range colorKeys {
if len(color) > i && color[i] {
return out.Color(colors.Scheme(c))
}
}
return out
}
func main() {
material.Load(home("Github/material-design-icons"))
mdi.Load(home("Github/MaterialDesign-Webfont"))
typicons.Load(home("Github/typicons.font"))
ionicons.LoadMd(home("Github/ionicons"))
fontawesome.Load(home("Github/Font-Awesome"))
colors.LoadBarConfig()
bg := colors.Scheme("background")
fg := colors.Scheme("statusline")
if fg != nil && bg != nil {
_, _, 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))
}
localdate := clock.Local().
Output(time.Second, func(now time.Time) bar.Output {
return outputs.Pango(
pango.Icon("material-today").Alpha(0.6),
now.Format("Mon Jan 2"),
).OnClick(click.RunLeft("gsimplecal"))
})
localtime := clock.Local().
Output(time.Second, func(now time.Time) bar.Output {
return outputs.Text(now.Format("15:04:05")).
OnClick(click.Left(func() {
mainModalController.Toggle("timezones")
}))
})
makeTzClock := func(lbl, tzName string) bar.Module {
c, err := clock.ZoneByName(tzName)
if err != nil {
panic(err)
}
return c.Output(time.Minute, func(now time.Time) bar.Output {
return outputs.Pango(pango.Text(lbl).Smaller(), spacer, now.Format("15:04"))
})
}
// Weather information comes from OpenWeatherMap.
// https://openweathermap.org/api.
wthr := weather.New(autoWeatherProvider{}).Output(func(w weather.Weather) bar.Output {
iconName := ""
switch w.Condition {
case weather.Thunderstorm,
weather.TropicalStorm,
weather.Hurricane:
iconName = "stormy"
case weather.Drizzle,
weather.Hail:
iconName = "shower"
case weather.Rain:
iconName = "downpour"
case weather.Snow,
weather.Sleet:
iconName = "snow"
case weather.Mist,
weather.Smoke,
weather.Whirls,
weather.Haze,
weather.Fog:
iconName = "windy-cloudy"
case weather.Clear:
if !w.Sunset.IsZero() && time.Now().After(w.Sunset) {
iconName = "night"
} else if !w.Sunrise.IsZero() && time.Now().Before(w.Sunrise) {
iconName = "night"
} else {
iconName = "sunny"
}
case weather.PartlyCloudy:
iconName = "partly-sunny"
case weather.Cloudy, weather.Overcast:
iconName = "cloudy"
case weather.Tornado,
weather.Windy:
iconName = "windy"
}
if iconName == "" {
iconName = "warning-outline"
} else {
iconName = "weather-" + iconName
}
mainModalController.SetOutput("weather", makeIconOutput("typecn-"+iconName))
out := outputs.Group()
out.Append(outputs.Pango(
pango.Icon("typecn-"+iconName), spacer,
pango.Textf("%.1f", w.Temperature.Celsius()),
))
out.Append(outputs.Text(w.Description))
out.Append(outputs.Pango(
pango.Icon("mdi-flag-variant-outline").Alpha(0.8), spacer,
pango.Textf("%0.fmph %s", w.Wind.Speed.MilesPerHour(), w.Wind.Direction.Cardinal()),
))
out.Append(outputs.Pango(
pango.Icon("fa-tint").Alpha(0.6).Small(), spacer,
pango.Textf("%0.f%%", w.Humidity*100),
))
out.Append(outputs.Pango(
pango.Icon("mdi-weather-sunset-up").Alpha(0.8), spacer,
w.Sunrise.Format("15:04"), spacer,
pango.Icon("mdi-weather-sunset-down").Alpha(0.8), spacer,
w.Sunset.Format("15:04"),
))
out.Append(pango.Textf("provided by %s", w.Attribution).XSmall())
return out
})
battSummary, battDetail := split.New(battery.All().Output(func(i battery.Info) bar.Output {
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)
}
mainModalController.SetOutput("battery", makeIconOutput("mdi-"+iconName))
rem := i.RemainingTime()
out := outputs.Group()
// First segment will be used in summary mode.
out.Append(outputs.Pango(
pango.Icon("mdi-"+iconName).Alpha(0.6),
pango.Textf("%d:%02d", int(rem.Hours()), int(rem.Minutes())%60),
).OnClick(click.Left(func() {
mainModalController.Toggle("battery")
})))
// Others in detail mode.
out.Append(outputs.Pango(
pango.Icon("mdi-"+iconName).Alpha(0.6),
pango.Textf("%d%%", i.RemainingPct()),
spacer,
pango.Textf("(%d:%02d)", int(rem.Hours()), int(rem.Minutes())%60),
).OnClick(click.Left(func() {
mainModalController.Toggle("battery")
})))
out.Append(outputs.Pango(
pango.Textf("%4.1f/%4.1f", i.EnergyNow, i.EnergyFull),
pango.Text("Wh").Smaller(),
))
out.Append(outputs.Pango(
pango.Textf("% +6.2f", i.SignedPower()),
pango.Text("W").Smaller(),
))
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
}), 1)
wifiName, wifiDetails := split.New(wlan.Any().Output(func(i wlan.Info) bar.Output {
if !i.Connecting() && !i.Connected() {
mainModalController.SetOutput("network", makeIconOutput("mdi-ethernet"))
return nil
}
mainModalController.SetOutput("network", makeIconOutput("mdi-wifi"))
if i.Connecting() {
return outputs.Pango(pango.Icon("mdi-wifi").Alpha(0.6), "...").
Color(colors.Scheme("degraded"))
}
out := outputs.Group()
// First segment shown in summary mode only.
out.Append(outputs.Pango(
pango.Icon("mdi-wifi").Alpha(0.6),
pango.Text(truncate(i.SSID, -9)),
).OnClick(click.Left(func() {
mainModalController.Toggle("network")
})))
// Full name, frequency, bssid in detail mode
out.Append(outputs.Pango(
pango.Icon("mdi-wifi").Alpha(0.6),
pango.Text(i.SSID),
))
out.Append(outputs.Textf("%2.1fG", i.Frequency.Gigahertz()))
out.Append(outputs.Pango(
pango.Icon("mdi-access-point").Alpha(0.8),
pango.Text(i.AccessPointMAC).Small(),
))
return out
}), 1)
vol := volume.DefaultMixer().Output(func(v volume.Volume) bar.Output {
if v.Mute {
return outputs.
Pango(pango.Icon("ion-volume-off").Alpha(0.8), spacer, "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).Alpha(0.6),
spacer,
pango.Textf("%2d%%", pct),
)
})
loadAvg := sysinfo.New().Output(func(s sysinfo.Info) bar.Output {
out := outputs.Pango(
pango.Icon("mdi-desktop-tower").Alpha(0.6),
pango.Textf("%0.2f", s.Loads[0]),
)
// 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
}
threshold(out,
s.Loads[0] > 128 || s.Loads[2] > 64,
s.Loads[0] > 64 || s.Loads[2] > 32,
s.Loads[0] > 32 || s.Loads[2] > 16,
)
out.OnClick(click.Left(func() {
mainModalController.Toggle("sysinfo")
}))
return out
})
loadAvgDetail := sysinfo.New().Output(func(s sysinfo.Info) bar.Output {
return pango.Textf("%0.2f %0.2f", s.Loads[1], s.Loads[2]).Smaller()
})
uptime := sysinfo.New().Output(func(s sysinfo.Info) bar.Output {
u := s.Uptime
var uptimeOut *pango.Node
if u.Hours() < 24 {
uptimeOut = pango.Textf("%d:%02d",
int(u.Hours()), int(u.Minutes())%60)
} else {
uptimeOut = pango.Textf("%dd%02dh",
int(u.Hours()/24), int(u.Hours())%24)
}
return pango.Icon("mdi-trending-up").Alpha(0.6).Concat(uptimeOut)
})
freeMem := meminfo.New().Output(func(m meminfo.Info) bar.Output {
out := outputs.Pango(
pango.Icon("material-memory").Alpha(0.8),
format.IBytesize(m.Available()),
)
freeGigs := m.Available().Gigabytes()
threshold(out,
freeGigs < 0.5,
freeGigs < 1,
freeGigs < 2,
freeGigs > 12)
out.OnClick(click.Left(func() {
mainModalController.Toggle("sysinfo")
}))
return out
})
swapMem := meminfo.New().Output(func(m meminfo.Info) bar.Output {
return outputs.Pango(
pango.Icon("mdi-swap-horizontal").Alpha(0.8),
format.IBytesize(m["SwapTotal"]-m["SwapFree"]), spacer,
pango.Textf("(% 2.0f%%)", (1-m.FreeFrac("Swap"))*100.0).Small(),
)
})
temp := cputemp.New().
RefreshInterval(2 * time.Second).
Output(func(temp unit.Temperature) bar.Output {
out := outputs.Pango(
pango.Icon("mdi-fan").Alpha(0.6), spacer,
pango.Textf("%2d", int(temp.Celsius())),
)
threshold(out,
temp.Celsius() > 90,
temp.Celsius() > 70,
temp.Celsius() > 60,
)
return out
})
sub := netlink.Any()
iface := sub.Get().Name
sub.Unsubscribe()
netsp := netspeed.New(iface).
RefreshInterval(2 * time.Second).
Output(func(s netspeed.Speeds) bar.Output {
return outputs.Pango(
pango.Icon("fa-upload").Alpha(0.5), spacer, pango.Textf("%7s", format.Byterate(s.Tx)),
pango.Text(" ").Small(),
pango.Icon("fa-download").Alpha(0.5), spacer, pango.Textf("%7s", format.Byterate(s.Rx)),
)
})
net := netinfo.New().Output(func(i netinfo.State) bar.Output {
if !i.Enabled() {
return nil
}
if i.Connecting() || len(i.IPs) < 1 {
return outputs.Text(i.Name).Color(colors.Scheme("degraded"))
}
return outputs.Group(outputs.Text(i.Name), outputs.Textf("%s", i.IPs[0]))
})
formatDiskSpace := func(i diskspace.Info, icon string) bar.Output {
out := outputs.Pango(
pango.Icon(icon).Alpha(0.7), spacer, format.IBytesize(i.Available))
return threshold(out,
i.Available.Gigabytes() < 1,
i.AvailFrac() < 0.05,
i.AvailFrac() < 0.1,
)
}
rootDev := deviceForMountPath("/")
var homeDiskspace bar.Module
if deviceForMountPath(home()) != rootDev {
homeDiskspace = diskspace.New(home()).Output(func(i diskspace.Info) bar.Output {
return formatDiskSpace(i, "typecn-home-outline")
})
}
rootDiskspace := diskspace.New("/").Output(func(i diskspace.Info) bar.Output {
return formatDiskSpace(i, "fa-hdd")
})
mainDiskio := diskio.New(strings.TrimPrefix(rootDev, "/dev/")).
Output(func(r diskio.IO) bar.Output {
return pango.Icon("mdi-swap-vertical").
Concat(spacer).
ConcatText(format.IByterate(r.Total()))
})
mediaSummary, mediaDetail := split.New(media.Auto().Output(mediaFormatFunc), 1)
ghNotify := github.New("%%GITHUB_CLIENT_ID%%", "%%GITHUB_CLIENT_SECRET%%").
Output(func(n github.Notifications) bar.Output {
if n.Total() == 0 {
return nil
}
out := outputs.Group(
pango.Icon("fab-github").
Concat(spacer).
ConcatTextf("%d", n.Total()))
mentions := n["mention"] + n["team_mention"]
if mentions > 0 {
out.Append(spacer)
out.Append(outputs.Pango(
pango.Icon("mdi-bell").
ConcatTextf("%d", mentions)).
Urgent(true))
}
return out.Glue().OnClick(
click.RunLeft("xdg-open", "https://github.com/notifications"))
})
gm := gmail.New(gsuiteOauthConfig, "INBOX").
Output(func(i gmail.Info) bar.Output {
if i.Unread["INBOX"] == 0 {
return nil
}
return outputs.Pango(
pango.Icon("material-email"),
spacer,
pango.Textf("%d", i.Unread["INBOX"]),
).OnClick(click.RunLeft("xdg-open", "https://mail.google.com"))
})
cal := calendar.New(gsuiteOauthConfig).
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
})
mainModal := modal.New()
sysMode := mainModal.Mode("sysinfo").
SetOutput(makeIconOutput("ion-analytics")).
Add(loadAvg).
Detail(loadAvgDetail, uptime).
Add(freeMem).
Detail(swapMem, temp)
if homeDiskspace != nil {
sysMode.Detail(homeDiskspace)
}
sysMode.Detail(rootDiskspace, mainDiskio)
mainModal.Mode("network").
SetOutput(makeIconOutput("mdi-ethernet")).
Summary(wifiName).
Detail(wifiDetails, net, netsp)
mainModal.Mode("media").
SetOutput(makeIconOutput("mdi-music-box")).
Add(vol, mediaSummary).
Detail(mediaDetail)
mainModal.Mode("notifications").
SetOutput(nil).
Add(gm, cal, ghNotify)
mainModal.Mode("battery").
// Filled in by the battery module if one is available.
SetOutput(nil).
Summary(battSummary).
Detail(battDetail)
mainModal.Mode("weather").
// Set to current conditions by the weather module.
SetOutput(makeIconOutput("typecn-warning-outline")).
Detail(wthr)
mainModal.Mode("timezones").
SetOutput(makeIconOutput("material-access-time")).
Detail(makeTzClock("Seattle", "America/Los_Angeles")).
Detail(makeTzClock("New York", "America/New_York")).
Detail(makeTzClock("UTC", "Etc/UTC")).
Detail(makeTzClock("Berlin", "Europe/Berlin")).
Detail(makeTzClock("Tokyo", "Asia/Tokyo")).
Add(localdate)
var mm bar.Module
mm, mainModalController = mainModal.Build()
panic(barista.Run(mm, localtime))
}
You can’t perform that action at this time.