You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
12 KiB
451 lines
12 KiB
3 years ago
|
// 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"
|
||
|
"net/http"
|
||
|
"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/github"
|
||
|
"barista.run/modules/media"
|
||
|
"barista.run/modules/meminfo"
|
||
|
"barista.run/modules/netspeed"
|
||
|
"barista.run/modules/sysinfo"
|
||
|
"barista.run/modules/volume"
|
||
|
"barista.run/modules/volume/alsa"
|
||
|
"barista.run/modules/weather"
|
||
|
"barista.run/modules/weather/openweathermap"
|
||
|
"barista.run/oauth"
|
||
|
"barista.run/outputs"
|
||
|
"barista.run/pango"
|
||
|
"barista.run/pango/icons/fontawesome"
|
||
|
"barista.run/pango/icons/material"
|
||
|
"barista.run/pango/icons/mdi"
|
||
|
"barista.run/pango/icons/typicons"
|
||
|
"barista.run/testing/httpcache"
|
||
|
|
||
|
colorful "github.com/lucasb-eyer/go-colorful"
|
||
|
"github.com/martinlindhe/unit"
|
||
|
keyring "github.com/zalando/go-keyring"
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
http.DefaultTransport = httpcache.Wrap(http.DefaultTransport)
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
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 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
|
||
|
}
|
||
|
|
||
|
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 main() {
|
||
|
material.Load(home("Github/material-design-icons"))
|
||
|
mdi.Load(home("Github/MaterialDesign-Webfont"))
|
||
|
typicons.Load(home("Github/typicons.font"))
|
||
|
fontawesome.Load(home("Github/Font-Awesome"))
|
||
|
|
||
|
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"))
|
||
|
})
|
||
|
|
||
|
// 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 {
|
||
|
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
|
||
|
}
|
||
|
return outputs.Pango(
|
||
|
pango.Icon("typecn-"+iconName), spacer,
|
||
|
pango.Textf("%.1f℃", w.Temperature.Celsius()),
|
||
|
pango.Textf(" (provided by %s)", w.Attribution).XSmall(),
|
||
|
)
|
||
|
})
|
||
|
|
||
|
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.New(alsa.DefaultMixer()).Output(func(v volume.Volume) bar.Output {
|
||
|
if v.Mute {
|
||
|
return outputs.
|
||
|
Pango(pango.Icon("fa-volume-mute"), spacer, "MUT").
|
||
|
Color(colors.Scheme("degraded"))
|
||
|
}
|
||
|
iconName := "off"
|
||
|
pct := v.Pct()
|
||
|
if pct > 66 {
|
||
|
iconName = "up"
|
||
|
} else if pct > 33 {
|
||
|
iconName = "down"
|
||
|
}
|
||
|
return outputs.Pango(
|
||
|
pango.Icon("fa-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)
|
||
|
|
||
|
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"))
|
||
|
})
|
||
|
|
||
|
panic(barista.Run(
|
||
|
rhythmbox,
|
||
|
grp,
|
||
|
ghNotify,
|
||
|
vol,
|
||
|
batt,
|
||
|
wthr,
|
||
|
localtime,
|
||
|
))
|
||
|
}
|