Note the meaning of version numbers
[khatus.git] / x1 / bin / khatus_show
1 #! /bin/bash
2
3 set -e
4
5 BIN=$HOME/bin
6 STATUS_DIR=$HOME/var/run/status
7 STATUS_FILE__WIFI=$STATUS_DIR/wifi
8 STATUS_FILE__ENERGY_NOTIFIED_BELLOW_HALF=$STATUS_DIR/notified_energy_bellow_half
9 while getopts ":b:d:s:i:w:" opt
10 do
11 case "$opt" in
12 b)
13 SCREEN_BRIGHTNESS_DEVICE_NAME="$OPTARG"
14 ;;
15 d)
16 DISK_IO_DEVICE="$OPTARG"
17 ;;
18 s)
19 DISK_SPACE_DEVICE="$OPTARG"
20 ;;
21 i)
22 PREFIXES_OF_INTERFACES_TO_SHOW="$OPTARG"
23 ;;
24 w)
25 WEATHER_STATION_ID="$OPTARG"
26 ;;
27 \?)
28 echo "Unknown option: $OPTARG" >&2
29 exit 1
30 ;;
31 :)
32 echo "Missing argument for option: $OPTARG" >&2
33 exit 1
34 ;;
35 esac
36 done
37 if [ "$SCREEN_BRIGHTNESS_DEVICE_NAME" == '' ]; then echo 'Missing -b' >&2; exit 1; fi
38 if [ "$DISK_IO_DEVICE" == '' ]; then echo 'Missing -d' >&2; exit 1; fi
39 if [ "$DISK_SPACE_DEVICE" == '' ]; then echo 'Missing -s' >&2; exit 1; fi
40 if [ "$WEATHER_STATION_ID" == '' ]; then echo 'Missing -w' >&2; exit 1; fi
41 SCREEN_BRIGHTNESS_DEVICE_PATH="/sys/class/backlight/$SCREEN_BRIGHTNESS_DEVICE_NAME"
42
43
44 load=$(awk '{printf("%4.2f", $1)}' /proc/loadavg)
45
46 fan=$(awk '/^speed:/ {printf "%4d", $2}' /proc/acpi/ibm/fan)
47
48 #cpu=$($BIN/khatus_cpu_usage_from_proc_since_last_check)
49
50 memory=$(
51 free \
52 | awk '
53 function round(n) {return int(n + 0.5)}
54
55 $1 == "Mem:" {
56 total=$2;
57 used=$3;
58 cache=$6;
59 prev_file = ENVIRON["HOME"] "/var/run/status/memory_used_percentage";
60 curr = round(used / total * 100);
61 getline prev < prev_file;
62 print curr > prev_file;
63 if (curr > prev) {
64 direction = ">";
65 } else if (curr < prev) {
66 direction = "<";
67 } else {
68 direction = "=";
69 }
70 printf("%s%d%%", direction, curr);
71 }')
72
73 temp=$(awk 'NR == 1 {print $1 / 1000}' /sys/class/thermal/thermal_zone0/temp)
74
75 disk_io=$(
76 awk '
77 {
78 bytes_per_sector = 512
79 bytes_per_unit = 1024 * 1024
80
81 curr_sectors_read = $3
82 curr_sectors_write = $7
83
84 prev_file_prefix = ENVIRON["HOME"] "/var/run/status/disk_io"
85 prev_sectors_read_file = prev_file_prefix "_sectors_read"
86 prev_sectors_write_file = prev_file_prefix "_sectors_write"
87
88 getline prev_sectors_read < prev_sectors_read_file
89 getline prev_sectors_write < prev_sectors_write_file
90
91 diff_read_sectors = (curr_sectors_read - prev_sectors_read)
92 diff_write_sectors = (curr_sectors_write - prev_sectors_write)
93
94 diff_read_bytes = diff_read_sectors * bytes_per_sector
95 diff_write_bytes = diff_write_sectors * bytes_per_sector
96
97 diff_read = diff_read_bytes / bytes_per_unit
98 diff_write = diff_write_bytes / bytes_per_unit
99
100 print curr_sectors_read > prev_sectors_read_file
101 print curr_sectors_write > prev_sectors_write_file
102
103 printf("%0.3f▲ %0.3f▼\n", diff_write, diff_read);
104
105 }
106 ' "/sys/block/$DISK_IO_DEVICE/stat"
107 )
108
109 disk=$(
110 df \
111 | awk \
112 -v disk_io="$disk_io" \
113 -v device="$DISK_SPACE_DEVICE" \
114 '
115 function round(n) {return int(n + 0.5)}
116
117 $1 == device {
118 curr_perc = $5; sub("%$", "", curr_perc);
119 prev_perc_file = ENVIRON["HOME"] "/var/run/status/disk_space_used";
120 getline prev_perc < prev_perc_file;
121 print curr_perc > prev_perc_file;
122 if (curr_perc > prev_perc) {
123 direction = ">";
124 } else if (curr_perc < prev_perc) {
125 direction = "<";
126 } else {
127 direction = "=";
128 }
129 printf("%s[%d%% %s]", direction, curr_perc, disk_io);
130 }')
131
132 # TODO: Wi-Fi status file should be a file per-wifi-device
133 network=$(
134 ip -s addr \
135 | awk \
136 -v wifi_conn="$(cat $STATUS_FILE__WIFI)" \
137 -v prefixes_of_interfaces_to_show="$PREFIXES_OF_INTERFACES_TO_SHOW" \
138 '
139 BEGIN {
140 bytes_per_unit = 1024 * 1024
141 }
142
143 /^[0-9]+:/ {
144 sub(":$", "", $1)
145 sub(":$", "", $2)
146 sequence = $1
147 interface = $2
148 interfaces[sequence] = interface
149 }
150
151 /^ +inet [0-9]/ {
152 sub("/[0-9]+", "", $2)
153 addr = $2
154 addrs[interface] = addr
155 }
156
157 /^ +RX: / {transfer_direction = "r"}
158 /^ +TX: / {transfer_direction = "w"}
159
160 /^ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ *$/ {
161 io[interface, transfer_direction] = $1;
162 }
163
164 END {
165 for (seq=1; seq<=sequence; seq++) {
166 interface = interfaces[seq]
167 label = substr(interface, 1, 1)
168 if (addrs[interface]) {
169 curr_read = io[interface, "r"]
170 curr_write = io[interface, "w"]
171
172 prefix = ENVIRON["HOME"] "/var/run/status/io_net_" interface
173 prev_read_file = prefix "_read"
174 prev_write_file = prefix "_write"
175
176 getline prev_read < prev_read_file
177 getline prev_write < prev_write_file
178
179 diff_read = (curr_read - prev_read ) / bytes_per_unit
180 diff_written = (curr_write - prev_write) / bytes_per_unit
181
182 print curr_read > prev_read_file
183 print curr_write > prev_write_file
184
185 io_stat = sprintf("%0.3f▲ %0.3f▼", diff_written, diff_read)
186 if (interface ~ "^w") {
187 label = label ":" wifi_conn
188 }
189 } else {
190 io_stat = "--"
191 }
192 number_of_interfaces_to_show = \
193 split(\
194 prefixes_of_interfaces_to_show,\
195 array_of_prefixes_of_interfaces_to_show,\
196 " +"\
197 )
198 for (n = 1; n <= number_of_interfaces_to_show; n++) {
199 prefix = array_of_prefixes_of_interfaces_to_show[n]
200 if (interface ~ ("^" prefix)) {
201 if (++count_printed > 1) {
202 sep = " "
203 } else {
204 sep = ""
205 }
206 printf("%s%s:%s", sep, label, io_stat)
207 }
208 }
209 }
210 }'
211 )
212
213 energy=$(
214 upower -e \
215 | grep battery \
216 | xargs upower -i \
217 | awk '
218 /^ +percentage: +/ {percentage=$2}
219 /^ +state: +/ {state=$2}
220 END {
221 if (state == "discharging") {
222 direction_of_change = "<"
223 } else if (state == "charging") {
224 direction_of_change = ">"
225 } else {
226 direction_of_change = "="
227 };
228 printf("%s%s", direction_of_change, percentage)
229 }')
230
231 datetime=$(date +'%a %b %d %H:%M:%S')
232 #datetime=$(
233 # date +'%a %u %b %d %H:%M:%S' \
234 # | awk '
235 # {
236 # wday_name = $1
237 # wday_seq = $2
238 # month = $3
239 # mday = $4
240 # time = $5
241 #
242 # week = ""
243 # for (i=1; i<=7; i++) {
244 # if (i == 6 || i == 4) {
245 # sep = " "
246 # } else {
247 # sep = ""
248 # }
249 #
250 # if (i == wday_seq) {
251 # #symbol = substr(wday_name, 1, 1)
252 # symbol = "/"
253 # } else if (i < wday_seq){
254 # symbol = "X"
255 # } else {
256 # symbol = "_"
257 # }
258 # week = week sep symbol
259 # }
260 #
261 # print "["week"]", month, mday, time;
262 # }
263 # '
264 #)
265
266 #volume_amixer=$(
267 # amixer get Master \
268 # | tail -1 \
269 # | awk '
270 # {
271 # level = $4;
272 # sub("^\\[", "", level);
273 # sub("\\]$", "", level);
274 # print level;
275 # }' \
276 # )
277
278 #volume_amixer=$(
279 # amixer get Master \
280 # | tail -n 1 \
281 # | awk '{print $4}' \
282 # | tr -d '[]'
283 #)
284
285 volume_pactl=$(
286 pactl list sinks \
287 | awk '
288 /^\tMute:/ {
289 printf("%s,", $0);
290 }
291 /^\tVolume:/ {
292 for (i=2; i<=NF; i++) printf(" %s", $i);
293 }' \
294 | awk -v RS=',' '
295 /^[ \t]*Mute:/ {mute = $2}
296 /^[ \t]*front-left:/ {left = $4}
297 /^[ \t]*front-right:/ {right = $4}
298 END {
299 if (mute == "yes") {
300 printf("x")
301 } else {
302 printf("%s %s", left, right)
303 }
304 }
305 '
306 )
307
308 volume="($volume_pactl)"
309
310 screen_brightness=$(
311 echo -n $((
312 $(cat $SCREEN_BRIGHTNESS_DEVICE_PATH/brightness)
313 / $(cat $SCREEN_BRIGHTNESS_DEVICE_PATH/max_brightness)
314 * 100
315 ))
316 )
317
318 #bluetooth_status=$(
319 # grep '^status:' /proc/acpi/ibm/bluetooth \
320 # | awk '
321 # $2 == "disabled" {printf "off"}
322 # $2 == "enabled" {printf "on"}
323 # '
324 #)
325
326 bluetooth_power=$(
327 echo -e 'show \n quit' \
328 | bluetoothctl \
329 | awk '
330 /^Controller / {
331 controller = $2;
332 controllers[++ctrl_count] = controller;
333 }
334 /^\t[A-Z][A-Za-z]+:/ {
335 key = $1;
336 sub(":$", "", key);
337 val = $2;
338 for (i=3; i<=NF; i++) {
339 val = val " " $i};
340 data[controller, key] = val;
341 }
342 END {
343 # Using the 1st seen controller. Should we select specific instead?
344 power_status = data[controllers[1], "Powered"];
345 if (ctrl_count > 0) {
346 if (power_status == "no") {
347 power_status = "off"
348 } else if (power_status == "yes") {
349 power_status = "on"
350 } else {
351 printf("Unexpected bluetooth power status: %s\n", power_status)\
352 > "/dev/stderr";
353 power_status = "ERROR"
354 }
355 } else {
356 power_status = "off" # TODO: Perhaps use differentiated marker?
357 }
358 printf("%s", power_status);
359 }'
360 )
361
362 #touchpad_status=$(
363 # xinput list-props 12 \
364 # | awk '
365 # /^\tDevice Enabled \([0-9]+\):/ {
366 # status = $4;
367 # printf("%s", status);
368 # }'
369 #)
370
371 #color_off='\033[0m'
372 #color_on_bg_gray='\033[m\033[40m'
373
374 energy_direction=$(echo "$energy" | cut -b 1)
375 energy_percentage=$(echo "$energy" | tr -d '<>=%')
376 if [[ "$energy_direction" = '<' ]]
377 then
378 if [[ $energy_percentage -le 5 ]]
379 then
380 DISPLAY=:0.0 notify-send \
381 -u critical \
382 "Energy CRITICALLY low: $energy" \
383 'CHARGE NOW!!! GO GO GO!!!'
384 elif [[ $energy_percentage -le 10 ]]
385 then
386 DISPLAY=:0.0 notify-send \
387 -u critical \
388 "Energy VERY low: $energy" \
389 'Plug it in ASAP.'
390 elif [[ $energy_percentage -le 15 ]]
391 then
392 DISPLAY=:0.0 notify-send \
393 -u critical \
394 "Energy low: $energy" \
395 'Get the charger.'
396 elif [[ $energy_percentage -le 50 ]]
397 then
398 if [[ ! -a "$STATUS_FILE__ENERGY_NOTIFIED_BELLOW_HALF" ]]
399 then
400 DISPLAY=:0.0 notify-send \
401 -u normal \
402 "Energy bellow half: $energy" \
403 'Where is the charger?'
404 touch "$STATUS_FILE__ENERGY_NOTIFIED_BELLOW_HALF"
405 fi
406 fi
407 else
408 rm -f "$STATUS_FILE__ENERGY_NOTIFIED_BELLOW_HALF"
409 fi
410
411 weather=$(
412 awk \
413 'NR == 1 {printf("%s°F", $1)}' \
414 "$HOME/var/run/metar-${WEATHER_STATION_ID}-decoded-temp-fahrenheit"
415 )
416
417 #signal_last_msg_age=$(
418 # ls -lt --time-style=+%s $HOME/var/lib/signal/latest_message.json \
419 # | awk -v now_seconds=$(date +%s) \
420 # '{
421 # mtime_seconds = $6;
422 # seconds = now_seconds - mtime_seconds;
423 # minutes = seconds / 60;
424 # hours = minutes / 60;
425 # days = hours / 24;
426 # weeks = days / 7;
427 # months = days / 30;
428 # #fmt = "%.1f";
429 # fmt = "%d";
430 # printf(fmt " s\n", seconds);
431 # printf(fmt " m\n", minutes);
432 # printf(fmt " h\n", hours);
433 # printf(fmt " d\n", days);
434 # printf(fmt " w\n", weeks);
435 # printf(fmt " mo\n", months);
436 # }' \
437 # | awk '$1 >= 1' \
438 # | sort -n -k 1 \
439 # | head -1 \
440 # | tr -d ' '
441 #)
442
443 mpd_currentsong=$(
444 echo 'currentsong' \
445 | nc 127.0.0.1 6600 \
446 | awk -v max_chars=10 '
447 /^OK/ {
448 next
449 }
450
451 {
452 key = $1
453 val = $2
454 for (i=3; i<=NF; i++) {val = val " " $i}
455 data[key] = val
456 }
457
458 END {
459 name = data["Name:"]
460 title = data["Title:"]
461 file = data["file:"]
462
463 if (name) {
464 out = name
465 } else if (title) {
466 out = title
467 } else if (file) {
468 last = split(file, parts, "/")
469 out = parts[last]
470 } else {
471 out = ""
472 }
473
474 printf("%s", substr(out, 1, max_chars))
475 }
476 '
477 )
478
479 mpd_state=$(
480 echo 'status' \
481 | nc 127.0.0.1 6600 \
482 | awk \
483 -v current_song="$mpd_currentsong" \
484 '
485 {
486 status[$1] = $2
487 }
488
489 /^time: +[0-9]+:[0-9]+$/ {
490 split($2, time, ":")
491 seconds_current = time[1]
492 seconds_total = time[2]
493
494 hours = int(seconds_current / 60 / 60);
495 secs_beyond_hours = seconds_current - (hours * 60 * 60);
496 mins = int(secs_beyond_hours / 60);
497 secs = secs_beyond_hours - (mins * 60);
498 if (hours > 0) {
499 current_time = sprintf("%d:%.2d:%.2d", hours, mins, secs)
500 } else {
501 current_time = sprintf("%.2d:%.2d", mins, secs)
502 }
503
504 if (seconds_total > 0) {
505 time_percentage = (seconds_current / seconds_total) * 100
506 current_percentage = sprintf("%d%%", time_percentage)
507 } else {
508 current_percentage = "~"
509 }
510 }
511
512 function print_known_state(symbol) {
513 printf(\
514 "%s %s %s %s",
515 symbol, current_time, current_percentage, current_song \
516 )
517 }
518
519 function print_unknown_state(symbol) {
520 printf("%s", symbol)
521 }
522
523 END {
524 state = status["state:"]
525
526 if (state == "play") {
527 print_known_state("▶")
528 } else if (state == "pause") {
529 print_known_state("❚❚")
530 } else if (state == "stop") {
531 print_known_state("⬛")
532 } else {
533 print_unknown_state("--")
534 }
535 }
536 '
537 )
538
539 #graphics_card=$(
540 #nvidia-smi \
541 #--format=csv,noheader,nounits \
542 #--query-gpu=memory.total,memory.used,temperature.gpu \
543 #| awk -F ',' '
544 #{
545 #mem_total = $1;
546 #mem_used = $2;
547 #temp = $3;
548 #mem_used_percent = (100 * mem_used) / mem_total;
549 #printf("[%d%% %dC]", mem_used_percent, temp);
550 #}
551 #'
552 #)
553
554 echo \
555 "\
556 E$energy\
557 \
558 M$memory\
559 \
560 C=[$load ${temp}°C ${fan}rpm]\
561 \
562 D$disk\
563 \
564 N:[$network]\
565 \
566 B:$bluetooth_power\
567 \
568 *$screen_brightness%\
569 \
570 $volume\
571 \
572 [$mpd_state]\
573 \
574 $weather\
575 \
576 $datetime \
577 "
This page took 0.125385 seconds and 4 git commands to generate.