summaryrefslogtreecommitdiffstats
path: root/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'widgets')
-rw-r--r--widgets/battery.go94
-rw-r--r--widgets/batterygauge.go83
-rw-r--r--widgets/cpu.go112
-rw-r--r--widgets/disk.go169
-rw-r--r--widgets/drawille-go/LICENSE.md661
-rw-r--r--widgets/drawille-go/README.md24
-rw-r--r--widgets/drawille-go/drawille.go275
-rw-r--r--widgets/entry.go113
-rw-r--r--widgets/gauge.go22
-rw-r--r--widgets/linegraph.go228
-rw-r--r--widgets/linegraph_test.go60
-rw-r--r--widgets/mem.go76
-rw-r--r--widgets/metrics.go19
-rw-r--r--widgets/net.go134
-rw-r--r--widgets/proc.go29
-rw-r--r--widgets/proc_freebsd.go2
-rw-r--r--widgets/proc_linux.go3
-rw-r--r--widgets/proc_windows.go4
-rw-r--r--widgets/sparkline.go106
-rw-r--r--widgets/statusbar.go14
-rw-r--r--widgets/table.go221
-rw-r--r--widgets/temp.go93
-rw-r--r--widgets/widgets.go13
23 files changed, 1979 insertions, 576 deletions
diff --git a/widgets/battery.go b/widgets/battery.go
index 4a8dcbf..0fcf874 100644
--- a/widgets/battery.go
+++ b/widgets/battery.go
@@ -2,61 +2,38 @@ package widgets
import (
"fmt"
- "log"
"math"
"strconv"
"time"
- "github.com/VictoriaMetrics/metrics"
- "github.com/distatus/battery"
-
- ui "github.com/xxxserxxx/gotop/v4/termui"
+ "github.com/xxxserxxx/gotop/v4/devices"
)
type BatteryWidget struct {
- *ui.LineGraph
+ *LineGraph
updateInterval time.Duration
+ d []*devices.Batteries
}
func NewBatteryWidget(horizontalScale int) *BatteryWidget {
- self := &BatteryWidget{
- LineGraph: ui.NewLineGraph(),
+ bw := &BatteryWidget{
+ LineGraph: NewLineGraph(),
updateInterval: time.Minute,
+ d: make([]*devices.Batteries, 0),
}
- self.Title = tr.Value("widget.label.battery")
- self.HorizontalScale = horizontalScale
+ bw.Title = tr.Value("widget.label.battery")
+ bw.HorizontalScale = horizontalScale
+
+ return bw
+}
+func (b *BatteryWidget) Attach(n *devices.Batteries) {
+ b.d = append(b.d, n)
// intentional duplicate
// adds 2 datapoints to the graph, otherwise the dot is difficult to see
- self.update()
- self.update()
+ b.Update()
+ b.Update()
- go func() {
- for range time.NewTicker(self.updateInterval).C {
- self.Lock()
- self.update()
- self.Unlock()
- }
- }()
-
- return self
-}
-
-func (b *BatteryWidget) EnableMetric() {
- bats, err := battery.GetAll()
- if err != nil {
- log.Printf(tr.Value("error.metricsetup", "batt", err.Error()))
- return
- }
- for i, _ := range bats {
- id := makeID(i)
- metrics.NewGauge(makeName("battery", i), func() float64 {
- if ds, ok := b.Data[id]; ok {
- return ds[len(ds)-1]
- }
- return 0.0
- })
- }
}
func makeID(i int) string {
@@ -67,38 +44,17 @@ func (b *BatteryWidget) Scale(i int) {
b.LineGraph.HorizontalScale = i
}
-func (b *BatteryWidget) update() {
- batteries, err := battery.GetAll()
- if err != nil {
- switch errt := err.(type) {
- case battery.ErrFatal:
- log.Printf(tr.Value("error.fatalfetch", "batt", err.Error()))
- return
- case battery.Errors:
- batts := make([]*battery.Battery, 0)
- for i, e := range errt {
- if e == nil {
- batts = append(batts, batteries[i])
- } else {
- log.Printf(tr.Value("error.recovfetch"), "batt", e.Error())
- }
+func (b *BatteryWidget) Update() {
+ for _, bats := range b.d {
+ for i, battery := range bats.Data {
+ if battery.Full == 0.0 {
+ continue
}
- if len(batts) < 1 {
- log.Print(tr.Value("error.nodevfound", "batt"))
- return
- }
- batteries = batts
- }
- }
- for i, battery := range batteries {
- if battery.Full == 0.0 {
- continue
+ id := makeID(i)
+ perc := battery.Current / battery.Full
+ percentFull := math.Abs(perc) * 100.0
+ b.Data[id] = append(b.Data[id], percentFull)
+ b.Labels[id] = fmt.Sprintf("%3.0f%% %.0f/%.0f", percentFull, math.Abs(battery.Current), math.Abs(battery.Full))
}
- id := makeID(i)
- perc := battery.Current / battery.Full
- percentFull := math.Abs(perc) * 100.0
- // TODO: look into this sort of thing; doesn't the array grow forever? Is the widget library truncating it?
- b.Data[id] = append(b.Data[id], percentFull)
- b.Labels[id] = fmt.Sprintf("%3.0f%% %.0f/%.0f", percentFull, math.Abs(battery.Current), math.Abs(battery.Full))
}
}
diff --git a/widgets/batterygauge.go b/widgets/batterygauge.go
index 3a89590..ddbbac6 100644
--- a/widgets/batterygauge.go
+++ b/widgets/batterygauge.go
@@ -2,77 +2,60 @@ package widgets
import (
"fmt"
- "log"
-
"time"
- "github.com/VictoriaMetrics/metrics"
- "github.com/distatus/battery"
-
- "github.com/xxxserxxx/gotop/v4/termui"
+ "github.com/xxxserxxx/gotop/v4/devices"
)
+// FIXME gauge isn't updating.
+
type BatteryGauge struct {
- *termui.Gauge
+ *Gauge
+ d []*devices.Batteries
}
+// NewBatteryGauge creates a gauge widget representing the percentage of how
+// full power a battery is.
func NewBatteryGauge() *BatteryGauge {
- self := &BatteryGauge{Gauge: termui.NewGauge()}
+ self := &BatteryGauge{
+ Gauge: NewGauge(),
+ d: make([]*devices.Batteries, 0),
+ }
self.Title = tr.Value("widget.label.gauge")
- self.update()
-
- go func() {
- for range time.NewTicker(time.Second).C {
- self.Lock()
- self.update()
- self.Unlock()
- }
- }()
-
return self
}
-func (b *BatteryGauge) EnableMetric() {
- metrics.NewGauge(makeName("battery", "total"), func() float64 {
- return float64(b.Percent)
- })
+func (g *BatteryGauge) Attach(b *devices.Batteries) {
+ g.d = append(g.d, b)
}
// Only report battery errors once.
var errLogged = false
-func (b *BatteryGauge) update() {
- bats, err := battery.GetAll()
- if err != nil {
- if !errLogged {
- log.Printf("error setting up batteries: %v", err)
- errLogged = true
- }
- }
- if len(bats) < 1 {
- b.Label = fmt.Sprintf("N/A")
- return
- }
+func (b *BatteryGauge) Update() {
mx := 0.0
cu := 0.0
- charging := "%d%% ⚡%s"
- rate := 0.0
- for _, bat := range bats {
- if bat.Full == 0.0 {
- continue
- }
- mx += bat.Full
- cu += bat.Current
- if rate < bat.ChargeRate {
- rate = bat.ChargeRate
- }
- if bat.State == battery.Charging {
- charging = "%d%% 🔌%s"
+ // default: discharging
+ formatString := "%d%% ⚡%s"
+ var rate time.Duration
+ for _, bats := range b.d {
+ for _, bat := range bats.Data {
+ if bat.Full == 0.0 {
+ continue
+ }
+ mx += bat.Full
+ cu += bat.Current
+ if bat.Charging {
+ fullTime := (mx - cu) / bat.ChargeRate
+ rate, _ = time.ParseDuration(fmt.Sprintf("%fh", fullTime))
+ formatString = "%d%% 🔌%s"
+ } else {
+ runTime := cu / bat.ChargeRate
+ rate, _ = time.ParseDuration(fmt.Sprintf("%fh", runTime))
+ }
}
}
- tn := (mx - cu) / rate
- d, _ := time.ParseDuration(fmt.Sprintf("%fh", tn))
b.Percent = int((cu / mx) * 100.0)
- b.Label = fmt.Sprintf(charging, b.Percent, d.Truncate(time.Minute))
+ b.Label = fmt.Sprintf(formatString, b.Percent, rate.Truncate(time.Minute))
}
diff --git a/widgets/cpu.go b/widgets/cpu.go
index bb72866..b497754 100644
--- a/widgets/cpu.go
+++ b/widgets/cpu.go
@@ -4,31 +4,32 @@ import (
"fmt"
"time"
- "github.com/VictoriaMetrics/metrics"
"github.com/VividCortex/ewma"
"github.com/xxxserxxx/gotop/v4/devices"
"github.com/gizak/termui/v3"
- ui "github.com/xxxserxxx/gotop/v4/termui"
)
+const AVRG = "AVRG"
+
// TODO Maybe group CPUs in columns if space permits
+// TODO add CPU freq
type CPUWidget struct {
- *ui.LineGraph
- CPUCount int
+ *LineGraph
+ cpuCount int
ShowAverageLoad bool
ShowPerCPULoad bool
updateInterval time.Duration
cpuLoads map[string]float64
average ewma.MovingAverage
+ cpus []*devices.CPUs
+ keys [][]string
}
-var cpuLabels []string
-
func NewCPUWidget(updateInterval time.Duration, horizontalScale int, showAverageLoad bool, showPerCPULoad bool) *CPUWidget {
self := &CPUWidget{
- LineGraph: ui.NewLineGraph(),
- CPUCount: len(cpuLabels),
+ LineGraph: NewLineGraph(),
+ cpuCount: len(cpuLabels),
updateInterval: updateInterval,
ShowAverageLoad: showAverageLoad,
ShowPerCPULoad: showPerCPULoad,
@@ -40,7 +41,7 @@ func NewCPUWidget(updateInterval time.Duration, horizontalScale int, showAverage
self.HorizontalScale = horizontalScale
if !(self.ShowAverageLoad || self.ShowPerCPULoad) {
- if self.CPUCount <= 8 {
+ if self.cpuCount <= 8 {
self.ShowPerCPULoad = true
} else {
self.ShowAverageLoad = true
@@ -51,41 +52,32 @@ func NewCPUWidget(updateInterval time.Duration, horizontalScale int, showAverage
self.Data[AVRG] = []float64{0}
}
- if self.ShowPerCPULoad {
- cpus := make(map[string]int)
- devices.UpdateCPU(cpus, self.updateInterval, self.ShowPerCPULoad)
- for k, v := range cpus {
- self.Data[k] = []float64{float64(v)}
- }
- }
-
- self.update()
-
- go func() {
- for range time.NewTicker(self.updateInterval).C {
- self.update()
- }
- }()
-
return self
}
-const AVRG = "AVRG"
+var cpuLabels []string
-func (cpu *CPUWidget) EnableMetric() {
- if cpu.ShowAverageLoad {
- metrics.NewGauge(makeName("cpu", " avg"), func() float64 {
- return cpu.cpuLoads[AVRG]
- })
- } else {
- cpus := make(map[string]int)
- devices.UpdateCPU(cpus, cpu.updateInterval, cpu.ShowPerCPULoad)
- for key, perc := range cpus {
- kc := key
- cpu.cpuLoads[key] = float64(perc)
- metrics.NewGauge(makeName("cpu", key), func() float64 {
- return cpu.cpuLoads[kc]
- })
+func (cpu *CPUWidget) Attach(cs *devices.CPUs) {
+ cpu.cpus = append(cpu.cpus, cs)
+
+ cpu.cpuCount = 0
+ cpu.keys = make([][]string, len(cpu.cpus))
+ if cpu.ShowPerCPULoad {
+ cpu.Data = make(map[string][]float64)
+ for _, c := range cpu.cpus {
+ cpu.cpuCount += len(c.Data)
+ }
+ formatString := "%s%1d"
+ if cpu.cpuCount > 10 {
+ formatString = "%s%02d"
+ }
+ for i, ci := range cpu.cpus {
+ cpu.keys[i] = make([]string, len(ci.Data))
+ for j, _ := range ci.Data {
+ key := fmt.Sprintf(formatString, ci.Name, j)
+ cpu.Data[key] = make([]float64, 0)
+ cpu.keys[i][j] = key
+ }
}
}
}
@@ -94,28 +86,30 @@ func (cpu *CPUWidget) Scale(i int) {
cpu.LineGraph.HorizontalScale = i
}
-func (cpu *CPUWidget) update() {
- go func() {
- cpus := make(map[string]int)
- devices.UpdateCPU(cpus, cpu.updateInterval, true)
- cpu.Lock()
- defer cpu.Unlock()
- // AVG = ((AVG*i)+n)/(i+1)
- var sum int
- for key, percent := range cpus {
- sum += percent
- if cpu.ShowPerCPULoad {
+func (cpu *CPUWidget) Update() {
+ cpus := make(map[string]int)
+ // AVG = ((AVG*i)+n)/(i+1)
+ var sum float64
+ if cpu.ShowPerCPULoad {
+ for i, c := range cpu.cpus {
+ sum += c.Average
+ for j, percent := range c.Data {
+ key := cpu.keys[i][j]
cpu.Data[key] = append(cpu.Data[key], float64(percent))
- cpu.Labels[key] = fmt.Sprintf("%3d%%", percent)
+ cpu.Labels[key] = fmt.Sprintf("%3d%%", int(percent))
cpu.cpuLoads[key] = float64(percent)
}
}
- if cpu.ShowAverageLoad {
- cpu.average.Add(float64(sum) / float64(len(cpus)))
- avg := cpu.average.Value()
- cpu.Data[AVRG] = append(cpu.Data[AVRG], avg)
- cpu.Labels[AVRG] = fmt.Sprintf("%3.0f%%", avg)
- cpu.cpuLoads[AVRG] = avg
+ } else {
+ for _, c := range cpu.cpus {
+ sum += c.Average
}
- }()
+ }
+ if cpu.ShowAverageLoad {
+ cpu.average.Add(sum / float64(len(cpus)))
+ avg := cpu.average.Value()
+ cpu.Data[AVRG] = append(cpu.Data[AVRG], avg)
+ cpu.Labels[AVRG] = fmt.Sprintf("%3.0f%%", avg)
+ cpu.cpuLoads[AVRG] = avg
+ }
}
diff --git a/widgets/disk.go b/widgets/disk.go
index 9df3c3b..e0aa4fe 100644
--- a/widgets/disk.go
+++ b/widgets/disk.go
@@ -2,40 +2,25 @@ package widgets
import (
"fmt"
- "log"
"sort"
"strings"
"time"
- "github.com/VictoriaMetrics/metrics"
- psDisk "github.com/shirou/gopsutil/disk"
-
- ui "github.com/xxxserxxx/gotop/v4/termui"
+ "github.com/xxxserxxx/gotop/v4/devices"
"github.com/xxxserxxx/gotop/v4/utils"
)
-type Partition struct {
- Device string
- MountPoint string
- BytesRead uint64
- BytesWritten uint64
- BytesReadRecently string
- BytesWrittenRecently string
- UsedPercent uint32
- Free string
-}
-
type DiskWidget struct {
- *ui.Table
- updateInterval time.Duration
- Partitions map[string]*Partition
+ *Table
+ parts datas
+ factor uint64
}
-func NewDiskWidget() *DiskWidget {
+func NewDiskWidget(refresh time.Duration) *DiskWidget {
self := &DiskWidget{
- Table: ui.NewTable(),
- updateInterval: time.Second,
- Partitions: make(map[string]*Partition),
+ Table: NewTable(),
+ parts: datas{make([]string, 0), make([]*devices.Partition, 0)},
+ factor: uint64(refresh / time.Second),
}
self.Table.Tr = tr
self.Title = tr.Value("widget.label.disk")
@@ -49,124 +34,46 @@ func NewDiskWidget() *DiskWidget {
}
}
- self.update()
-
- go func() {
- for range time.NewTicker(self.updateInterval).C {
- self.Lock()
- self.update()
- self.Unlock()
- }
- }()
-
return self
}
-func (disk *DiskWidget) EnableMetric() {
- for key, part := range disk.Partitions {
- pc := part
- metrics.NewGauge(makeName("disk", strings.ReplaceAll(key, "/", ":")), func() float64 {
- return float64(pc.UsedPercent) / 100.0
- })
+func (disk *DiskWidget) Attach(d devices.Disk) {
+ for name, part := range d {
+ disk.parts.names = append(disk.parts.names, name)
+ disk.parts.parts = append(disk.parts.parts, part)
}
+ sort.Sort(disk.parts)
}
-func (disk *DiskWidget) update() {
- partitions, err := psDisk.Partitions(false)
- if err != nil {
- log.Printf(tr.Value("error.setup", "disk-partitions", err.Error()))
- return
- }
-
- // add partition if it's new
- for _, partition := range partitions {
- // don't show loop devices
- if strings.HasPrefix(partition.Device, "/dev/loop") {
- continue
- }
- // don't show docker container filesystems
- if strings.HasPrefix(partition.Mountpoint, "/var/lib/docker/") {
- continue
- }
- // check if partition doesn't already exist in our list
- if _, ok := disk.Partitions[partition.Device]; !ok {
- disk.Partitions[partition.Device] = &Partition{
- Device: partition.Device,
- MountPoint: partition.Mountpoint,
- }
- }
- }
-
- // delete a partition if it no longer exists
- toDelete := []string{}
- for device := range disk.Partitions {
- exists := false
- for _, partition := range partitions {
- if device == partition.Device {
- exists = true
- break
- }
- }
- if !exists {
- toDelete = append(toDelete, device)
- }
- }
- for _, device := range toDelete {
- delete(disk.Partitions, device)
- }
-
- // updates partition info. We add 0.5 to all values to make sure the truncation rounds
- for _, partition := range disk.Partitions {
- usage, err := psDisk.Usage(partition.MountPoint)
- if err != nil {
- log.Printf(tr.Value("error.recovfetch", "partition-"+partition.MountPoint+"-usage", err.Error()))
- continue
- }
- partition.UsedPercent = uint32(usage.UsedPercent + 0.5)
- bytesFree, magnitudeFree := utils.ConvertBytes(usage.Free)
- partition.Free = fmt.Sprintf("%3d%s", uint64(bytesFree+0.5), magnitudeFree)
-
- ioCounters, err := psDisk.IOCounters(partition.Device)
- if err != nil {
- log.Printf(tr.Value("error.recovfetch", "partition-"+partition.Device+"-rw", err.Error()))
- continue
- }
- ioCounter := ioCounters[strings.Replace(partition.Device, "/dev/", "", -1)]
- bytesRead, bytesWritten := ioCounter.ReadBytes, ioCounter.WriteBytes
- if partition.BytesRead != 0 { // if this isn't the first update
- bytesReadRecently := bytesRead - partition.BytesRead
- bytesWrittenRecently := bytesWritten - partition.BytesWritten
+func (disk *DiskWidget) Update() {
+ // converts self.Partitions into self.Rows which is a [][]String
+ disk.Rows = make([][]string, len(disk.parts.names))
- readFloat, readMagnitude := utils.ConvertBytes(bytesReadRecently)
- writeFloat, writeMagnitude := utils.ConvertBytes(bytesWrittenRecently)
- bytesReadRecently, bytesWrittenRecently = uint64(readFloat+0.5), uint64(writeFloat+0.5)
- partition.BytesReadRecently = fmt.Sprintf("%d%s", bytesReadRecently, readMagnitude)
- partition.BytesWrittenRecently = fmt.Sprintf("%d%s", bytesWrittenRecently, writeMagnitude)
- } else {
- partition.BytesReadRecently = fmt.Sprintf("%d%s", 0, "B")
- partition.BytesWrittenRecently = fmt.Sprintf("%d%s", 0, "B")
- }
- partition.BytesRead, partition.BytesWritten = bytesRead, bytesWritten
+ for i, part := range disk.parts.parts {
+ disk.Rows[i] = make([]string, 6)
+ disk.Rows[i][0] = strings.Replace(strings.Replace(part.Device, "/dev/", "", -1), "mapper/", "", -1)
+ disk.Rows[i][1] = part.MountPoint
+ disk.Rows[i][2] = fmt.Sprintf("%d%%", int(part.UsedPercent))
+ disk.Rows[i][3] = fmt.Sprintf("%d", part.Free)
+ disk.Rows[i][4] = fmt.Sprintf("%d", part.BytesReadRecently/disk.factor)
+ disk.Rows[i][5] = fmt.Sprintf("%d", part.BytesWrittenRecently/disk.factor)
}
+}
- // converts self.Partitions into self.Rows which is a [][]String
+type datas struct {
+ names []string
+ parts []*devices.Partition
+}
- sortedPartitions := []string{}
- for seriesName := range disk.Partitions {
- sortedPartitions = append(sortedPartitions, seriesName)
- }
- sort.Strings(sortedPartitions)
+func (d datas) Len() int {
+ return len(d.names)
+}
- disk.Rows = make([][]string, len(disk.Partitions))
+func (d datas) Less(i, j int) bool {
+ return d.names[i] < d.names[j]
+}
- for i, key := range sortedPartitions {
- partition := disk.Partitions[key]
- disk.Rows[i] = make([]string, 6)
- disk.Rows[i][0] = strings.Replace(strings.Replace(partition.Device, "/dev/", "", -1), "mapper/", "", -1)
- disk.Rows[i][1] = partition.MountPoint
- disk.Rows[i][2] = fmt.Sprintf("%d%%", partition.UsedPercent)
- disk.Rows[i][3] = partition.Free
- disk.Rows[i][4] = partition.BytesReadRecently
- disk.Rows[i][5] = partition.BytesWrittenRecently
- }
+func (d datas) Swap(i, j int) {
+ d.names[i], d.names[j] = d.names[j], d.names[i]
+ d.parts[i], d.parts[j] = d.parts[j], d.parts[i]
}
diff --git a/widgets/drawille-go/LICENSE.md b/widgets/drawille-go/LICENSE.md
new file mode 100644
index 0000000..58777e3
--- /dev/null
+++ b/widgets/drawille-go/LICENSE.md
@@ -0,0 +1,661 @@
+GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no char