├── .gitignore ├── LICENSE ├── README.md ├── config ├── goblocks-full.yml ├── goblocks-screenshot.yml └── screenshots │ ├── goblocks-alert.png │ └── goblocks-normal.png ├── goblocks.go └── lib └── modules ├── battery.go ├── command.go ├── configure.go ├── disk.go ├── iface.go ├── keyindicator.go ├── load.go ├── memory.go ├── raid.go ├── temperature.go ├── time.go ├── uptime.go ├── volume.go ├── wifi.go └── zfs.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, David Scholberg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Goblocks 2 | 3 | Goblocks is a fast, lightweight [i3status](https://i3wm.org/i3status/) replacement written in [Go](https://golang.org/), using the [Go-i3barjson](https://github.com/davidscholberg/go-i3barjson) library to communicate with [i3bar](https://i3wm.org/i3bar/). 4 | 5 | The main goal of this project is to have all of the status indicator modules written in pure Go with minimal reliance on external processes. This will keep Goblocks fast and lightweight, allowing the user to configure Goblocks with a very high update frequency without fear of taking up excessive system resource and battery. 6 | 7 | ### Features 8 | 9 | * Status indicators for: 10 | * XKB toggled keys (caps lock, num lock, etc.) 11 | * ZFS status 12 | * RAID status (mdraid only) 13 | * filesystem usage 14 | * system load 15 | * memory availability 16 | * CPU temperature 17 | * network interfaces (up/down status, IP addresses) 18 | * wifi signal strength 19 | * battery level 20 | * volume (ALSA or Pulse channels through amixer) 21 | * uptime 22 | * date/time 23 | * Ability to show output from arbitrary commands. 24 | * Configuration in [YAML](http://yaml.org/) format (see [config/goblocks-full.yml](config/goblocks-full.yml)). 25 | * Color support. 26 | * Ability to configure separate refresh intervals for each individual block. 27 | * Ability to configure UNIX signal handlers to refresh individual blocks. 28 | * Ability to reload the configuration by sending the HUP signal (e.g. `pkill -HUP goblocks`). 29 | * Debug option to [pretty-print](https://en.wikipedia.org/wiki/Prettyprint) Goblocks' JSON output. 30 | 31 | Below is an example of what the configuration at [config/goblocks-screenshot.yml](config/goblocks-screenshot.yml) might look like: 32 | 33 | ![screenshot-normal](config/screenshots/goblocks-normal.png) 34 | 35 | This is an example of what a block that's gone critical might look like: 36 | 37 | ![screenshot-alert](config/screenshots/goblocks-alert.png) 38 | 39 | ### Install 40 | 41 | Gobocks requires Go version 1.7+. 42 | 43 | Arch users can install Goblocks using the [Goblocks AUR package](https://aur.archlinux.org/packages/goblocks/). 44 | 45 | Goblocks can also be installed manually with the following commands: 46 | 47 | ```bash 48 | go get github.com/davidscholberg/goblocks 49 | mkdir -p $HOME/.config/goblocks 50 | cp $GOPATH/src/github.com/davidscholberg/goblocks/config/goblocks-full.yml $HOME/.config/goblocks/goblocks.yml 51 | ``` 52 | 53 | #### Optional dependecies 54 | 55 | The following status indicators have additional dependencies: 56 | 57 | * volume 58 | * requires the `amixer` utility 59 | * zfs 60 | * requires the `zpool` utility 61 | 62 | ### Configure 63 | 64 | Goblocks configuration is specified in [YAML](http://yaml.org/). The configuration file path is `$HOME/.config/goblocks/goblocks.yml`. A full configuration example with all available block types and options can be found at [config/goblocks-full.yml](config/goblocks-full.yml). 65 | 66 | ### Run 67 | 68 | To use Goblocks in your i3bar, add the Goblocks binary to the [bar section of your i3 config](https://i3wm.org/docs/userguide.html#_configuring_i3bar). Note that if `$GOPATH/bin/` is not in your `$PATH` variable, then you'll have to specify the full path to the Goblocks binary. 69 | 70 | You can reload Goblocks' configuration without restarting i3 by sending the `HUP` signal to Goblocks: 71 | 72 | ```bash 73 | pkill -HUP goblocks 74 | ``` 75 | 76 | You can debug Goblocks' output by running it on the command line. If you set the `debug` [config option](config/goblocks-full.yml) to true, then Goblocks will [pretty-print](https://en.wikipedia.org/wiki/Prettyprint) the JSON output, making it easier to read. 77 | 78 | ### Contributing 79 | 80 | If you would like to see a new feature or enhancement in Goblocks, please feel free to submit an [issue](/../../issues) or [pull request](/../../pulls). 81 | 82 | ### TODO 83 | 84 | * Update README with instructions for adding modules. 85 | -------------------------------------------------------------------------------- /config/goblocks-full.yml: -------------------------------------------------------------------------------- 1 | # Full Goblocks config example with all available block types and options. 2 | 3 | # The global section contains global config options. 4 | 5 | global: 6 | # debug is a boolean indicating whether or not to pretty print the json 7 | # output. Note that this setting requires a restart in order to take 8 | # effect. 9 | debug: False 10 | # refresh_interval is a floating point number indicating the time interval 11 | # in seconds between when Goblocks sends an updated status line to i3bar. 12 | # Basically, it controls Goblocks' "frame rate". This value defaults to 1. 13 | # Note that this value also serves as the default update_interval value for 14 | # each block. 15 | refresh_interval: 1 16 | 17 | # Below is the per-block configuration. The blocks are defined in a YAML array. 18 | 19 | # Each block must have a type field indicating which status indicator to use for 20 | # that block. Different status indicators can have different configuration 21 | # fields. 22 | 23 | # Each block may optionally have an update_interval field indicating the time 24 | # interval in seconds between block updates. This is a floating point number, 25 | # allowing for fractions of a second. Note that this interval does not affect 26 | # the refresh rate of Goblocks' output; it only impacts the timing in which a 27 | # block's info gets updated. If update_interval is omitted, then the 28 | # refresh_interval value in the global section is used as the update_interval. 29 | 30 | # Each block may optionally have a label field indicating a prefix string to 31 | # prepend to the block's status text. 32 | 33 | # If a block has an update_signal field, then that block will be updated and 34 | # Goblocks refreshed when Goblocks receives an RTMIN signal offset by the given 35 | # signal number. See the volume block config. 36 | # For example, if update_signal is 1, then running 'pkill -RTMIN+1 goblocks' 37 | # will cause Goblocks to update the block and refresh. 38 | # The update_signal value must be 1 or greater. 39 | 40 | # Blocks may optionally have a color field that specifies the default color of 41 | # the block text, using the 6 digit hex RGB format (e.g. #00ff00 for green). 42 | 43 | blocks: 44 | # The key block shows statuses of various toggled keys (caps lock, num lock, 45 | # etc.) 46 | - type: key 47 | update_interval: 60 48 | update_signal: 1 49 | # The key field is the key name to search for in the "xset q" output. 50 | key: Caps Lock 51 | # The key-text field is the text to display in the block. 52 | key-text: Caps 53 | # on-color and off-color indicate which colors to use when the given key 54 | # is on or off, respectively. 55 | on-color: "#ff0000" 56 | off-color: "#222222" 57 | 58 | # This block requires a passwordless sudoers entry for the user running 59 | # goblocks. 60 | # E.g. ALL ALL = (root) NOPASSWD: /sbin/zpool 61 | # Also, the zpool utility must be installed and in your $PATH. 62 | - type: zfs 63 | label: "Z: " 64 | zpool_name: "boot" 65 | update_interval: 60 66 | 67 | # Only linux mdraid is supported. 68 | - type: raid 69 | label: "R: " 70 | color: "#00ff00" 71 | 72 | # The disk block is an aggregate status of overall filesystem usage. 73 | - type: disk 74 | label: "D: " 75 | # filesystems is a map of strings to floating point numbers. The strings 76 | # are the filesystems you want to check, and the numbers are the 77 | # critical usage percentages for those filesystems. 78 | filesystems: 79 | /: 90 80 | /home: 90 81 | 82 | - type: load 83 | label: "L: " 84 | crit_load: 4 85 | 86 | # The memory block displays available memory. 87 | - type: memory 88 | label: "M: " 89 | crit_mem: 1 90 | 91 | - type: temperature 92 | # cpu_temp_path is the path to the "hwmon" directory of the CPU 93 | # temperature info. 94 | cpu_temp_path: /sys/devices/platform/coretemp.0/hwmon 95 | crit_temp: 50 96 | 97 | - type: interface 98 | label: "E: " 99 | interface_name: enp3s0 100 | # interface_format is a format string that allows you to specify which 101 | # interface info to display. The syntax for this string is described here: 102 | # https://golang.org/pkg/text/template/ 103 | # These are the supported values: 104 | # * Status - whether the interface is up or down 105 | # * Ipv4Addr - IPv4 address 106 | # * Ipv4Cidr - IPv4 address in CIDR notation 107 | # * Ipv4LocalAddr - link local IPv4 address 108 | # * Ipv4LocalCidr - link local IPv4 address in CIDR notation 109 | # * Ipv6Addr - IPv6 address 110 | # * Ipv6Cidr - IPv6 address in CIDR notation 111 | # * Ipv6LocalAddr - link local IPv6 address 112 | # * Ipv6LocalCidr - link local IPv6 address in CIDR notation 113 | # The default value for interface_format is "{{.Status}}" 114 | interface_format: "{{.Status}}, {{.Ipv4Cidr}}" 115 | 116 | - type: interface 117 | label: "W: " 118 | interface_name: wlp4s2 119 | 120 | - type: wifi 121 | label: "W: " 122 | interface_name: wlp3s0 123 | crit_quality: 30 124 | 125 | - type: battery 126 | label: "B0: " 127 | # The battery number can be found in /sys/class/power_supply/ 128 | battery_number: 0 129 | crit_battery: 20 130 | 131 | # The volume block currently supports Pulse or ALSA channels through amixer. 132 | # The amixer utility must be installed and in your $PATH. 133 | - type: volume 134 | update_interval: 60 135 | label: "V: " 136 | update_signal: 1 137 | # mixer_device defines the device name to use. If omitted, the default 138 | # value is "default". Other possible values include "pulse". 139 | mixer_device: default 140 | # channel defines the volume channel to monitor. If omitted, the default 141 | # value is "Master". 142 | channel: Master 143 | 144 | - type: uptime 145 | update_interval: 0.5 146 | label: "U: " 147 | # duration_format is a format string whose syntax is defined here: 148 | # https://github.com/davidscholberg/go-durationfmt#duration-format 149 | duration_format: "%d days, %0h:%0m:%0s" 150 | 151 | - type: time 152 | update_interval: 0.5 153 | # The time_format value must be a string in Go's standard time format 154 | # (see https://golang.org/pkg/time/#pkg-constants). 155 | time_format: 2006-01-02 15:04:05 156 | 157 | # Specify a shell command to display output from. 158 | # Command flags are supported, complex commands with quotes or pipe 159 | # characters should be executed from a script file. 160 | - type: command 161 | label: "$ " 162 | command: 'date -u' 163 | -------------------------------------------------------------------------------- /config/goblocks-screenshot.yml: -------------------------------------------------------------------------------- 1 | global: 2 | debug: False 3 | refresh_interval: 1 4 | 5 | blocks: 6 | - type: uptime 7 | update_interval: 0.5 8 | label: "U: " 9 | duration_format: "%d days, %0h:%0m:%0s" 10 | 11 | - type: raid 12 | label: "R: " 13 | color: "#71b58e" 14 | 15 | - type: disk 16 | label: "D: " 17 | filesystems: 18 | /: 90 19 | /home: 90 20 | 21 | - type: load 22 | label: "L: " 23 | color: "#71b58e" 24 | crit_load: 4 25 | 26 | - type: memory 27 | label: "M: " 28 | crit_mem: 1 29 | 30 | - type: temperature 31 | color: "#71b58e" 32 | cpu_temp_path: /sys/devices/platform/coretemp.0/hwmon 33 | crit_temp: 50 34 | 35 | - type: interface 36 | label: "E: " 37 | interface_name: enp3s0 38 | 39 | - type: volume 40 | label: "V: " 41 | color: "#71b58e" 42 | update_interval: 60 43 | update_signal: 8 44 | mixer_device: default 45 | channel: Master 46 | 47 | - type: time 48 | update_interval: 0.5 49 | time_format: 2006-01-02 15:04:05 50 | -------------------------------------------------------------------------------- /config/screenshots/goblocks-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidscholberg/goblocks/0969f3280668d5ea9c30d6a957799987ae357745/config/screenshots/goblocks-alert.png -------------------------------------------------------------------------------- /config/screenshots/goblocks-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidscholberg/goblocks/0969f3280668d5ea9c30d6a957799987ae357745/config/screenshots/goblocks-normal.png -------------------------------------------------------------------------------- /goblocks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "reflect" 8 | 9 | "github.com/davidscholberg/go-i3barjson" 10 | "github.com/davidscholberg/goblocks/lib/modules" 11 | ) 12 | 13 | func main() { 14 | errLogger := log.New(os.Stderr, "error: ", 0) 15 | 16 | gb, err := modules.NewGoblocks() 17 | if err != nil { 18 | errLogger.Fatalln(err) 19 | } 20 | 21 | h := i3barjson.Header{Version: 1} 22 | err = i3barjson.Init(os.Stdout, nil, h, gb.Cfg.Global.Debug) 23 | if err != nil { 24 | errLogger.Fatalln(err) 25 | } 26 | 27 | // send the first statusLine 28 | err = i3barjson.Update(gb.StatusLine) 29 | if err != nil { 30 | errLogger.Fatalln(err) 31 | } 32 | 33 | shouldRefresh := false 34 | 35 | for { 36 | // select on all chans 37 | i, _, _ := reflect.Select(gb.SelectCases.Cases) 38 | selectReturn := gb.SelectCases.Actions[i](gb.SelectCases.Blocks[i]) 39 | if selectReturn.Exit { 40 | fmt.Println("") 41 | break 42 | } 43 | if selectReturn.SignalRefresh { 44 | shouldRefresh = true 45 | } else if selectReturn.Refresh { 46 | if shouldRefresh { 47 | err = i3barjson.Update(gb.StatusLine) 48 | if err != nil { 49 | errLogger.Fatalln(err) 50 | } 51 | shouldRefresh = false 52 | } 53 | } else if selectReturn.ForceRefresh { 54 | err = i3barjson.Update(gb.StatusLine) 55 | if err != nil { 56 | errLogger.Fatalln(err) 57 | } 58 | shouldRefresh = false 59 | } else if selectReturn.Reload { 60 | gb.Reset() 61 | gb, err = modules.NewGoblocks() 62 | if err != nil { 63 | errLogger.Fatalln(err) 64 | } 65 | err = i3barjson.Update(gb.StatusLine) 66 | if err != nil { 67 | errLogger.Fatalln(err) 68 | } 69 | shouldRefresh = false 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/modules/battery.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "os" 7 | ) 8 | 9 | // Battery represents the configuration for the battery block. 10 | type Battery struct { 11 | BlockConfigBase `yaml:",inline"` 12 | BatteryNumber int `yaml:"battery_number"` 13 | CritBattery float64 `yaml:"crit_battery"` 14 | } 15 | 16 | // UpdateBlock updates the battery status block. 17 | func (c Battery) UpdateBlock(b *i3barjson.Block) { 18 | b.Color = c.Color 19 | fullTextFmt := fmt.Sprintf("%s%%d%%%%", c.Label) 20 | var capacity int 21 | sysFilePath := fmt.Sprintf("/sys/class/power_supply/BAT%d/capacity", c.BatteryNumber) 22 | r, err := os.Open(sysFilePath) 23 | if err != nil { 24 | b.Urgent = true 25 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 26 | return 27 | } 28 | defer r.Close() 29 | _, err = fmt.Fscanf(r, "%d", &capacity) 30 | if err != nil { 31 | b.Urgent = true 32 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 33 | return 34 | } 35 | if float64(capacity) >= c.CritBattery { 36 | b.Urgent = false 37 | } else { 38 | b.Urgent = true 39 | } 40 | b.FullText = fmt.Sprintf(fullTextFmt, capacity) 41 | } 42 | -------------------------------------------------------------------------------- /lib/modules/command.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | 8 | i3barjson "github.com/davidscholberg/go-i3barjson" 9 | ) 10 | 11 | type Command struct { 12 | BlockConfigBase `yaml:",inline"` 13 | Cmd string `yaml:"command"` 14 | } 15 | 16 | func (c Command) UpdateBlock(b *i3barjson.Block) { 17 | var cmdOutput []byte 18 | b.Color = c.Color 19 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 20 | 21 | cmdParse := strings.Fields(c.Cmd) 22 | cmd, args := cmdParse[0], cmdParse[1:] 23 | 24 | cmdOutput, err := exec.Command(cmd, args...).Output() 25 | if err != nil { 26 | b.Urgent = true 27 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 28 | return 29 | } 30 | 31 | b.FullText = fmt.Sprintf(fullTextFmt, strings.TrimSpace(string(cmdOutput))) 32 | } 33 | -------------------------------------------------------------------------------- /lib/modules/configure.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "os/signal" 8 | "reflect" 9 | "syscall" 10 | "time" 11 | 12 | "github.com/davidscholberg/go-i3barjson" 13 | "gopkg.in/yaml.v2" 14 | ) 15 | 16 | // Block contains all functions and objects necessary to configure and update 17 | // a single status block. 18 | type Block struct { 19 | I3barBlock i3barjson.Block 20 | Config BlockConfig 21 | } 22 | 23 | // PreConfig is the struct used to initially unmarshal the configuration. Once 24 | // the configuration has been fully processed, it is stored in the Config 25 | // struct. 26 | type PreConfig struct { 27 | Global GlobalConfig `yaml:"global"` 28 | Blocks []map[string]interface{} `yaml:"blocks"` 29 | } 30 | 31 | // Config is the root configuration struct. 32 | type Config struct { 33 | Global GlobalConfig 34 | Blocks []BlockConfig 35 | } 36 | 37 | // GlobalConfig represents global config options. 38 | type GlobalConfig struct { 39 | Debug bool `yaml:"debug"` 40 | RefreshInterval float64 `yaml:"refresh_interval"` 41 | } 42 | 43 | // BlockConfig is an interface for Block configuration structs. 44 | type BlockConfig interface { 45 | GetUpdateInterval() float64 46 | GetUpdateSignal() int 47 | GetBlockType() string 48 | UpdateBlock(b *i3barjson.Block) 49 | } 50 | 51 | // BlockConfigBase is a base struct for Block configuration structs. It 52 | // implements all of the methods of the BlockConfig interface except the 53 | // UpdateBlock method. That method should be implemented by each Block 54 | // configuration struct, which should also embed the BlockConfigBase struct as 55 | // an anonymous field. That way, each Block configuration struct will implement 56 | // the full BlockConfig interface. 57 | type BlockConfigBase struct { 58 | Type string `yaml:"type"` 59 | UpdateInterval float64 `yaml:"update_interval"` 60 | Label string `yaml:"label"` 61 | Color string `yaml:"color"` 62 | UpdateSignal int `yaml:"update_signal"` 63 | } 64 | 65 | // GetUpdateInterval returns the block's update interval in seconds. 66 | func (c BlockConfigBase) GetUpdateInterval() float64 { 67 | return c.UpdateInterval 68 | } 69 | 70 | // GetUpdateSignal returns the block's update signal that forces an update and 71 | // refresh. 72 | func (c BlockConfigBase) GetUpdateSignal() int { 73 | return c.UpdateSignal 74 | } 75 | 76 | // GetBlockType returns the block's type as a string. 77 | func (c BlockConfigBase) GetBlockType() string { 78 | return c.Type 79 | } 80 | 81 | // getBlockConfigInstance returns a BlockConfig object whose underlying type is 82 | // determined from the passed-in config map. 83 | func getBlockConfigInstance(m map[string]interface{}) (*BlockConfig, error) { 84 | yamlStr, err := yaml.Marshal(m) 85 | if err != nil { 86 | return nil, err 87 | } 88 | t := m["type"].(string) 89 | switch t { 90 | case "battery": 91 | c := Battery{} 92 | err := yaml.Unmarshal(yamlStr, &c) 93 | b := BlockConfig(c) 94 | return &b, err 95 | case "disk": 96 | c := Disk{} 97 | err := yaml.Unmarshal(yamlStr, &c) 98 | b := BlockConfig(c) 99 | return &b, err 100 | case "interface": 101 | c := Interface{} 102 | err := yaml.Unmarshal(yamlStr, &c) 103 | b := BlockConfig(c) 104 | return &b, err 105 | case "key": 106 | c := KeyIndicator{} 107 | err := yaml.Unmarshal(yamlStr, &c) 108 | b := BlockConfig(c) 109 | return &b, err 110 | case "load": 111 | c := Load{} 112 | err := yaml.Unmarshal(yamlStr, &c) 113 | b := BlockConfig(c) 114 | return &b, err 115 | case "memory": 116 | c := Memory{} 117 | err := yaml.Unmarshal(yamlStr, &c) 118 | b := BlockConfig(c) 119 | return &b, err 120 | case "raid": 121 | c := Raid{} 122 | err := yaml.Unmarshal(yamlStr, &c) 123 | b := BlockConfig(c) 124 | return &b, err 125 | case "temperature": 126 | c := Temperature{} 127 | err := yaml.Unmarshal(yamlStr, &c) 128 | b := BlockConfig(c) 129 | return &b, err 130 | case "time": 131 | c := Time{} 132 | err := yaml.Unmarshal(yamlStr, &c) 133 | b := BlockConfig(c) 134 | return &b, err 135 | case "uptime": 136 | c := Uptime{} 137 | err := yaml.Unmarshal(yamlStr, &c) 138 | b := BlockConfig(c) 139 | return &b, err 140 | case "volume": 141 | c := Volume{} 142 | err := yaml.Unmarshal(yamlStr, &c) 143 | b := BlockConfig(c) 144 | return &b, err 145 | case "wifi": 146 | c := Wifi{} 147 | err := yaml.Unmarshal(yamlStr, &c) 148 | b := BlockConfig(c) 149 | return &b, err 150 | case "zfs": 151 | c := Zfs{} 152 | err := yaml.Unmarshal(yamlStr, &c) 153 | b := BlockConfig(c) 154 | return &b, err 155 | case "command": 156 | c := Command{} 157 | err := yaml.Unmarshal(yamlStr, &c) 158 | b := BlockConfig(c) 159 | return &b, err 160 | } 161 | 162 | return nil, fmt.Errorf("type %s not valid", t) 163 | } 164 | 165 | const confPathFmt = "%s/.config/goblocks/goblocks.yml" 166 | 167 | // GetConfig loads the Goblocks configuration object. 168 | func GetConfig(cfg *Config) error { 169 | // TODO: set up default values 170 | confPath := fmt.Sprintf(confPathFmt, os.Getenv("HOME")) 171 | confStr, err := ioutil.ReadFile(confPath) 172 | if err != nil { 173 | return err 174 | } 175 | 176 | preCfg := PreConfig{} 177 | err = yaml.Unmarshal(confStr, &preCfg) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | cfg.Global = preCfg.Global 183 | 184 | for _, m := range preCfg.Blocks { 185 | block, err := getBlockConfigInstance(m) 186 | if err != nil { 187 | return err 188 | } 189 | cfg.Blocks = append(cfg.Blocks, *block) 190 | } 191 | 192 | return nil 193 | } 194 | 195 | // GetBlocks initializes and returns a Block slice based on the 196 | // given configuration. 197 | func GetBlocks(blockConfigSlice []BlockConfig) ([]*Block, error) { 198 | blocks := make([]*Block, len(blockConfigSlice)) 199 | for i, blockConfig := range blockConfigSlice { 200 | blocks[i] = &Block{ 201 | i3barjson.Block{Separator: true, SeparatorBlockWidth: 20}, 202 | blockConfig, 203 | } 204 | } 205 | 206 | return blocks, nil 207 | } 208 | 209 | // SelectCases represents the set of channels that Goblocks selects on in the 210 | // main program loop, as well as the functions and data to run and operate on, 211 | // respectively. 212 | type SelectCases struct { 213 | Cases []reflect.SelectCase 214 | Actions []SelectAction 215 | Blocks []*Block 216 | } 217 | 218 | const sigrtmin = syscall.Signal(34) 219 | 220 | // AddSignalSelectCases loads the select cases related to OS signals. 221 | func (s *SelectCases) AddSignalSelectCases(blocks []*Block) { 222 | sigReloadChan := make(chan os.Signal, 1) 223 | signal.Notify(sigReloadChan, syscall.SIGHUP) 224 | s.addChanSelectCase( 225 | sigReloadChan, 226 | SelectActionReload, 227 | ) 228 | 229 | sigEndChan := make(chan os.Signal, 1) 230 | signal.Notify(sigEndChan, syscall.SIGINT, syscall.SIGTERM) 231 | s.addChanSelectCase( 232 | sigEndChan, 233 | SelectActionExit, 234 | ) 235 | 236 | for _, block := range blocks { 237 | updateSignal := block.Config.GetUpdateSignal() 238 | if updateSignal > 0 { 239 | sigUpdateChan := make(chan os.Signal, 1) 240 | signal.Notify(sigUpdateChan, sigrtmin+syscall.Signal(updateSignal)) 241 | s.add( 242 | sigUpdateChan, 243 | func(b *Block) SelectReturn { 244 | b.Config.UpdateBlock(&b.I3barBlock) 245 | return SelectActionForceRefresh(b) 246 | }, 247 | block, 248 | ) 249 | 250 | } 251 | } 252 | } 253 | 254 | // add adds a channel, action, and Block to the SelectCases object. 255 | func (s *SelectCases) add(c interface{}, a SelectAction, b *Block) { 256 | selectCase := reflect.SelectCase{ 257 | Dir: reflect.SelectRecv, 258 | Chan: reflect.ValueOf(c), 259 | } 260 | s.Cases = append(s.Cases, selectCase) 261 | s.Actions = append(s.Actions, a) 262 | s.Blocks = append(s.Blocks, b) 263 | } 264 | 265 | // addChanSelectCase is a helper function that adds a non-Block channel and 266 | // action to SelectCases. This can be used for signal handling and other non- 267 | // block specific operations. 268 | func (s *SelectCases) addChanSelectCase(c interface{}, a SelectAction) { 269 | s.add( 270 | c, 271 | a, 272 | nil, 273 | ) 274 | } 275 | 276 | // addBlockToSelectCase is a helper function to add a Block to SelectCases. 277 | // The channel used is a time.Ticker channel set to tick according to the 278 | // block's configuration. The SelectAction function updates the block's status 279 | // and tells Goblocks that a refresh should occur at the next refresh interval 280 | // tick. 281 | func addBlockToSelectCase(s *SelectCases, b *Block, c <-chan time.Time) { 282 | s.add( 283 | c, 284 | func(b *Block) SelectReturn { 285 | b.Config.UpdateBlock(&b.I3barBlock) 286 | return SelectActionSignalRefresh(b) 287 | }, 288 | b, 289 | ) 290 | } 291 | 292 | // SelectAction is a function type that performs an action when a channel is 293 | // selected on in the main program loop. The return value indicates some action 294 | // for the caller to take. 295 | type SelectAction func(*Block) SelectReturn 296 | 297 | // SelectReturn is returned by a SelectAction type function and tells the caller 298 | // a certain action to take. 299 | type SelectReturn struct { 300 | Exit bool 301 | ForceRefresh bool 302 | Refresh bool 303 | Reload bool 304 | SignalRefresh bool 305 | } 306 | 307 | // SelectActionExit is a helper function of type SelectAction that tells 308 | // Goblocks to exit. 309 | func SelectActionExit(b *Block) SelectReturn { 310 | return SelectReturn{Exit: true} 311 | } 312 | 313 | // SelectActionForceRefresh is a helper function of type SelectAction that tells 314 | // Goblocks to immediately refresh the output. This differs from 315 | // SelectActionRefresh in that a refresh is performed regardless of whether 316 | // SelectActionSignalRefresh has been called. 317 | func SelectActionForceRefresh(b *Block) SelectReturn { 318 | return SelectReturn{ForceRefresh: true} 319 | } 320 | 321 | // SelectActionRefresh is a helper function of type SelectAction that tells 322 | // Goblocks to refresh the output. Note that the output is only refreshed if 323 | // SelectActionSignalRefresh was returned at least once since the last refresh 324 | // interval tick. This prevents needlessly refreshing the output when nothing 325 | // changed. 326 | func SelectActionRefresh(b *Block) SelectReturn { 327 | return SelectReturn{Refresh: true} 328 | } 329 | 330 | // SelectActionReload is a helper function of type SelectAction that tells 331 | // Goblocks to reload the configuration. 332 | func SelectActionReload(b *Block) SelectReturn { 333 | return SelectReturn{Reload: true} 334 | } 335 | 336 | // SelectActionSignalRefresh is a helper function of type SelectAction that 337 | // tells Goblocks to signal the refresher that a refresh should be performed. 338 | // The actual refresh won't be performed until the refresh interval timer fires 339 | // again. 340 | func SelectActionSignalRefresh(b *Block) SelectReturn { 341 | return SelectReturn{SignalRefresh: true} 342 | } 343 | 344 | // Goblocks contains all configuration and runtime data needed for the 345 | // application. 346 | type Goblocks struct { 347 | Cfg Config 348 | SelectCases SelectCases 349 | Tickers []*time.Ticker 350 | StatusLine i3barjson.StatusLine 351 | } 352 | 353 | // NewGoblocks returns a Goblocks instance with all configuration and runtime 354 | // data loaded in. 355 | func NewGoblocks() (*Goblocks, error) { 356 | gb := Goblocks{} 357 | err := GetConfig(&gb.Cfg) 358 | if err != nil { 359 | return nil, err 360 | } 361 | 362 | // set config defaults 363 | if gb.Cfg.Global.RefreshInterval == 0 { 364 | gb.Cfg.Global.RefreshInterval = 1 365 | } 366 | 367 | blocks, err := GetBlocks(gb.Cfg.Blocks) 368 | if err != nil { 369 | return nil, err 370 | } 371 | 372 | gb.SelectCases.AddSignalSelectCases(blocks) 373 | gb.AddBlockSelectCases(blocks) 374 | gb.AddUpdateTickerSelectCase() 375 | 376 | for _, block := range blocks { 377 | gb.StatusLine = append(gb.StatusLine, &block.I3barBlock) 378 | // update block so it's ready for first run 379 | block.Config.UpdateBlock(&block.I3barBlock) 380 | } 381 | 382 | return &gb, nil 383 | } 384 | 385 | // AddBlockSelectCases is a helper function to add all configured Block 386 | // objects to Goblocks' SelectCases. 387 | func (gb *Goblocks) AddBlockSelectCases(b []*Block) { 388 | for _, block := range b { 389 | blockUpdateInterval := block.Config.GetUpdateInterval() 390 | if blockUpdateInterval == 0 { 391 | blockUpdateInterval = gb.Cfg.Global.RefreshInterval 392 | } 393 | ticker := time.NewTicker( 394 | time.Duration( 395 | blockUpdateInterval * float64(time.Second), 396 | ), 397 | ) 398 | gb.Tickers = append(gb.Tickers, ticker) 399 | addBlockToSelectCase(&gb.SelectCases, block, ticker.C) 400 | } 401 | } 402 | 403 | // AddUpdateTickerSelectCase adds the Goblocks update ticker that controls 404 | // refreshing the Goblocks output. 405 | func (gb *Goblocks) AddUpdateTickerSelectCase() { 406 | updateTicker := time.NewTicker( 407 | time.Duration(gb.Cfg.Global.RefreshInterval * float64(time.Second)), 408 | ) 409 | gb.SelectCases.addChanSelectCase( 410 | updateTicker.C, 411 | SelectActionRefresh, 412 | ) 413 | gb.Tickers = append(gb.Tickers, updateTicker) 414 | } 415 | 416 | // Reset stops all tickers and resets all signal handlers. 417 | func (gb *Goblocks) Reset() { 418 | for _, ticker := range gb.Tickers { 419 | ticker.Stop() 420 | } 421 | signal.Reset() 422 | } 423 | -------------------------------------------------------------------------------- /lib/modules/disk.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "syscall" 7 | ) 8 | 9 | // Disk represents the configuration for the disk block. 10 | type Disk struct { 11 | BlockConfigBase `yaml:",inline"` 12 | Filesystems map[string]float64 `yaml:"filesystems"` 13 | } 14 | 15 | // UpdateBlock updates the status of the disk block. 16 | // The block displays "ok" unless one of the given filesystems are over 95%. 17 | func (c Disk) UpdateBlock(b *i3barjson.Block) { 18 | b.Color = c.Color 19 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 20 | for fsPath, critPercent := range c.Filesystems { 21 | stats := syscall.Statfs_t{} 22 | err := syscall.Statfs(fsPath, &stats) 23 | if err != nil { 24 | b.Urgent = true 25 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 26 | return 27 | } 28 | percentUsed := 100 - (float64(stats.Bavail) * 100 / float64(stats.Blocks)) 29 | if percentUsed >= critPercent { 30 | b.Urgent = true 31 | b.FullText = fmt.Sprintf( 32 | fullTextFmt, 33 | fmt.Sprintf( 34 | "%s at %.2f%%", 35 | fsPath, 36 | percentUsed, 37 | ), 38 | ) 39 | return 40 | } 41 | } 42 | b.Urgent = false 43 | b.FullText = fmt.Sprintf(fullTextFmt, "ok") 44 | } 45 | -------------------------------------------------------------------------------- /lib/modules/iface.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/davidscholberg/go-i3barjson" 7 | "net" 8 | "text/template" 9 | ) 10 | 11 | // Interface represents the configuration for the network interface block. 12 | type Interface struct { 13 | BlockConfigBase `yaml:",inline"` 14 | IfaceName string `yaml:"interface_name"` 15 | IfaceFormat string `yaml:"interface_format"` 16 | } 17 | 18 | // ifaceInfo contains the status info for the interface being monitored. 19 | // The field names correspond directly to the template fields in 20 | // Interface.IfaceFormat. 21 | type ifaceInfo struct { 22 | Status string 23 | Ipv4Addr string 24 | Ipv4Cidr string 25 | Ipv4LocalAddr string 26 | Ipv4LocalCidr string 27 | Ipv6Addr string 28 | Ipv6Cidr string 29 | Ipv6LocalAddr string 30 | Ipv6LocalCidr string 31 | } 32 | 33 | // UpdateBlock updates the network interface block. 34 | func (c Interface) UpdateBlock(b *i3barjson.Block) { 35 | var info ifaceInfo 36 | 37 | b.Color = c.Color 38 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 39 | 40 | // set default interface_format for backwards compat 41 | if c.IfaceFormat == "" { 42 | c.IfaceFormat = "{{.Status}}" 43 | } 44 | 45 | iface, err := net.InterfaceByName(c.IfaceName) 46 | if err != nil { 47 | b.Urgent = true 48 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 49 | return 50 | } 51 | 52 | if iface.Flags&net.FlagUp != 0 { 53 | b.Urgent = false 54 | info.Status = "up" 55 | } else { 56 | b.Urgent = true 57 | info.Status = "down" 58 | } 59 | 60 | addrs, err := iface.Addrs() 61 | if err != nil { 62 | b.Urgent = true 63 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 64 | return 65 | } 66 | 67 | for _, addr := range addrs { 68 | ip, _, err := net.ParseCIDR(addr.String()) 69 | if err != nil { 70 | b.Urgent = true 71 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 72 | return 73 | } 74 | 75 | // Checking for address family 76 | if ip.To4() != nil { 77 | if ip.IsLinkLocalUnicast() { 78 | info.Ipv4LocalAddr = ip.String() 79 | info.Ipv4LocalCidr = addr.String() 80 | } else { 81 | info.Ipv4Addr = ip.String() 82 | info.Ipv4Cidr = addr.String() 83 | } 84 | } else { 85 | if ip.IsLinkLocalUnicast() { 86 | info.Ipv6LocalAddr = ip.String() 87 | info.Ipv6LocalCidr = addr.String() 88 | } else { 89 | info.Ipv6Addr = ip.String() 90 | info.Ipv6Cidr = addr.String() 91 | } 92 | } 93 | } 94 | 95 | t, err := template.New("iface").Parse(c.IfaceFormat) 96 | if err != nil { 97 | b.Urgent = true 98 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 99 | return 100 | } 101 | 102 | buf := new(bytes.Buffer) 103 | err = t.Execute(buf, info) 104 | if err != nil { 105 | b.Urgent = true 106 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 107 | return 108 | } 109 | 110 | b.FullText = fmt.Sprintf(fullTextFmt, buf.String()) 111 | } 112 | -------------------------------------------------------------------------------- /lib/modules/keyindicator.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // KeyIndicator represents the configuration for the key indicator block. 11 | type KeyIndicator struct { 12 | BlockConfigBase `yaml:",inline"` 13 | Key string `yaml:"key"` 14 | KeyText string `yaml:"key-text"` 15 | OnColor string `yaml:"on-color"` 16 | OffColor string `yaml:"off-color"` 17 | } 18 | 19 | // UpdateBlock updates the key indicator block's status. 20 | func (c KeyIndicator) UpdateBlock(b *i3barjson.Block) { 21 | b.Color = c.Color 22 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 23 | xsetCmd := "xset" 24 | xsetArgs := []string{"q"} 25 | out, err := exec.Command(xsetCmd, xsetArgs...).Output() 26 | if err != nil { 27 | b.Urgent = true 28 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 29 | return 30 | } 31 | 32 | keyFound := false 33 | keyStatus := false 34 | xsetLines := strings.Split(string(out), "\n") 35 | for _, xsetLine := range xsetLines { 36 | keyIndex := strings.Index(xsetLine, c.Key) 37 | if keyIndex == -1 { 38 | continue 39 | } 40 | xsetLineSubstr := xsetLine[keyIndex+len(c.Key):] 41 | keyStatusIndex := strings.Index(xsetLineSubstr, "o") 42 | if keyStatusIndex == -1 { 43 | b.Urgent = true 44 | b.FullText = fmt.Sprintf( 45 | fullTextFmt, 46 | fmt.Sprintf( 47 | "couldn't find status for key '%s'", 48 | c.Key, 49 | ), 50 | ) 51 | return 52 | } 53 | switch xsetLineSubstr[keyStatusIndex+1 : keyStatusIndex+2] { 54 | case "n": 55 | keyFound = true 56 | keyStatus = true 57 | case "f": 58 | keyFound = true 59 | keyStatus = false 60 | default: 61 | b.Urgent = true 62 | b.FullText = fmt.Sprintf( 63 | fullTextFmt, 64 | fmt.Sprintf( 65 | "unknown status for key '%s'", 66 | c.Key, 67 | ), 68 | ) 69 | return 70 | } 71 | if keyFound { 72 | break 73 | } 74 | } 75 | 76 | if !keyFound { 77 | b.Urgent = true 78 | b.FullText = fmt.Sprintf( 79 | fullTextFmt, 80 | fmt.Sprintf( 81 | "couldn't find key '%s'", 82 | c.Key, 83 | ), 84 | ) 85 | return 86 | } 87 | 88 | b.Urgent = false 89 | 90 | if keyStatus { 91 | b.Color = c.OnColor 92 | } else { 93 | b.Color = c.OffColor 94 | } 95 | 96 | b.FullText = fmt.Sprintf(fullTextFmt, c.KeyText) 97 | } 98 | -------------------------------------------------------------------------------- /lib/modules/load.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "os" 7 | ) 8 | 9 | // Load represents the configuration for the system load. 10 | type Load struct { 11 | BlockConfigBase `yaml:",inline"` 12 | CritLoad float64 `yaml:"crit_load"` 13 | } 14 | 15 | // UpdateBlock updates the load block status. 16 | func (c Load) UpdateBlock(b *i3barjson.Block) { 17 | b.Color = c.Color 18 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 19 | var load float64 20 | r, err := os.Open("/proc/loadavg") 21 | if err != nil { 22 | b.Urgent = true 23 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 24 | return 25 | } 26 | defer r.Close() 27 | _, err = fmt.Fscanf(r, "%f ", &load) 28 | if err != nil { 29 | b.Urgent = true 30 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 31 | return 32 | } 33 | if load >= c.CritLoad { 34 | b.Urgent = true 35 | } else { 36 | b.Urgent = false 37 | } 38 | b.FullText = fmt.Sprintf("%s%.2f", c.Label, load) 39 | } 40 | -------------------------------------------------------------------------------- /lib/modules/memory.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "os" 7 | ) 8 | 9 | // Memory represents the configuration for the memory block. 10 | type Memory struct { 11 | BlockConfigBase `yaml:",inline"` 12 | CritMem float64 `yaml:"crit_mem"` 13 | } 14 | 15 | // UpdateBlock updates the system memory block status. 16 | // The value dispayed is the amount of available memory. 17 | func (c Memory) UpdateBlock(b *i3barjson.Block) { 18 | b.Color = c.Color 19 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 20 | var memAvail, memJunk int64 21 | r, err := os.Open("/proc/meminfo") 22 | if err != nil { 23 | b.Urgent = true 24 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 25 | return 26 | } 27 | defer r.Close() 28 | _, err = fmt.Fscanf( 29 | r, 30 | "MemTotal: %d kB\nMemFree: %d kB\nMemAvailable: %d ", 31 | &memJunk, &memJunk, &memAvail) 32 | if err != nil { 33 | b.Urgent = true 34 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 35 | return 36 | } 37 | memAvailG := float64(memAvail) / 1048576.0 38 | if memAvailG < c.CritMem { 39 | b.Urgent = true 40 | } else { 41 | b.Urgent = false 42 | } 43 | b.FullText = fmt.Sprintf(fullTextFmt, fmt.Sprintf("%.2fG", memAvailG)) 44 | } 45 | -------------------------------------------------------------------------------- /lib/modules/raid.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "io/ioutil" 7 | "strings" 8 | ) 9 | 10 | // Raid represents the configuration for the RAID block. 11 | type Raid struct { 12 | BlockConfigBase `yaml:",inline"` 13 | } 14 | 15 | // UpdateBlock updates the RAID block's status. 16 | // This block only supports linux mdraid, and alerts if any RAID volume on the 17 | // system is degraded. 18 | func (c Raid) UpdateBlock(b *i3barjson.Block) { 19 | b.Color = c.Color 20 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 21 | mdstatPath := "/proc/mdstat" 22 | stats, err := ioutil.ReadFile(mdstatPath) 23 | if err != nil { 24 | b.Urgent = true 25 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 26 | return 27 | } 28 | i := strings.Index(string(stats), "_") 29 | if i != -1 { 30 | b.Urgent = true 31 | b.FullText = fmt.Sprintf(fullTextFmt, "degraded") 32 | return 33 | } 34 | b.Urgent = false 35 | b.FullText = fmt.Sprintf(fullTextFmt, "ok") 36 | } 37 | -------------------------------------------------------------------------------- /lib/modules/temperature.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // Temperature represents the configuration for the CPU temperature block. 12 | // CpuTempPath is the path to the "hwmon" directory of the CPU temperature info. 13 | // e.g. /sys/devices/platform/coretemp.0/hwmon 14 | type Temperature struct { 15 | BlockConfigBase `yaml:",inline"` 16 | CpuTempPath string `yaml:"cpu_temp_path"` 17 | CritTemp float64 `yaml:"crit_temp"` 18 | } 19 | 20 | // UpdateBlock updates the CPU temperature info. 21 | // The value output by the block is the average temperature of all cores. 22 | func (c Temperature) UpdateBlock(b *i3barjson.Block) { 23 | b.Color = c.Color 24 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 25 | totalTemp := 0 26 | procs := 0 27 | sysFileDirList, err := ioutil.ReadDir(c.CpuTempPath) 28 | if err != nil { 29 | b.Urgent = true 30 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 31 | return 32 | } 33 | if len(sysFileDirList) != 1 { 34 | b.Urgent = true 35 | msg := fmt.Sprintf( 36 | "in %s, expected 1 file, got %d", 37 | c.CpuTempPath, 38 | len(sysFileDirList), 39 | ) 40 | b.FullText = fmt.Sprintf(fullTextFmt, msg) 41 | return 42 | } 43 | sysFileDirPath := fmt.Sprintf( 44 | "%s/%s", 45 | c.CpuTempPath, 46 | sysFileDirList[0].Name(), 47 | ) 48 | sysFileNameFmt := fmt.Sprintf("%s/%%s", sysFileDirPath) 49 | sysFiles, err := ioutil.ReadDir(sysFileDirPath) 50 | if err != nil { 51 | b.Urgent = true 52 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 53 | return 54 | } 55 | for _, sysFile := range sysFiles { 56 | sysFileName := sysFile.Name() 57 | if !strings.HasSuffix(sysFileName, "input") { 58 | continue 59 | } 60 | r, err := os.Open(fmt.Sprintf(sysFileNameFmt, sysFileName)) 61 | if err != nil { 62 | b.Urgent = true 63 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 64 | return 65 | } 66 | defer r.Close() 67 | var temp int 68 | _, err = fmt.Fscanf(r, "%d", &temp) 69 | if err != nil { 70 | b.Urgent = true 71 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 72 | return 73 | } 74 | totalTemp += temp 75 | procs++ 76 | } 77 | avgTemp := float64(totalTemp) / float64(procs*1000) 78 | if avgTemp >= c.CritTemp { 79 | b.Urgent = true 80 | } else { 81 | b.Urgent = false 82 | } 83 | b.FullText = fmt.Sprintf("%s%.2f°C", c.Label, avgTemp) 84 | } 85 | -------------------------------------------------------------------------------- /lib/modules/time.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "time" 7 | ) 8 | 9 | // Time represents the configuration for the time display block. 10 | type Time struct { 11 | BlockConfigBase `yaml:",inline"` 12 | TimeFormat string `yaml:"time_format"` 13 | } 14 | 15 | // UpdateBlock updates the time display block. 16 | func (c Time) UpdateBlock(b *i3barjson.Block) { 17 | b.Color = c.Color 18 | b.FullText = fmt.Sprintf( 19 | "%s%s", 20 | c.Label, 21 | time.Now().Format(c.TimeFormat), 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /lib/modules/uptime.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-durationfmt" 6 | "github.com/davidscholberg/go-i3barjson" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // Uptime represents the configuration for the time display block. 12 | type Uptime struct { 13 | BlockConfigBase `yaml:",inline"` 14 | DurationFormat string `yaml:"duration_format"` 15 | } 16 | 17 | // UpdateBlock updates the time display block. 18 | func (c Uptime) UpdateBlock(b *i3barjson.Block) { 19 | b.Color = c.Color 20 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 21 | var load float64 22 | r, err := os.Open("/proc/uptime") 23 | if err != nil { 24 | b.Urgent = true 25 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 26 | return 27 | } 28 | defer r.Close() 29 | _, err = fmt.Fscanf(r, "%f ", &load) 30 | if err != nil { 31 | b.Urgent = true 32 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 33 | return 34 | } 35 | dur := time.Duration(load) * time.Second 36 | durFmt := c.DurationFormat 37 | if durFmt == "" { 38 | durFmt = "%hh%mm%ss" 39 | } 40 | durStr, err := durationfmt.Format(dur, durFmt) 41 | if err != nil { 42 | b.Urgent = true 43 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 44 | return 45 | } 46 | b.FullText = fmt.Sprintf( 47 | "%s%s", 48 | c.Label, 49 | durStr, 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /lib/modules/volume.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // Volume represents the configuration for the volume display block. 11 | type Volume struct { 12 | BlockConfigBase `yaml:",inline"` 13 | MixerDevice string `yaml:"mixer_device"` 14 | Channel string `yaml:"channel"` 15 | } 16 | 17 | // UpdateBlock updates the volume display block. 18 | // Currently, only the ALSA master channel volume is supported. 19 | func (c Volume) UpdateBlock(b *i3barjson.Block) { 20 | b.Color = c.Color 21 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 22 | amixerCmd := "amixer" 23 | if c.MixerDevice == "" { 24 | c.MixerDevice = "default" 25 | } 26 | if c.Channel == "" { 27 | c.Channel = "Master" 28 | } 29 | amixerArgs := []string{"-D", c.MixerDevice, "get", c.Channel} 30 | out, err := exec.Command(amixerCmd, amixerArgs...).Output() 31 | if err != nil { 32 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 33 | return 34 | } 35 | outStr := string(out) 36 | iBegin := strings.Index(outStr, "[") 37 | if iBegin == -1 { 38 | b.FullText = fmt.Sprintf(fullTextFmt, "cannot parse amixer output") 39 | return 40 | } 41 | iEnd := strings.Index(outStr, "]") 42 | if iEnd == -1 { 43 | b.FullText = fmt.Sprintf(fullTextFmt, "cannot parse amixer output") 44 | return 45 | } 46 | b.FullText = fmt.Sprintf(fullTextFmt, outStr[iBegin+1:iEnd]) 47 | } 48 | -------------------------------------------------------------------------------- /lib/modules/wifi.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "io/ioutil" 7 | "strings" 8 | ) 9 | 10 | // Wifi represents the configuration for the wifi percent block. 11 | type Wifi struct { 12 | BlockConfigBase `yaml:",inline"` 13 | IfaceName string `yaml:"interface_name"` 14 | CritQuality float64 `yaml:"crit_quality"` 15 | } 16 | 17 | // UpdateBlock updates the wifi block's status. 18 | func (c Wifi) UpdateBlock(b *i3barjson.Block) { 19 | b.Color = c.Color 20 | fullTextFmt := fmt.Sprintf("%s%%d%%%%", c.Label) 21 | var wifiSignal float64 22 | var ignore string 23 | wifiPath := "/proc/net/wireless" 24 | wifiStats, err := ioutil.ReadFile(wifiPath) 25 | if err != nil { 26 | b.Urgent = true 27 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 28 | return 29 | } 30 | wifiLines := strings.Split(string(wifiStats), "\n") 31 | for _, wifiLine := range wifiLines { 32 | if strings.HasPrefix(wifiLine, c.IfaceName) { 33 | _, err := fmt.Sscan(wifiLine, &ignore, &ignore, &wifiSignal) 34 | if err != nil { 35 | b.Urgent = true 36 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 37 | return 38 | } 39 | break 40 | } 41 | } 42 | wifiPercent := (wifiSignal * 100.0) / 70.0 43 | if wifiPercent > c.CritQuality { 44 | b.Urgent = false 45 | } else { 46 | b.Urgent = true 47 | } 48 | b.FullText = fmt.Sprintf(fullTextFmt, int(wifiPercent)) 49 | } 50 | -------------------------------------------------------------------------------- /lib/modules/zfs.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidscholberg/go-i3barjson" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // Zfs represents the configuration data for the ZFS block 11 | type Zfs struct { 12 | BlockConfigBase `yaml:",inline"` 13 | PoolName string `yaml:"zpool_name"` 14 | } 15 | 16 | // UpdateBlock updates the ZFS block 17 | func (c Zfs) UpdateBlock(b *i3barjson.Block) { 18 | b.Color = c.Color 19 | fullTextFmt := fmt.Sprintf("%s%%s", c.Label) 20 | 21 | zpoolCmd := exec.Command("sudo", "zpool", "status", c.PoolName) 22 | out, err := zpoolCmd.Output() 23 | 24 | if err != nil { 25 | b.Urgent = true 26 | b.FullText = fmt.Sprintf(fullTextFmt, err.Error()) 27 | return 28 | } 29 | 30 | zpoolLines := strings.Split(string(out), "\n") 31 | for _, zpoolLine := range zpoolLines { 32 | line := strings.TrimSpace(zpoolLine) 33 | if strings.HasPrefix(line, "state") { 34 | split := strings.Split(line, ":") 35 | status := strings.TrimSpace(split[1]) 36 | 37 | if status == "ONLINE" { 38 | b.Urgent = false 39 | } else { 40 | b.Urgent = true 41 | } 42 | b.FullText = fmt.Sprintf(fullTextFmt, status) 43 | return 44 | } 45 | } 46 | 47 | b.Urgent = true 48 | b.FullText = fmt.Sprintf(fullTextFmt, "NOT FOUND") 49 | return 50 | } 51 | --------------------------------------------------------------------------------