Last active
August 29, 2015 14:06
-
-
Save mmstick/9d4389ee875aa9ceb62a to your computer and use it in GitHub Desktop.
i3 status bar programmed in Go -- works with both laptops and desktops now. If a battery is present, it will print battery information. Otherwise, it will not print battery information. It will also automatically detect the currently active network connection and use statistics from that to notify the user how much data has been downloaded and u…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io/ioutil" | |
"os" | |
"os/user" | |
"runtime" | |
"strconv" | |
"strings" | |
"syscall" | |
"time" | |
) | |
var sprintf = fmt.Sprintf | |
var refreshRate = 1 * time.Second | |
// Contains system information | |
type systemInfo struct { | |
kernel, cpuModel, host, user string // Static | |
uptime, cpufreqs, memStat, transferStat, batteryInfo, date string // Dynamic | |
memTotal int | |
} | |
// This is used to get a string from the kernel utsname | |
func utsnameToString(unameArray [65]int8) string { | |
var byteString [65]byte | |
var indexLength int | |
for ; unameArray[indexLength] != 0; indexLength++ { | |
byteString[indexLength] = uint8(unameArray[indexLength]) | |
} | |
return string(byteString[0:indexLength]) | |
} | |
// Returns kernel version information | |
func kernelVersion() string { | |
var utsname syscall.Utsname | |
_ = syscall.Uname(&utsname) | |
return utsnameToString(utsname.Release) | |
} | |
// Returns a newline-delimited string slice of the file | |
func parseFile(file string) []string { | |
cached, _ := ioutil.ReadFile(file) | |
return strings.Split(string(cached), "\n") | |
} | |
// Sets the CPU Model | |
func getCPUModel() string { | |
return parseFile("/proc/cpuinfo")[4][13:] | |
} | |
// Returns the amount of memory installed in the system | |
func installedMemory() int { | |
mem := parseFile("/proc/meminfo")[0] | |
totalMem, _ := strconv.Atoi(mem[17 : len(mem)-3]) | |
return totalMem / 1024 | |
} | |
// Returns the memory currently available as an int | |
func memAvailable() int { | |
mem := parseFile("/proc/meminfo")[2] | |
memAvailable, _ := strconv.Atoi(mem[18 : len(mem)-3]) | |
return memAvailable / 1024 | |
} | |
// Returns a string indicating memory usage out of total available memory | |
func memoryStatistics(memTotal *int, memStat *string, done chan bool) { | |
*memStat = sprintf("RAM: %d/%dMB", *memTotal-memAvailable(), *memTotal) | |
done <- true | |
} | |
// Returns the current time | |
func currentTime(date *string, done chan bool) { | |
*date = time.Now().Format(time.RFC1123) | |
done <- true | |
} | |
// Returns the number of CPU cores in the system | |
func parseCPUCount(count string) int { | |
cores, _ := strconv.Atoi(count[11:]) | |
return cores | |
} | |
// Parses CPU frequency information | |
func parseFrequency(frequency string) string { | |
frequency = frequency[11 : len(frequency)-4] | |
if len(frequency) < 4 { | |
frequency = " " + frequency | |
} | |
return frequency + "MHz" | |
} | |
// Obtains the printf format for the current core frequency | |
func getFrequencyFormat(index *int, lastCore int) string { | |
if *index == lastCore { | |
return "%s" | |
} else { | |
return "%s " | |
} | |
} | |
// Returns the frequency of the current core | |
func getCoreFrequency(cpuInfo []string, index *int, format *string) string { | |
return sprintf(*format, parseFrequency(cpuInfo[*index*27+7])) | |
} | |
// Returns a string containing the frequencies of each CPU core | |
func getCPUFrequencies(cpufreqs *string, done chan bool) { | |
cpuInfo := parseFile("/proc/cpuinfo") | |
numCPUs := parseCPUCount(cpuInfo[len(cpuInfo)-17]) + 1 | |
var cpuFrequencies string | |
for index := 0; index < numCPUs; index++ { | |
format := getFrequencyFormat(&index, numCPUs-1) | |
cpuFrequencies += getCoreFrequency(cpuInfo, &index, &format) | |
} | |
*cpufreqs = "Cores:" + cpuFrequencies | |
done <- true | |
} | |
// Returns the status of the battery: [C]harging, [F]ull, or [D]ischarging. | |
func batteryStatus() byte { | |
return parseFile("/sys/class/power_supply/BAT1/status")[0][0] | |
} | |
// Returns the current battery life in percent. | |
func batteryCharge() string { | |
return parseFile("/sys/class/power_supply/BAT1/capacity")[0] | |
} | |
// Returns a string indicating that the battery is currently charging. | |
func batteryIsCharging() string { | |
return sprintf("BAT Charging: %s%%", batteryCharge()) | |
} | |
// Returns a string indicating that the battery is currently discharging. | |
func batteryIsDischarging() string { | |
return sprintf("BAT: %s%%", batteryCharge()) | |
} | |
// Checks the battery status and returns information based on that information. | |
func batteryInformation(batteryStat *string, done chan bool) { | |
switch batteryStatus() { | |
case 'C': | |
*batteryStat = batteryIsCharging() | |
case 'F': | |
*batteryStat = "BAT Full" | |
default: | |
*batteryStat = batteryIsDischarging() | |
} | |
done <- true | |
} | |
// Takes an uptime value and divides it by a scale (days/hours/minutes). | |
// After determining the amount of time in that scale, it subtracts that amount | |
// from uptime and returns the time in days/hours/minutes | |
func getTimeScale(time **int, scale int) int { | |
var output int | |
if **time > scale { | |
output = **time / scale | |
**time -= output * scale | |
} | |
return output | |
} | |
// Adds an extra zero in case the time only has one digit | |
func formatTime(time int) string { | |
output := strconv.Itoa(time) | |
if len(output) == 1 { | |
output = "0" + output | |
} | |
return output | |
} | |
// Returns the time formatted in days and subtracts that from time. | |
func getDays(time *int) string { | |
return formatTime(getTimeScale(&time, 86400)) | |
} | |
// Returns the time formatted in hours and subtracts that from time. | |
func getHours(time *int) string { | |
return formatTime(getTimeScale(&time, 3600)) | |
} | |
// Returns the time formatted in minutes and subtracts that from time. | |
func getMinutes(time *int) string { | |
return formatTime(getTimeScale(&time, 60)) | |
} | |
// Returns the time formatted in seconds. | |
func getSeconds(time *int) string { | |
return formatTime(*time) | |
} | |
// Takes the uptime integer and converts it into a human readable format. | |
// Ex: 01:21:18:57 for days:hours:seconds:minutes | |
func humanReadableTime(time int) string { | |
return sprintf("%s:%s:%s:%s", getDays(&time), getHours(&time), | |
getMinutes(&time), getSeconds(&time)) | |
} | |
// Converts a string that we know is an integer to an integer | |
func strToInt(input string) int { | |
output, _ := strconv.Atoi(input) | |
return output | |
} | |
// Open /proc/uptime and return the value in integer format. | |
func parseUptime() int { | |
parsedInfo := strings.SplitAfter(parseFile("/proc/uptime")[0], " ") | |
return strToInt(parsedInfo[0][0 : len(parsedInfo[0])-4]) | |
} | |
// Returns the current uptime in days:hours:minutes:seconds format | |
func getUptime(uptime *string, done chan bool) { | |
*uptime = sprintf("Uptime: %s", humanReadableTime(parseUptime())) | |
done <- true | |
} | |
// Returns the hostname | |
func getHost() string { | |
host, _ := os.Hostname() | |
return host | |
} | |
// Returns true if the connection is not a loopback address. | |
func connectionIsNotLoopback(connection *string) bool { | |
if *connection == "lo" { | |
return false | |
} else { | |
return true | |
} | |
} | |
// Returns true if the connection is up | |
func connectionIsUp(connection *string) bool { | |
if parseFile("/sys/class/net/" + *connection + "/operstate")[0][0] == 'u' { | |
return true | |
} else { | |
return false | |
} | |
} | |
// Returns true if the connection is active and isn't a loopback address. | |
func connectionIsActive(connection string) bool { | |
if connectionIsNotLoopback(&connection) && connectionIsUp(&connection) { | |
return true | |
} else { | |
return false | |
} | |
} | |
// Returns the currently active connection name. | |
func getCurrentNetwork() string { | |
var activeConnection string | |
networkConnections, _ := ioutil.ReadDir("/sys/class/net/") | |
for _, connection := range networkConnections { | |
if connectionIsActive(connection.Name()) { | |
activeConnection = connection.Name() | |
} | |
} | |
return activeConnection | |
} | |
// Pads digits with spaces so that the status bar always has the same number of | |
// characters. | |
func padDigits(number int) string { | |
numberString := sprintf("%d", number) | |
switch len(numberString) { | |
case 1: | |
return " " + numberString | |
case 2: | |
return " " + numberString | |
case 3: | |
return " " + numberString | |
default: | |
return numberString | |
} | |
} | |
// Formats the bytes into their respectiev scales | |
func formatBytes(bytes int) string { | |
switch { | |
case bytes > 10737418240: | |
return sprintf("%sGiB", padDigits(bytes/1073741824)) | |
case bytes > 10485760: | |
return sprintf("%sMiB", padDigits(bytes/1048576)) | |
case bytes > 10240: | |
return sprintf("%sKiB", padDigits(bytes/1024)) | |
default: | |
return sprintf("%s B", padDigits(bytes)) | |
} | |
} | |
// Returns the transfer statistics directory name. | |
func transferDir() string { | |
return sprintf("/sys/class/net/%s/statistics/", getCurrentNetwork()) | |
} | |
// Returns the contents of the file as an integer variable | |
func fileAsInt(file string) int { | |
return strToInt(parseFile(file)[0]) | |
} | |
// Returns the amount of bytes downloaded since boot. | |
func downloadInformation() string { | |
return formatBytes(fileAsInt(transferDir() + "rx_bytes")) | |
} | |
// Returns the amount of bytes uploaded since boot. | |
func uploadInformation() string { | |
return formatBytes(fileAsInt(transferDir() + "tx_bytes")) | |
} | |
// Returns RX/TX transfer statistics | |
func transferStatistics(transferStat *string, done chan bool) { | |
*transferStat = sprintf("D:%s U:%s", downloadInformation(), | |
uploadInformation()) | |
done <- true | |
} | |
// Returns the user's name | |
func getUsername() string { | |
currentUser, _ := user.Current() | |
return currentUser.Username | |
} | |
// If a battery exists in the system, return true and the number of concurrent | |
// jobs to perform. | |
func ifBatteryExists() (bool, int) { | |
battery, _ := ioutil.ReadDir("/sys/class/power_supply") | |
if len(battery) > 0 { | |
return true, 6 | |
} else { | |
return false, 5 | |
} | |
} | |
// Returns the printf format for formatting the status bar. If a battery exists, | |
// this will return an additional string element. | |
func statusBarFormat(batteryExists bool) string { | |
if batteryExists { | |
return "%s@%s | %s | %s | %s %s | %s | %s | %s | %s\n" | |
} else { | |
return "%s@%s | %s | %s | %s %s | %s | %s | %s\n" | |
} | |
} | |
// Returns system information that does not need to be dynamically updated. | |
func getStaticSystemInformation() *systemInfo { | |
info := systemInfo{ | |
kernel: kernelVersion(), | |
cpuModel: getCPUModel(), | |
memTotal: installedMemory(), | |
host: getHost(), | |
user: getUsername(), | |
} | |
return &info | |
} | |
// Adds dynamic system information to the systemInfo struct and checks for | |
// the existence of a battery. | |
func getDynamicSystemInformation(info systemInfo) (*systemInfo, bool) { | |
batteryExists, jobs := ifBatteryExists() | |
synchronize := make(chan bool, jobs) | |
if batteryExists { | |
go batteryInformation(&info.batteryInfo, synchronize) | |
} | |
go getUptime(&info.uptime, synchronize) | |
go getCPUFrequencies(&info.cpufreqs, synchronize) | |
go memoryStatistics(&info.memTotal, &info.memStat, synchronize) | |
go transferStatistics(&info.transferStat, synchronize) | |
go currentTime(&info.date, synchronize) | |
for jobCount := 0; jobCount < jobs; jobCount++ { | |
<-synchronize | |
} | |
return &info, batteryExists | |
} | |
// Refreshes the status bar | |
func refreshBar(info *systemInfo, batteryExists bool) { | |
if batteryExists { | |
fmt.Printf(statusBarFormat(true), info.user, info.host, info.kernel, | |
info.uptime, info.cpuModel, info.cpufreqs, info.memStat, | |
info.transferStat, info.batteryInfo, info.date) | |
} else { | |
fmt.Printf(statusBarFormat(false), info.user, info.host, info.kernel, | |
info.uptime, info.cpuModel, info.cpufreqs, info.memStat, | |
info.transferStat, info.date) | |
} | |
} | |
func main() { | |
runtime.GOMAXPROCS(6) | |
system := getStaticSystemInformation() | |
for { | |
go refreshBar(getDynamicSystemInformation(*system)) | |
time.Sleep(refreshRate) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment