├── LICENSE ├── README.md ├── config.go └── dwms.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ian Remmler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dwms 2 | Simple status generator for dwm. 3 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | Unknown = "?" 9 | UpdatePeriod = 5 * time.Second 10 | ) 11 | 12 | var Items = []statusFunc{ 13 | netStatus("wlp3s0", "enp0s25"), 14 | batteryStatus("BAT0", "BAT1"), 15 | audioStatus("-M"), 16 | timeStatus, 17 | } 18 | -------------------------------------------------------------------------------- /dwms.go: -------------------------------------------------------------------------------- 1 | // dwms is a dwm status generator. 2 | // 3 | // Assign custom values to exported identifiers in config.go to configure. 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "os" 12 | "os/exec" 13 | "os/signal" 14 | "path/filepath" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | "syscall" 19 | "time" 20 | 21 | "github.com/BurntSushi/xgb" 22 | "github.com/BurntSushi/xgb/xproto" 23 | ) 24 | 25 | type statusFunc func() string 26 | 27 | const ( 28 | battSysPath = "/sys/class/power_supply" 29 | netSysPath = "/sys/class/net" 30 | ) 31 | 32 | var ( 33 | ssidRE = regexp.MustCompile(`SSID:\s+(.*)`) 34 | bitrateRE = regexp.MustCompile(`tx bitrate:\s+(\d+)`) 35 | signalRE = regexp.MustCompile(`signal:\s+(-\d+)`) 36 | amixerRE = regexp.MustCompile(`\[(\d+)%\].*\[(\w+)\]`) 37 | xconn *xgb.Conn 38 | xroot xproto.Window 39 | ) 40 | 41 | var WifiFmt = func(dev, ssid string, bitrate, signal int, up bool) string { 42 | if !up { 43 | return "" 44 | } 45 | return fmt.Sprintf("\\/%s/%d/%d", ssid, bitrate, signal) 46 | } 47 | 48 | var WiredFmt = func(dev string, speed int, up bool) string { 49 | if !up { 50 | return "" 51 | } 52 | return "[=" + strconv.Itoa(speed) 53 | } 54 | 55 | var NetFmt = func(devs []string) string { 56 | return strings.Join(filterEmpty(devs), " ") 57 | } 58 | 59 | var BatteryDevFmt = func(pct int, state string) string { 60 | return strconv.Itoa(pct) + map[string]string{"Charging": "+", "Discharging": "-"}[state] 61 | } 62 | 63 | var BatteryFmt = func(bats []string) string { 64 | return "[]" + strings.Join(bats, "/") 65 | } 66 | 67 | var AudioFmt = func(vol int, muted bool) string { 68 | return map[bool]string{false: "<)", true: "= 2 { 86 | ssid = string(match[1]) 87 | } 88 | if match := bitrateRE.FindSubmatch(out); len(match) >= 2 { 89 | if br, err := strconv.Atoi(string(match[1])); err == nil { 90 | bitrate = br 91 | } 92 | } 93 | if match := signalRE.FindSubmatch(out); len(match) >= 2 { 94 | if sig, err := strconv.Atoi(string(match[1])); err == nil { 95 | signal = sig 96 | } 97 | } 98 | return ssid, bitrate, signal 99 | } 100 | 101 | func wiredStatus(dev string) int { 102 | speed, err := sysfsIntVal(filepath.Join(netSysPath, dev, "speed")) 103 | if err != nil { 104 | return 0 105 | } 106 | return speed 107 | } 108 | 109 | func netDevStatus(dev string) string { 110 | status, err := sysfsStringVal(filepath.Join(netSysPath, dev, "operstate")) 111 | up := err == nil && status == "up" 112 | if _, err = os.Stat(filepath.Join(netSysPath, dev, "wireless")); err == nil { 113 | ssid, bitrate, signal := wifiStatus(dev) 114 | return WifiFmt(dev, ssid, bitrate, signal, up) 115 | } 116 | speed := wiredStatus(dev) 117 | return WiredFmt(dev, speed, up) 118 | } 119 | 120 | func netStatus(devs ...string) statusFunc { 121 | return func() string { 122 | var netStats []string 123 | for _, dev := range devs { 124 | netStats = append(netStats, netDevStatus(dev)) 125 | } 126 | return NetFmt(netStats) 127 | } 128 | } 129 | 130 | func batteryDevStatus(batt string) string { 131 | pct, err := sysfsIntVal(filepath.Join(battSysPath, batt, "capacity")) 132 | if err != nil { 133 | return Unknown 134 | } 135 | status, err := sysfsStringVal(filepath.Join(battSysPath, batt, "status")) 136 | if err != nil { 137 | return Unknown 138 | } 139 | return BatteryDevFmt(pct, status) 140 | } 141 | 142 | func batteryStatus(batts ...string) statusFunc { 143 | return func() string { 144 | var battStats []string 145 | for _, batt := range batts { 146 | battStats = append(battStats, batteryDevStatus(batt)) 147 | } 148 | return BatteryFmt(battStats) 149 | } 150 | } 151 | 152 | func audioStatus(args ...string) statusFunc { 153 | args = append(args, []string{"get", "Master"}...) 154 | return func() string { 155 | out, err := exec.Command("amixer", args...).Output() 156 | if err != nil { 157 | return Unknown 158 | } 159 | match := amixerRE.FindSubmatch(out) 160 | if len(match) < 3 { 161 | return Unknown 162 | } 163 | vol, err := strconv.Atoi(string(match[1])) 164 | if err != nil { 165 | return Unknown 166 | } 167 | muted := (string(match[2]) == "off") 168 | return AudioFmt(vol, muted) 169 | } 170 | } 171 | 172 | func timeStatus() string { 173 | return TimeFmt(time.Now()) 174 | } 175 | 176 | func status() string { 177 | var stats []string 178 | for _, item := range Items { 179 | stats = append(stats, item()) 180 | } 181 | return StatusFmt(stats) 182 | } 183 | 184 | func setStatus(statusText string) { 185 | xproto.ChangeProperty(xconn, xproto.PropModeReplace, xroot, xproto.AtomWmName, 186 | xproto.AtomString, 8, uint32(len(statusText)), []byte(statusText)) 187 | } 188 | 189 | func sysfsIntVal(path string) (int, error) { 190 | data, err := ioutil.ReadFile(path) 191 | if err != nil { 192 | return 0, err 193 | } 194 | val, err := strconv.Atoi(string(bytes.TrimSpace(data))) 195 | if err != nil { 196 | return 0, err 197 | } 198 | return val, nil 199 | } 200 | 201 | func sysfsStringVal(path string) (string, error) { 202 | data, err := ioutil.ReadFile(path) 203 | if err != nil { 204 | return "", err 205 | } 206 | return string(bytes.TrimSpace(data)), nil 207 | } 208 | 209 | func filterEmpty(strings []string) []string { 210 | filtStrings := strings[:0] 211 | for _, str := range strings { 212 | if str != "" { 213 | filtStrings = append(filtStrings, str) 214 | } 215 | } 216 | return filtStrings 217 | } 218 | 219 | func run() { 220 | setStatus(status()) 221 | defer setStatus("") // cleanup 222 | sigs := make(chan os.Signal, 1) 223 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) 224 | update := time.Tick(UpdatePeriod) 225 | for { 226 | select { 227 | case sig := <-sigs: 228 | switch sig { 229 | case syscall.SIGUSR1: 230 | setStatus(status()) 231 | default: 232 | return 233 | } 234 | case <-update: 235 | setStatus(status()) 236 | } 237 | } 238 | } 239 | 240 | func main() { 241 | var err error 242 | xconn, err = xgb.NewConn() 243 | if err != nil { 244 | log.Fatal(err) 245 | } 246 | defer xconn.Close() 247 | xroot = xproto.Setup(xconn).DefaultScreen(xconn).Root 248 | run() 249 | } 250 | --------------------------------------------------------------------------------