Disable all battery code if battery not selected in options.
Refactors widgetCount to localize use.
This commit is contained in:
Sean E. Russell 2019-01-01 11:04:31 -06:00
commit 2a6a479d11
16 changed files with 681 additions and 150 deletions

View File

@ -115,11 +115,11 @@ This will place the built packages into the `dist` folder.
## Built With
- [cjbassi/termui](https://github.com/cjbassi/termui)
- [drawille-go](https://github.com/exrook/drawille-go)
- [termbox](https://github.com/nsf/termbox-go)
- [gopsutil](https://github.com/shirou/gopsutil)
- [goreleaser](https://github.com/goreleaser/goreleaser)
- [gizak/termui](https://github.com/gizak/termui)
- [nsf/termbox](https://github.com/nsf/termbox-go)
- [exrook/drawille-go](https://github.com/exrook/drawille-go)
- [shirou/gopsutil](https://github.com/shirou/gopsutil)
- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
- [battery](https://github.com/distatus/battery)
## Stargazers over time

11
go.mod
View File

@ -1,22 +1,17 @@
module github.com/cjbassi/gotop
require (
github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21 // indirect
github.com/ProtonMail/go-appdir v0.0.0-20180220133335-7c788d1b45c6
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd // indirect
github.com/cjbassi/termui v0.0.0-20181208033036-e8dd23f6146c
github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distatus/battery v0.0.0-20170521010419-916919eb43bd
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815
github.com/gizak/termui v0.0.0-20190101100649-ffdef4cb72e9
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/mattn/go-runewidth v0.0.2 // indirect
github.com/nexulh/batmond v0.0.0-20180213215845-395ac7fdba8c // indirect
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 // indirect
github.com/nsf/termbox-go v0.0.0-20180407224525-3e24a7b6661e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/shirou/gopsutil v2.18.11+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/stretchr/testify v1.2.2 // indirect
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 // indirect
golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a // indirect
)

33
go.sum
View File

@ -1,48 +1,35 @@
github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21 h1:moSC7ACaTejHmVRRwfDTMgByRSwjg2vZooncdWLj7o8=
github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
github.com/ProtonMail/go-appdir v0.0.0-20180220133335-7c788d1b45c6 h1:de/SvQsi6Oou9TJYp6Kp17S+JjXGn1w8XVYWFlE0z/U=
github.com/ProtonMail/go-appdir v0.0.0-20180220133335-7c788d1b45c6/go.mod h1:3d8Y9F5mbEUjrYbcJ3rcDxcWbqbttF+011nVZmdRdzc=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd h1:nSJpATLVvFa19BEHX4ys+VGNWfI4FUGMweEI6QXs8wg=
github.com/cjbassi/drawille-go v0.0.0-20180329221028-ad535d0f92cd/go.mod h1:vjcQJUZJYD3MeVGhtZXSMnCHfUNZxsyYzJt90eCYxK4=
github.com/cjbassi/termui v0.0.0-20181208033036-e8dd23f6146c h1:vcaCtK8ObawtpQRW8GdbKZ+eJGEUn41xJ8Snagd/c6I=
github.com/cjbassi/termui v0.0.0-20181208033036-e8dd23f6146c/go.mod h1:rqXckrwz+i0fH/zNwU6AdBNULHwmZsgehnSlhKP5i2Q=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distatus/battery v0.0.0-20170521010419-916919eb43bd h1:rxw7AZpSwNgPPFdxDNSwQ+TfvOjmSGDK6tlpY7vw9bw=
github.com/distatus/battery v0.0.0-20170521010419-916919eb43bd/go.mod h1:gGO7GxHTi1zlRT+cAj8uGG0/8HFiqAeH0TJvoipnuPs=
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815 h1:HMAfwOa33y82IaQEKQDfUCiwNlxtM1iw7HLM9ru0RNc=
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:l7JNRynTRuqe45tpIyItHNqZWTxywYjp87MWTOnU5cg=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/gizak/termui v0.0.0-20190101100649-ffdef4cb72e9 h1:h5eo3CW6c9WMG+rN8cVxf25v8bSWFjuaDJdvtEWc61E=
github.com/gizak/termui v0.0.0-20190101100649-ffdef4cb72e9/go.mod h1:S3xz8JHXNDPSNFsvXCdG7bHlEGrwvUG3a0joR/xYZ5M=
github.com/gizak/termui v2.3.0+incompatible h1:S8wJoNumYfc/rR5UezUM4HsPEo3RJh0LKdiuDWQpjqw=
github.com/gizak/termui v2.3.0+incompatible/go.mod h1:PkJoWUt/zacQKysNfQtcw1RW+eK2SxkieVBtl+4ovLA=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/nexulh/batmond v0.0.0-20180213215845-395ac7fdba8c h1:Hiq+wfawWPyyX2AVpIhEiVsoKVQdc8zQwAwiKiVZbTI=
github.com/nexulh/batmond v0.0.0-20180213215845-395ac7fdba8c/go.mod h1:Ho2yA2Aa5zD+GEgmhFdLLpHVFoGJXF/Et15iRydTFtY=
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 h1:+2OJrU8cmOstEoh0uQvYemRGVH1O6xtO2oANUWHFnP0=
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y=
github.com/nsf/termbox-go v0.0.0-20180407224525-3e24a7b6661e h1:w2JDz0jtOlFFdvtUXISyYPFwmbZnwKL1mRDT0tKDvuk=
github.com/nsf/termbox-go v0.0.0-20180407224525-3e24a7b6661e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs=
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/serussell/battery v0.0.0-20170521010419-b399f895029c/go.mod h1:0eSBUZHzM+0VCcUzFSkpxGxOg+jkMTT2cr4U/zupwsk=
github.com/shirou/gopsutil v2.18.11+incompatible h1:PMFTKnFTr/YTRW5rbLK4vWALV3a+IGXse5nvhSjztmg=
github.com/shirou/gopsutil v2.18.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a h1:DwI0ihryIiWlRUKL/ii7Snvn4LiL9TvMoVZq3qMbffg=
golang.org/x/sys v0.0.0-20180406135729-3b87a42e500a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=

186
main.go
View File

@ -18,8 +18,8 @@ import (
"github.com/cjbassi/gotop/colorschemes"
"github.com/cjbassi/gotop/src/logging"
w "github.com/cjbassi/gotop/src/widgets"
ui "github.com/cjbassi/termui"
docopt "github.com/docopt/docopt.go"
ui "github.com/gizak/termui"
)
var version = "1.7.1"
@ -34,11 +34,13 @@ var (
averageLoad = false
battery = false
percpuLoad = false
widgetCount = 7
fahrenheit = false
configDir = appdir.New("gotop").UserConfig()
logPath = filepath.Join(configDir, "errors.log")
stderrLogger = log.New(os.Stderr, "", 0)
statusbar = false
termWidth int
termHeight int
cpu *w.CPU
batt *w.Batt
@ -48,6 +50,7 @@ var (
disk *w.Disk
temp *w.Temp
help *w.HelpMenu
grid *ui.Grid
)
func cliArguments() error {
@ -63,7 +66,8 @@ Options:
-p, --percpu Show each CPU in the CPU widget.
-a, --averagecpu Show average CPU in the CPU widget.
-f, --fahrenheit Show temperatures in fahrenheit.
-b, --battery Show battery charge over time (minimal overrides & sets false)
-t, --battery Show battery charge over time ('minimal' disables; widget updates slowly)
-b, --bar Show a statusbar with the time.
Colorschemes:
default
@ -87,9 +91,8 @@ Colorschemes:
battery, _ = args["--battery"].(bool)
minimal, _ = args["--minimal"].(bool)
if minimal {
widgetCount = 3
}
statusbar, _ = args["--bar"].(bool)
rateStr, _ := args["--rate"].(string)
rate, err := strconv.ParseFloat(rateStr, 64)
@ -141,46 +144,68 @@ func getCustomColorscheme(name string) (colorschemes.Colorscheme, error) {
}
func setupGrid() {
ui.Body.Cols = 12
ui.Body.Rows = 12
grid = ui.NewGrid()
grid.SetRect(0, 0, termWidth, termHeight)
var barRow interface{}
if minimal {
ui.Body.Set(0, 0, 12, 6, cpu)
ui.Body.Set(0, 6, 6, 12, mem)
ui.Body.Set(6, 6, 12, 12, proc)
} else {
if battery {
ui.Body.Set(0, 0, 8, 4, cpu)
ui.Body.Set(8, 0, 12, 4, batt)
} else {
ui.Body.Set(0, 0, 12, 4, cpu)
rowHeight := 1.0 / 2
if statusbar {
rowHeight = 50.0 / 101
barRow = ui.NewRow(1.0/101, w.NewStatusBar())
}
ui.Body.Set(0, 4, 4, 6, disk)
ui.Body.Set(0, 6, 4, 8, temp)
ui.Body.Set(4, 4, 12, 8, mem)
ui.Body.Set(0, 8, 6, 12, net)
ui.Body.Set(6, 8, 12, 12, proc)
grid.Set(
ui.NewRow(rowHeight, cpu),
ui.NewRow(rowHeight,
ui.NewCol(1.0/2, mem),
ui.NewCol(1.0/2, proc),
),
barRow,
)
} else {
rowHeight := 1.0 / 3
if statusbar {
rowHeight = 50.0 / 151
barRow = ui.NewRow(1.0/151, w.NewStatusBar())
}
var cpuRow ui.GridItem
if battery {
cpuRow = ui.NewRow(rowHeight,
ui.NewCol(2.0/3, cpu),
ui.NewCol(1.0/3, batt),
)
} else {
cpuRow = ui.NewRow(rowHeight, cpu)
}
grid.Set(
cpuRow,
ui.NewRow(rowHeight,
ui.NewCol(1.0/3,
ui.NewRow(1.0/2, disk),
ui.NewRow(1.0/2, temp),
),
ui.NewCol(2.0/3, mem),
),
ui.NewRow(rowHeight,
ui.NewCol(1.0/2, net),
ui.NewCol(1.0/2, proc),
),
barRow,
)
}
}
func termuiColors() {
ui.Theme.Fg = ui.Color(colorscheme.Fg)
ui.Theme.Bg = ui.Color(colorscheme.Bg)
ui.Theme.LabelFg = ui.Color(colorscheme.BorderLabel)
ui.Theme.LabelBg = ui.Color(colorscheme.Bg)
ui.Theme.BorderFg = ui.Color(colorscheme.BorderLine)
ui.Theme.BorderBg = ui.Color(colorscheme.Bg)
ui.Theme.TableCursor = ui.Color(colorscheme.ProcCursor)
ui.Theme.Sparkline = ui.Color(colorscheme.Sparkline)
ui.Theme.GaugeColor = ui.Color(colorscheme.DiskBar)
ui.Theme.Default = ui.AttrPair{ui.Attribute(colorscheme.Fg), ui.Attribute(colorscheme.Bg)}
ui.Theme.Block.Title = ui.AttrPair{ui.Attribute(colorscheme.BorderLabel), ui.Attribute(colorscheme.Bg)}
ui.Theme.Block.Border = ui.AttrPair{ui.Attribute(colorscheme.BorderLine), ui.Attribute(colorscheme.Bg)}
}
func widgetColors() {
mem.LineColor["Main"] = ui.Color(colorscheme.MainMem)
mem.LineColor["Swap"] = ui.Color(colorscheme.SwapMem)
mem.LineColor["Main"] = ui.Attribute(colorscheme.MainMem)
mem.LineColor["Swap"] = ui.Attribute(colorscheme.SwapMem)
proc.CursorColor = ui.Attribute(colorscheme.ProcCursor)
var keys []string
for key := range cpu.Data {
@ -194,60 +219,76 @@ func widgetColors() {
i = 0
}
c := colorscheme.CPULines[i]
cpu.LineColor[v] = ui.Color(c)
cpu.LineColor[v] = ui.Attribute(c)
i++
}
if !minimal {
temp.TempLow = ui.Color(colorscheme.TempLow)
temp.TempHigh = ui.Color(colorscheme.TempHigh)
var battKeys []string
for key := range batt.Data {
battKeys = append(battKeys, key)
}
sort.Strings(battKeys)
bi := 0
for _, v := range battKeys {
if bi >= len(colorscheme.BattLines) {
// assuming colorscheme for CPU lines is not empty
bi = 0
if battery {
var battKeys []string
for key := range batt.Data {
battKeys = append(battKeys, key)
}
sort.Strings(battKeys)
i = 0 // Re-using variable from CPU
for _, v := range battKeys {
if i >= len(colorscheme.BattLines) {
// assuming colorscheme for battery lines is not empty
i = 0
}
c := colorscheme.BattLines[i]
batt.LineColor[v] = ui.Attribute(c)
i++
}
c := colorscheme.BattLines[bi]
batt.LineColor[v] = ui.Color(c)
bi++
}
temp.TempLow = ui.Attribute(colorscheme.TempLow)
temp.TempHigh = ui.Attribute(colorscheme.TempHigh)
net.Lines[0].LineColor = ui.Attribute(colorscheme.Sparkline)
net.Lines[0].TitleColor = ui.Attribute(colorscheme.BorderLabel)
net.Lines[1].LineColor = ui.Attribute(colorscheme.Sparkline)
net.Lines[1].TitleColor = ui.Attribute(colorscheme.BorderLabel)
}
}
func initWidgets() {
var wg sync.WaitGroup
wg.Add(widgetCount)
wg.Add(1)
go func() {
cpu = w.NewCPU(interval, zoom, averageLoad, percpuLoad)
wg.Done()
}()
wg.Add(1)
go func() {
mem = w.NewMem(interval, zoom)
wg.Done()
}()
wg.Add(1)
go func() {
proc = w.NewProc()
wg.Done()
}()
if !minimal {
go func() {
batt = w.NewBatt(time.Minute, zoom)
wg.Done()
}()
if battery {
wg.Add(1)
go func() {
batt = w.NewBatt(time.Minute, zoom)
wg.Done()
}()
}
wg.Add(1)
go func() {
net = w.NewNet()
wg.Done()
}()
wg.Add(1)
go func() {
disk = w.NewDisk()
wg.Done()
}()
wg.Add(1)
go func() {
temp = w.NewTemp(fahrenheit)
wg.Done()
@ -274,7 +315,7 @@ func eventLoop() {
return
case <-drawTicker:
if !helpVisible {
ui.Render(ui.Body)
ui.Render(grid)
}
case e := <-uiEvents:
switch e.ID {
@ -286,7 +327,7 @@ func eventLoop() {
ui.Clear()
ui.Render(help)
} else {
ui.Render(ui.Body)
ui.Render(grid)
}
case "h":
if !helpVisible {
@ -307,27 +348,27 @@ func eventLoop() {
case "<Escape>":
if helpVisible {
helpVisible = false
ui.Render(ui.Body)
ui.Render(grid)
}
case "<Resize>":
payload := e.Payload.(ui.Resize)
ui.Body.Width, ui.Body.Height = payload.Width, payload.Height
ui.Body.Resize()
grid.SetRect(0, 0, payload.Width, payload.Height)
help.Resize(payload.Width, payload.Height)
ui.Clear()
if helpVisible {
ui.Render(help)
} else {
ui.Render(ui.Body)
ui.Render(grid)
}
case "<MouseLeft>":
payload := e.Payload.(ui.Mouse)
proc.Click(payload.X, payload.Y)
ui.Render(proc)
case "<MouseWheelUp>", "<Up>", "k":
case "k", "<Up>", "<MouseWheelUp>":
proc.Up()
ui.Render(proc)
case "<MouseWheelDown>", "<Down>", "j":
case "j", "<Down>", "<MouseWheelDown>":
proc.Down()
ui.Render(proc)
case "g", "<Home>":
@ -401,11 +442,6 @@ func main() {
stderrLogger.Fatalf("failed to parse cli args: %v", err)
}
termuiColors() // need to do this before initializing widgets so that they can inherit the colors
initWidgets()
widgetColors()
help = w.NewHelpMenu()
if err := ui.Init(); err != nil {
stderrLogger.Fatalf("failed to initialize termui: %v", err)
}
@ -413,8 +449,16 @@ func main() {
logging.StderrToLogfile(lf)
termWidth, termHeight = ui.TerminalSize()
termuiColors() // need to do this before initializing widgets so that they can inherit the colors
initWidgets()
widgetColors()
help = w.NewHelpMenu()
help.Resize(termWidth, termHeight)
setupGrid()
ui.Render(ui.Body)
ui.Render(grid)
eventLoop()
}

131
src/termui/linegraph.go Normal file
View File

@ -0,0 +1,131 @@
package termui
import (
"image"
"sort"
drawille "github.com/cjbassi/drawille-go"
. "github.com/gizak/termui"
)
// LineGraph implements a line graph of data points.
type LineGraph struct {
*Block
Data map[string][]float64
LineColor map[string]Attribute
Zoom int
Labels map[string]string
DefaultLineColor Attribute
}
// NewLineGraph returns a new LineGraph with current theme.
func NewLineGraph() *LineGraph {
return &LineGraph{
Block: NewBlock(),
Data: make(map[string][]float64),
LineColor: make(map[string]Attribute),
Labels: make(map[string]string),
Zoom: 5,
}
}
func (self *LineGraph) Draw(buf *Buffer) {
self.Block.Draw(buf)
// we render each data point on to the canvas then copy over the braille to the buffer at the end
// fyi braille characters have 2x4 dots for each character
c := drawille.NewCanvas()
// used to keep track of the braille colors until the end when we render the braille to the buffer
colors := make([][]Attribute, self.Inner.Dx()+2)
for i := range colors {
colors[i] = make([]Attribute, self.Inner.Dy()+2)
}
// sort the series so that overlapping data will overlap the same way each time
seriesList := make([]string, len(self.Data))
i := 0
for seriesName := range self.Data {
seriesList[i] = seriesName
i++
}
sort.Strings(seriesList)
// draw lines in reverse order so that the first color defined in the colorscheme is on top
for i := len(seriesList) - 1; i >= 0; i-- {
seriesName := seriesList[i]
seriesData := self.Data[seriesName]
seriesLineColor, ok := self.LineColor[seriesName]
if !ok {
seriesLineColor = self.DefaultLineColor
}
// coordinates of last point
lastY, lastX := -1, -1
// assign colors to `colors` and lines/points to the canvas
for i := len(seriesData) - 1; i >= 0; i-- {
x := ((self.Inner.Dx() + 1) * 2) - 1 - (((len(seriesData) - 1) - i) * self.Zoom)
y := ((self.Inner.Dy() + 1) * 4) - 1 - int((float64((self.Inner.Dy())*4)-1)*(seriesData[i]/100))
if x < 0 {
// render the line to the last point up to the wall
if x > 0-self.Zoom {
for _, p := range drawille.Line(lastX, lastY, x, y) {
if p.X > 0 {
c.Set(p.X, p.Y)
colors[p.X/2][p.Y/4] = seriesLineColor
}
}
}
break
}
if lastY == -1 { // if this is the first point
c.Set(x, y)
colors[x/2][y/4] = seriesLineColor
} else {
c.DrawLine(lastX, lastY, x, y)
for _, p := range drawille.Line(lastX, lastY, x, y) {
colors[p.X/2][p.Y/4] = seriesLineColor
}
}
lastX, lastY = x, y
}
// copy braille and colors to buffer
for y, line := range c.Rows(c.MinX(), c.MinY(), c.MaxX(), c.MaxY()) {
for x, char := range line {
x /= 3 // idk why but it works
if x == 0 {
continue
}
if char != 10240 { // empty braille character
buf.SetCell(
Cell{char, AttrPair{colors[x][y], -1}},
image.Pt(self.Inner.Min.X+x-1, self.Inner.Min.Y+y-1),
)
}
}
}
}
// renders key/label ontop
for i, seriesName := range seriesList {
if i+2 > self.Inner.Dy() {
continue
}
seriesLineColor, ok := self.LineColor[seriesName]
if !ok {
seriesLineColor = self.DefaultLineColor
}
// render key ontop, but let braille be drawn over space characters
str := seriesName + " " + self.Labels[seriesName]
for k, char := range str {
if char != ' ' {
buf.SetCell(
Cell{char, AttrPair{seriesLineColor, -1}},
image.Pt(self.Inner.Min.X+2+k, self.Inner.Min.Y+i+1),
)
}
}
}
}

99
src/termui/sparkline.go Normal file
View File

@ -0,0 +1,99 @@
package termui
import (
"image"
. "github.com/gizak/termui"
)
var SPARKS = [8]rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
// Sparkline is like: ▅▆▂▂▅▇▂▂▃▆▆▆▅▃. The data points should be non-negative integers.
type Sparkline struct {
Data []int
Title1 string
Title2 string
TitleColor Attribute
LineColor Attribute
}
// Sparklines is a renderable widget which groups together the given sparklines.
type Sparklines struct {
*Block
Lines []*Sparkline
}
// Add appends a given Sparkline to the *Sparklines.
func (self *Sparklines) Add(sl Sparkline) {
self.Lines = append(self.Lines, &sl)
}
// NewSparkline returns an unrenderable single sparkline that intended to be added into a Sparklines.
func NewSparkline() *Sparkline {
return &Sparkline{}
}
// NewSparklines return a new *Sparklines with given Sparklines, you can always add a new Sparkline later.
func NewSparklines(ss ...*Sparkline) *Sparklines {
return &Sparklines{
Block: NewBlock(),
Lines: ss,
}
}
func (self *Sparklines) Draw(buf *Buffer) {
self.Block.Draw(buf)
lc := len(self.Lines) // lineCount
// renders each sparkline and its titles
for i, line := range self.Lines {
// prints titles
title1Y := self.Inner.Min.Y + 1 + (self.Inner.Dy()/lc)*i
title2Y := self.Inner.Min.Y + 2 + (self.Inner.Dy()/lc)*i
title1 := TrimString(line.Title1, self.Inner.Dx())
title2 := TrimString(line.Title2, self.Inner.Dx())
if self.Inner.Dy() > 5 {
buf.SetString(
title1,
image.Pt(self.Inner.Min.X, title1Y),
AttrPair{line.TitleColor | AttrBold, -1},
)
}
if self.Inner.Dy() > 6 {
buf.SetString(
title2,
image.Pt(self.Inner.Min.X, title2Y),
AttrPair{line.TitleColor | AttrBold, -1},
)
}
sparkY := (self.Inner.Dy() / lc) * (i + 1)
// finds max data in current view used for relative heights
max := 1
for i := len(line.Data) - 1; i >= 0 && self.Inner.Dx()-((len(line.Data)-1)-i) >= 1; i-- {
if line.Data[i] > max {
max = line.Data[i]
}
}
// prints sparkline
for x := self.Inner.Dx(); x >= 1; x-- {
char := SPARKS[0]
if (self.Inner.Dx() - x) < len(line.Data) {
offset := self.Inner.Dx() - x
cur_item := line.Data[(len(line.Data)-1)-offset]
percent := float64(cur_item) / float64(max)
index := int(percent * 7)
if index < 0 || index >= len(SPARKS) {
panic("TODO")
}
char = SPARKS[index]
}
buf.SetCell(
Cell{char, AttrPair{line.LineColor, -1}},
image.Pt(self.Inner.Min.X+x-1, self.Inner.Min.Y+sparkY-1),
)
}
}
}

206
src/termui/table.go Normal file
View File

@ -0,0 +1,206 @@
package termui
import (
"image"
"strings"
"sync"
. "github.com/gizak/termui"
)
// Table tracks all the attributes of a Table instance
type Table struct {
*Block
sync.Mutex
Header []string
Rows [][]string
ColWidths []int
CellXPos []int // column position
ColResizer func() // for widgets that inherit a Table and want to overload the ColResize method
Gap int // gap between columns
PadLeft int
Cursor bool
CursorColor Attribute
UniqueCol int // the column used to identify the selected item
SelectedItem string // used to keep the cursor on the correct item if the data changes
SelectedRow int
TopRow int // used to indicate where in the table we are scrolled at
}
// NewTable returns a new Table instance
func NewTable() *Table {
self := &Table{
Block: NewBlock(),
// CursorColor: Theme.TableCursor,
SelectedRow: 0,
TopRow: 0,
UniqueCol: 0,
}
self.ColResizer = self.ColResize
return self
}
// ColResize is the default column resizer, but can be overriden.
// ColResize calculates the width of each column.
func (self *Table) ColResize() {
}
func (self *Table) Draw(buf *Buffer) {
self.Lock()
self.Block.Draw(buf)
self.ColResizer()
// finds exact column starting position
self.CellXPos = []int{}
cur := 1 + self.PadLeft
for _, w := range self.ColWidths {
self.CellXPos = append(self.CellXPos, cur)
cur += w
cur += self.Gap
}
// prints header
for i, h := range self.Header {
width := self.ColWidths[i]
if width == 0 {
continue
}
// don't render column if it doesn't fit in widget
if width > (self.Inner.Dx()-self.CellXPos[i])+1 {
continue
}
buf.SetString(
h,
image.Pt(self.Inner.Min.X+self.CellXPos[i]-1, self.Inner.Min.Y),
AttrPair{Theme.Default.Fg | AttrBold, -1},
)
}
// prints each row
for rowNum := self.TopRow; rowNum < self.TopRow+self.Inner.Dy()-1 && rowNum < len(self.Rows); rowNum++ {
if rowNum < 0 || rowNum >= len(self.Rows) {
panic("TODO")
}
row := self.Rows[rowNum]
y := (rowNum + 2) - self.TopRow
// prints cursor
fg := Theme.Default.Fg
if self.Cursor {
if (self.SelectedItem == "" && rowNum == self.SelectedRow) || (self.SelectedItem != "" && self.SelectedItem == row[self.UniqueCol]) {
fg = self.CursorColor | AttrReverse
for _, width := range self.ColWidths {
if width == 0 {
continue
}
buf.SetString(
strings.Repeat(" ", self.Inner.Dx()),
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y-1),
AttrPair{fg, -1},
)
}
self.SelectedItem = row[self.UniqueCol]
self.SelectedRow = rowNum
}
}
// prints each col of the row
for i, width := range self.ColWidths {
if width == 0 {
continue
}
// don't render column if width is greater than distance to end of widget
if width > (self.Inner.Dx()-self.CellXPos[i])+1 {
continue
}
r := TrimString(row[i], width)
buf.SetString(
r,
image.Pt(self.Inner.Min.X+self.CellXPos[i]-1, self.Inner.Min.Y+y-1),
AttrPair{fg, -1},
)
}
}
self.Unlock()
}
/////////////////////////////////////////////////////////////////////////////////
// Cursor Movement //
/////////////////////////////////////////////////////////////////////////////////
// calcPos is used to calculate the cursor position and the current view.
func (self *Table) calcPos() {
self.SelectedItem = ""
if self.SelectedRow < 0 {
self.SelectedRow = 0
}
if self.SelectedRow < self.TopRow {
self.TopRow = self.SelectedRow
}
if self.SelectedRow > len(self.Rows)-1 {
self.SelectedRow = len(self.Rows) - 1
}
if self.SelectedRow > self.TopRow+(self.Inner.Dy()-2) {
self.TopRow = self.SelectedRow - (self.Inner.Dy() - 2)
}
}
func (self *Table) Up() {
self.SelectedRow -= 1
self.calcPos()
}
func (self *Table) Down() {
self.SelectedRow += 1
self.calcPos()
}
func (self *Table) Top() {
self.SelectedRow = 0
self.calcPos()
}
func (self *Table) Bottom() {
self.SelectedRow = len(self.Rows) - 1
self.calcPos()
}
// The number of lines in a page is equal to the height of the widgeself.
func (self *Table) HalfPageUp() {
self.SelectedRow = self.SelectedRow - (self.Inner.Dy()-2)/2
self.calcPos()
}
func (self *Table) HalfPageDown() {
self.SelectedRow = self.SelectedRow + (self.Inner.Dy()-2)/2
self.calcPos()
}
func (self *Table) PageUp() {
self.SelectedRow -= (self.Inner.Dy() - 2)
self.calcPos()
}
func (self *Table) PageDown() {
self.SelectedRow += (self.Inner.Dy() - 2)
self.calcPos()
}
func (self *Table) Click(x, y int) {
x = x - self.Min.X
y = y - self.Min.Y
if (x > 0 && x <= self.Inner.Dx()) && (y > 0 && y <= self.Inner.Dy()) {
self.SelectedRow = (self.TopRow + y) - 2
self.calcPos()
}
}

View File

@ -7,7 +7,7 @@ import (
"strconv"
"time"
ui "github.com/cjbassi/termui"
ui "github.com/cjbassi/gotop/src/termui"
"github.com/distatus/battery"
)
@ -24,7 +24,7 @@ func NewBatt(interval time.Duration, zoom int) *Batt {
Count: len(batts),
interval: interval,
}
self.Label = "Battery Status"
self.Title = "Battery Status"
self.Zoom = zoom
if err != nil {
log.Printf("failed to get battery info from system: %v", err)

View File

@ -5,7 +5,7 @@ import (
"log"
"time"
ui "github.com/cjbassi/termui"
ui "github.com/cjbassi/gotop/src/termui"
psCPU "github.com/shirou/gopsutil/cpu"
)
@ -35,7 +35,7 @@ func NewCPU(interval time.Duration, zoom int, average bool, percpu bool) *CPU {
PerCPU: percpu,
formatString: formatString,
}
self.Label = "CPU Usage"
self.Title = " CPU Usage "
self.Zoom = zoom
if !(self.Average || self.PerCPU) {

View File

@ -7,8 +7,8 @@ import (
"strings"
"time"
ui "github.com/cjbassi/gotop/src/termui"
"github.com/cjbassi/gotop/src/utils"
ui "github.com/cjbassi/termui"
psDisk "github.com/shirou/gopsutil/disk"
)
@ -35,7 +35,7 @@ func NewDisk() *Disk {
interval: time.Second,
Partitions: make(map[string]*Partition),
}
self.Label = "Disk Usage"
self.Title = " Disk Usage "
self.Header = []string{"Disk", "Mount", "Used", "Free", "R/s", "W/s"}
self.Gap = 2
self.ColResizer = self.ColResize
@ -154,8 +154,8 @@ func (self *Disk) update() {
// ColResize overrides the default ColResize in the termui table.
func (self *Disk) ColResize() {
self.ColWidths = []int{
utils.Max(4, (self.X-29)/2),
utils.Max(5, (self.X-29)/2),
utils.Max(4, (self.Inner.Dx()-29)/2),
utils.Max(5, (self.Inner.Dx()-29)/2),
4, 5, 5, 5,
}

View File

@ -1,9 +1,10 @@
package widgets
import (
"image"
"strings"
ui "github.com/cjbassi/termui"
ui "github.com/gizak/termui"
)
const KEYBINDS = `
@ -34,27 +35,33 @@ CPU and Mem graph scaling:
`
type HelpMenu struct {
*ui.Block
ui.Block
}
func NewHelpMenu() *HelpMenu {
block := ui.NewBlock()
block.X = 51 // width - 1
block.Y = 24 // height - 1
return &HelpMenu{block}
return &HelpMenu{
Block: *ui.NewBlock(),
}
}
func (self *HelpMenu) Buffer() *ui.Buffer {
buf := self.Block.Buffer()
func (self *HelpMenu) Resize(termWidth, termHeight int) {
textWidth := 53
textHeight := 22
x := (termWidth - textWidth) / 2
y := (termHeight - textHeight) / 2
self.Block.XOffset = (ui.Body.Width - self.Block.X) / 2 // X coordinate
self.Block.YOffset = (ui.Body.Height - self.Block.Y) / 2 // Y coordinate
self.Block.SetRect(x, y, textWidth+x, textHeight+y)
}
func (self *HelpMenu) Draw(buf *ui.Buffer) {
self.Block.Draw(buf)
for y, line := range strings.Split(KEYBINDS, "\n") {
for x, char := range line {
buf.SetCell(x+1, y, ui.NewCell(char, ui.Color(7), self.Bg))
buf.SetCell(
ui.Cell{char, ui.AttrPair{ui.Attribute(7), -1}},
image.Pt(self.Inner.Min.X+x, self.Inner.Min.Y+y-1),
)
}
}
return buf
}

View File

@ -5,8 +5,8 @@ import (
"log"
"time"
ui "github.com/cjbassi/gotop/src/termui"
"github.com/cjbassi/gotop/src/utils"
ui "github.com/cjbassi/termui"
psMem "github.com/shirou/gopsutil/mem"
)
@ -20,7 +20,7 @@ func NewMem(interval time.Duration, zoom int) *Mem {
LineGraph: ui.NewLineGraph(),
interval: interval,
}
self.Label = "Memory Usage"
self.Title = " Memory Usage "
self.Zoom = zoom
self.Data["Main"] = []float64{0}
self.Data["Swap"] = []float64{0}

View File

@ -5,8 +5,8 @@ import (
"log"
"time"
ui "github.com/cjbassi/gotop/src/termui"
"github.com/cjbassi/gotop/src/utils"
ui "github.com/cjbassi/termui"
psNet "github.com/shirou/gopsutil/net"
)
@ -30,7 +30,7 @@ func NewNet() *Net {
Sparklines: spark,
interval: time.Second,
}
self.Label = "Network Usage"
self.Title = " Network Usage "
self.update()

View File

@ -8,8 +8,9 @@ import (
"strconv"
"time"
ui "github.com/cjbassi/gotop/src/termui"
"github.com/cjbassi/gotop/src/utils"
ui "github.com/cjbassi/termui"
"github.com/gizak/termui"
psCPU "github.com/shirou/gopsutil/cpu"
)
@ -49,7 +50,7 @@ func NewProc() *Proc {
sortMethod: "c",
group: true,
}
self.Label = "Processes"
self.Title = " Processes "
self.ColResizer = self.ColResize
self.Cursor = true
self.Gap = 3
@ -65,7 +66,9 @@ func NewProc() *Proc {
go func() {
ticker := time.NewTicker(self.interval)
for range ticker.C {
self.Lock()
self.update()
self.Unlock()
}
}()
@ -108,11 +111,11 @@ func (self *Proc) Sort() {
// ColResize overrides the default ColResize in the termui table.
func (self *Proc) ColResize() {
self.ColWidths = []int{
5, utils.Max(self.X-26, 10), 4, 4,
5, utils.Max(self.Inner.Dx()-26, 10), 4, 4,
}
}
func (self *Proc) ChangeSort(e ui.Event) {
func (self *Proc) ChangeSort(e termui.Event) {
if self.sortMethod != e.ID {
self.sortMethod = e.ID
self.Top()

50
src/widgets/statusbar.go Normal file
View File

@ -0,0 +1,50 @@
package widgets
import (
"image"
"os"
"time"
ui "github.com/gizak/termui"
)
type StatusBar struct {
ui.Block
}
func NewStatusBar() *StatusBar {
self := &StatusBar{*ui.NewBlock()}
self.Border = false
return self
}
func (self *StatusBar) Draw(buf *ui.Buffer) {
self.Block.Draw(buf)
hostname, _ := os.Hostname()
buf.SetString(
hostname,
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+(self.Inner.Dy()/2)),
ui.AttrPair{ui.Attribute(7), -1},
)
t := time.Now()
_time := t.Format("15:04:05")
buf.SetString(
_time,
image.Pt(
self.Inner.Min.X+(self.Inner.Dx()/2)-len(_time)/2,
self.Inner.Min.Y+(self.Inner.Dy()/2),
),
ui.AttrPair{7, -1},
)
buf.SetString(
"gotop",
image.Pt(
self.Inner.Max.X-6,
self.Inner.Min.Y+(self.Inner.Dy()/2),
),
ui.AttrPair{7, -1},
)
}

View File

@ -5,10 +5,11 @@ package widgets
import (
"fmt"
"image"
"sort"
"time"
ui "github.com/cjbassi/termui"
ui "github.com/gizak/termui"
)
type Temp struct {
@ -16,8 +17,8 @@ type Temp struct {
interval time.Duration
Data map[string]int
Threshold int
TempLow ui.Color
TempHigh ui.Color
TempLow ui.Attribute
TempHigh ui.Attribute
Fahrenheit bool
}
@ -28,7 +29,7 @@ func NewTemp(fahrenheit bool) *Temp {
Data: make(map[string]int),
Threshold: 80, // temp at which color should change
}
self.Label = "Temperatures"
self.Title = " Temperatures "
if fahrenheit {
self.Fahrenheit = true
@ -47,9 +48,8 @@ func NewTemp(fahrenheit bool) *Temp {
return self
}
// Buffer implements ui.Bufferer interface and renders the widget.
func (self *Temp) Buffer() *ui.Buffer {
buf := self.Block.Buffer()
func (self *Temp) Draw(buf *ui.Buffer) {
self.Block.Draw(buf)
var keys []string
for key := range self.Data {
@ -58,7 +58,7 @@ func (self *Temp) Buffer() *ui.Buffer {
sort.Strings(keys)
for y, key := range keys {
if y+1 > self.Y {
if y+1 > self.Inner.Dy() {
break
}
@ -67,14 +67,23 @@ func (self *Temp) Buffer() *ui.Buffer {
fg = self.TempHigh
}
s := ui.MaxString(key, (self.X - 4))
buf.SetString(1, y+1, s, self.Fg, self.Bg)
s := ui.TrimString(key, (self.Inner.Dx() - 4))
buf.SetString(s,
image.Pt(self.Inner.Min.X, self.Inner.Min.Y+y),
ui.Theme.Default,
)
if self.Fahrenheit {
buf.SetString(self.X-3, y+1, fmt.Sprintf("%3dF", self.Data[key]), fg, self.Bg)
buf.SetString(
fmt.Sprintf("%3dF", self.Data[key]),
image.Pt(self.Inner.Dx()-3, y+1),
ui.AttrPair{fg, -1},
)
} else {
buf.SetString(self.X-3, y+1, fmt.Sprintf("%3dC", self.Data[key]), fg, self.Bg)
buf.SetString(
fmt.Sprintf("%3dC", self.Data[key]),
image.Pt(self.Inner.Max.X-4, self.Inner.Min.Y+y),
ui.AttrPair{fg, -1},
)
}
}
return buf
}