-
-
Save qrwteyrutiyoup/0cfb193692e1da14a0846a05203ebcd0 to your computer and use it in GitHub Desktop.
This file contains 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 ( | |
"bufio" | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"os" | |
"os/exec" | |
"os/signal" | |
"regexp" | |
"runtime" | |
"strconv" | |
"strings" | |
"syscall" | |
"time" | |
) | |
type DwmIcon struct { | |
Name string | |
Icon string | |
} | |
type DwmConfig struct { | |
Sound_Device string | |
Network_Interface string | |
Icons []DwmIcon | |
} | |
type DwmColor struct { | |
start string | |
end string | |
} | |
var ( | |
config_file string | |
dwm_config DwmConfig | |
icons map[string]string | |
collected_data map[string]string | |
cores = runtime.NumCPU() | |
rx_old = 0 | |
tx_old = 0 | |
keys = []string{"calendar", "clock", "rx", "tx", "volume", "battery", "brightness", "cpu", "ram"} | |
// colors = []DwmColor{{"\x06", "\x07"}, {"\x08", "\x09"}, {"\x0a", "\x0b"}, {"\x0c", "\x0d"}, {"\x0e", "\x0f"}, {"\x10", "\x11"}} | |
colors = []DwmColor{{"\x0a", "\x0b"}, {"\x0b", "\x0a"}} | |
valid_net_device = false | |
valid_sound_device = false | |
) | |
const ( | |
version = "0.1" | |
author = "Sergio Correia <sergio@correia.cc>" | |
) | |
func is_valid_net_device() bool { | |
out, err := exec.Command("ip", "addr", "show", dwm_config.Network_Interface).Output() | |
if err != nil || len(out) == 0 { | |
remove_key("rx") | |
remove_key("tx") | |
fmt.Printf("Network device '%s' is not valid; please recheck the config file\n", dwm_config.Network_Interface) | |
return false | |
} | |
return true | |
} | |
func is_valid_sound_device() bool { | |
out, err := exec.Command("pactl", "list", "sinks", "short").Output() | |
if err != nil || len(out) == 0 { | |
fmt.Printf("Sound device '%s' is not valid; please recheck the config file\n", dwm_config.Sound_Device) | |
remove_key("volume") | |
return false | |
} | |
scanner := bufio.NewScanner(strings.NewReader(string(out))) | |
for scanner.Scan() { | |
device_id := strings.Split(scanner.Text(), "\t")[0] | |
if device_id == dwm_config.Sound_Device { | |
return true | |
} | |
} | |
remove_key("volume") | |
fmt.Printf("Sound device '%s' is not valid; please recheck the config file\n", dwm_config.Sound_Device) | |
return false | |
} | |
func load_config() { | |
json_config, err := os.Open(config_file) | |
if err != nil { | |
panic(err) | |
} | |
defer json_config.Close() | |
dwm_config = DwmConfig{} | |
json_parser := json.NewDecoder(json_config) | |
if err = json_parser.Decode(&dwm_config); err != nil { | |
panic(err) | |
} | |
icons = make(map[string]string) | |
for i := range dwm_config.Icons { | |
icons[dwm_config.Icons[i].Name] = dwm_config.Icons[i].Icon | |
} | |
valid_net_device = is_valid_net_device() | |
valid_sound_device = is_valid_sound_device() | |
} | |
func remove_key(key string) { | |
if _, ok := collected_data[key]; ok { | |
delete(collected_data, key) | |
} | |
} | |
func collect_date(key string) { | |
t := time.Now() | |
collected_data[key] = fmt.Sprintf("%s %s", icons[key], t.Format("Mon Jan _2")) | |
} | |
func collect_time(key string) { | |
t := time.Now() | |
collected_data[key] = fmt.Sprintf("%s %s", icons[key], t.Format("15:04:05 MST")) | |
} | |
func adjust_width(s string, width int) string { | |
s = strings.TrimLeft(s, " ") | |
if len(s) < width { | |
diff := width - len(s) | |
for i := 0; i < diff; i++ { | |
s += " " | |
} | |
} | |
return s | |
} | |
func format_bytes(b int) string { | |
kb := float32(b) / 1000.0 | |
if kb < 1 { | |
return adjust_width(fmt.Sprintf("%4dB", b), 6) | |
} | |
mb := kb / 1000.0 | |
if mb < 1 { | |
return adjust_width(fmt.Sprintf("%4.1fK", kb), 6) | |
} | |
gb := mb / 1000.0 | |
if gb < 1 { | |
return adjust_width(fmt.Sprintf("%4.1fM", mb), 6) | |
} | |
tb := gb / 1000.0 | |
if tb < 1 { | |
return adjust_width(fmt.Sprintf("%4.1fG", gb), 6) | |
} | |
return adjust_width(fmt.Sprintf("%4.1fT", tb), 6) | |
} | |
func collect_network(rxkey, txkey string) { | |
if !valid_net_device { | |
return | |
} | |
// from: https://github.com/schachmat/gods/blob/master/gods.go | |
file, err := os.Open("/proc/net/dev") | |
if err != nil { | |
remove_key(rxkey) | |
remove_key(txkey) | |
return | |
} | |
defer file.Close() | |
var void = 0 // target for unused values | |
var dev, rx, tx, rxNow, txNow = "", 0, 0, 0, 0 | |
var scanner = bufio.NewScanner(file) | |
for scanner.Scan() { | |
_, err = fmt.Sscanf(scanner.Text(), "%s %d %d %d %d %d %d %d %d %d", | |
&dev, &rx, &void, &void, &void, &void, &void, &void, &void, &tx) | |
if dev[:len(dev)-1] == dwm_config.Network_Interface { | |
rxNow += rx | |
txNow += tx | |
} | |
} | |
defer func() { rx_old, tx_old = rxNow, txNow }() | |
rxdata := fmt.Sprintf("%s %s", icons[rxkey], format_bytes(rxNow-rx_old)) | |
txdata := fmt.Sprintf("%s %s", icons[txkey], format_bytes(txNow-tx_old)) | |
collected_data[rxkey] = rxdata | |
collected_data[txkey] = txdata | |
} | |
func collect_brightness(key string) { | |
actual, err := ioutil.ReadFile("/sys/class/backlight/intel_backlight/actual_brightness") | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
max, err := ioutil.ReadFile("/sys/class/backlight/intel_backlight/max_brightness") | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
var actual_br, max_br int | |
_, err = fmt.Sscanf(string(actual), "%d", &actual_br) | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
_, err = fmt.Sscanf(string(max), "%d", &max_br) | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
cur := 100 * actual_br / max_br | |
collected_data[key] = fmt.Sprintf("%s %2d%%", icons[key], cur) | |
} | |
func collect_power(key string) { | |
out, err := exec.Command("acpi", "-b").Output() | |
if err != nil || len(out) == 0 { | |
remove_key(key) | |
return | |
} | |
output := string(out) | |
split := strings.Split(output, " ") | |
charge := split[3][:len(split[3])-1] | |
var value int | |
var icon string | |
_, err = fmt.Sscanf(charge, "%d", &value) | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
if value > 75 { | |
icon = icons["battery100"] | |
} else if value > 50 { | |
icon = icons["battery75"] | |
} else if value > 25 { | |
icon = icons["battery50"] | |
} else if value > 10 { | |
icon = icons["battery25"] | |
} else { | |
icon = icons["battery0"] | |
} | |
collected_data[key] = fmt.Sprintf("%s %s", icon, charge) | |
} | |
func collect_ram(key string) { | |
// from: https://github.com/schachmat/gods/blob/master/gods.go | |
file, err := os.Open("/proc/meminfo") | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
defer file.Close() | |
// done must equal the flag combination (0001 | 0010 | 0100 | 1000) = 15 | |
var total, used, done = 0, 0, 0 | |
for info := bufio.NewScanner(file); done != 15 && info.Scan(); { | |
var prop, val = "", 0 | |
if _, err = fmt.Sscanf(info.Text(), "%s %d", &prop, &val); err != nil { | |
remove_key(key) | |
return | |
} | |
switch prop { | |
case "MemTotal:": | |
total = val | |
used += val | |
done |= 1 | |
case "MemFree:": | |
used -= val | |
done |= 2 | |
case "Buffers:": | |
used -= val | |
done |= 4 | |
case "Cached:": | |
used -= val | |
done |= 8 | |
} | |
} | |
ram := used * 100 / total | |
collected_data[key] = fmt.Sprintf("%s %2d%% ", icons[key], ram) | |
} | |
func collect_cpu(key string) { | |
// from: https://github.com/schachmat/gods/blob/master/gods.go | |
var load float32 | |
loadavg, err := ioutil.ReadFile("/proc/loadavg") | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
_, err = fmt.Sscanf(string(loadavg), "%f", &load) | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
cpu := int(load * 100.0 / float32(cores)) | |
collected_data[key] = fmt.Sprintf("%s %2d%%", icons[key], cpu) | |
} | |
func collect_volume(key string) { | |
if !valid_sound_device { | |
return | |
} | |
device_id, err := strconv.Atoi(dwm_config.Sound_Device) | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
out, err := exec.Command("pactl", "list", "sinks").Output() | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
output := string(out) | |
var trimmed string | |
volumes := make([]string, device_id+1, device_id+1) | |
muted := make([]bool, device_id+1, device_id+1) | |
volumes_index := 0 | |
mute_index := 0 | |
re_inside_whtsp := regexp.MustCompile(`[\s\p{Zs}]{2,}`) | |
scanner := bufio.NewScanner(strings.NewReader(output)) | |
for scanner.Scan() { | |
trimmed = re_inside_whtsp.ReplaceAllString(strings.TrimLeft(scanner.Text(), " \t\r\n"), " ") | |
if strings.HasPrefix(trimmed, "Mute: ") { | |
// Mute: no | |
muted[mute_index] = (strings.Split(trimmed, " ")[1] == "yes") | |
mute_index++ | |
} | |
if strings.HasPrefix(trimmed, "Volume: ") { | |
// Volume: front-left: 43055 / 66% / -10.95 dB, front-right: 43055 / 66% / -10.95 dB | |
//fmt.Println(splitted) | |
volumes[volumes_index] = strings.Split(trimmed, " ")[4] | |
volumes_index++ | |
if volumes_index > device_id { | |
break | |
} | |
} | |
} | |
volume, err := strconv.Atoi(volumes[device_id][:len(volumes[device_id])-1]) | |
if err != nil { | |
remove_key(key) | |
return | |
} | |
var icon string | |
muted_char := " " | |
if muted[device_id] { | |
muted_char = "M" | |
icon = icons["volume_mute"] | |
} else { | |
if volume > 40 { | |
icon = icons["volume_loud"] | |
} else { | |
icon = icons["volume_low"] | |
} | |
} | |
collected_data[key] = fmt.Sprintf("%s %s %s", icon, volumes[device_id], muted_char) | |
} | |
func collect_stats() { | |
collect_volume("volume") | |
collect_time("clock") | |
collect_date("calendar") | |
collect_ram("ram") | |
collect_cpu("cpu") | |
collect_network("rx", "tx") | |
collect_power("battery") | |
collect_brightness("brightness") | |
} | |
func status_bar() string { | |
bar := "" | |
var key string | |
for i := range keys { | |
key = keys[i] | |
if data, ok := collected_data[key]; ok { | |
bar = fmt.Sprintf("%s %s", bar, data) | |
} | |
} | |
return bar | |
} | |
func colored_status_bar() string { | |
bar := colors[0].end | |
var key string | |
num_colors := len(colors) | |
color_index := 1 | |
for i := range keys { | |
key = keys[i] | |
if data, ok := collected_data[key]; ok { | |
bar = fmt.Sprintf("%s %s%s %s", bar, colors[color_index%num_colors].start, colors[color_index%num_colors].end, data) | |
color_index++ | |
} | |
} | |
return bar | |
} | |
func update_status_bar() { | |
collect_stats() | |
exec.Command("xsetroot", "-name", colored_status_bar()).Run() | |
} | |
func reload_status_bar() { | |
collect_volume("volume") | |
collect_brightness("brightness") | |
exec.Command("xsetroot", "-name", colored_status_bar()).Run() | |
} | |
func main() { | |
if len(os.Args) == 1 { | |
fmt.Printf("Usage: %s <config file>\n", os.Args[0]) | |
os.Exit(1) | |
} | |
config_file = os.Args[1] | |
collected_data = make(map[string]string) | |
load_config() | |
signal_chan := make(chan os.Signal, 1) | |
go func() { | |
for { | |
s := <-signal_chan | |
switch s { | |
case syscall.SIGHUP: | |
fmt.Println("reloading config") | |
load_config() | |
update_status_bar() | |
case syscall.SIGUSR1: | |
reload_status_bar() | |
default: | |
fmt.Println(s) | |
} | |
} | |
}() | |
signal.Notify(signal_chan, syscall.SIGHUP, syscall.SIGUSR1) | |
for { | |
update_status_bar() | |
// Sleep until beginning of next second. | |
var now = time.Now() | |
time.Sleep(now.Truncate(time.Second).Add(time.Second).Sub(now)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
"sound_device": "1",
./dwm-bar dwm-status.json
panic: runtime error: slice bounds out of range
goroutine 1 [running]:
main.collect_volume(0x52de0c, 0x6)
/home/shadow/go/src/dwm/dwm-status.go:361 +0x9ef
main.collect_stats()
/home/shadow/go/src/dwm/dwm-status.go:384 +0x3a
main.update_status_bar()
/home/shadow/go/src/dwm/dwm-status.go:422 +0x26
main.main()
/home/shadow/go/src/dwm/dwm-status.go:462 +0x1d2