10 /^ +percentage: +/ { percentage=$2 }
11 /^ +state: +/ { state=$2 }
12 END { print(state, percentage) }
18 free |
awk '$1 == "Mem:" {print $2, $3}'
26 produce_temperature
() {
28 cat "/sys/class/thermal/thermal_zone${thermal_zone}/temp"
43 ' "/sys/block/$disk_io_device/stat"
46 produce_disk_space
() {
47 disk_space_device
="$1"
48 df
--output=pcent
"$disk_space_device" |
awk 'NR == 2 {print $1}'
51 produce_net_addr_io
() {
55 bytes_per_unit = 1024 * 1024
63 interfaces[sequence] = interface
67 sub("/[0-9]+", "", $2)
69 addrs[interface] = addr
72 /^ +RX: / {transfer_direction = "r"}
73 /^ +TX: / {transfer_direction = "w"}
75 /^ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ *$/ {
76 io[interface, transfer_direction] = $1;
80 for (seq=1; seq<=sequence; seq++) {
81 interface = interfaces[seq]
82 label = substr(interface, 1, 1)
83 if (addrs[interface]) {
84 curr_read = io[interface, "r"]
85 curr_write = io[interface, "w"]
86 print(interface, addrs[interface], curr_write, curr_read)
94 produce_net_wifi_status
() {
96 -f ACTIVE
,SSID
,SIGNAL \
102 BEGIN {wifi_status = "--"}
103 $1 == "yes" {wifi_status = $2 ":" $3 "%"}
104 END {print wifi_status}
108 produce_bluetooth_power
() {
109 echo -e 'show \n quit' \
114 controllers[++ctrl_count] = controller;
116 /^\t[A-Z][A-Za-z]+:/ {
120 for (i=3; i<=NF; i++) {
122 data[controller, key] = val;
125 # Using the 1st seen controller. Should we select specific instead?
126 power_status = data[controllers[1], "Powered"];
127 if (ctrl_count > 0) {
128 if (power_status == "no") {
130 } else if (power_status == "yes") {
133 printf("Unexpected bluetooth power status: %s\n", power_status)\
135 power_status = "ERROR"
138 power_status = "off" # TODO: Perhaps use differentiated marker?
140 printf("%s\n", power_status);
144 produce_screen_brightness
() {
145 screen_brightness_device_path
="$1"
147 $(cat $screen_brightness_device_path/max_brightness) \
148 $(cat $screen_brightness_device_path/brightness)\
159 for (i=2; i<=NF; i++) printf(" %s", $i);
162 /^[ \t]*Mute:/ {mute = $2}
163 /^[ \t]*front-left:/ {left = $4}
164 /^[ \t]*front-right:/ {right = $4}
169 print("%s %s\n", left, right)
175 produce_mpd_state
() {
177 | nc
127.0.0.1 6600 \
183 /^time: +[0-9]+:[0-9]+$/ {
185 seconds_current = time[1]
186 seconds_total = time[2]
188 hours = int(seconds_current / 60 / 60);
189 secs_beyond_hours = seconds_current - (hours * 60 * 60);
190 mins = int(secs_beyond_hours / 60);
191 secs = secs_beyond_hours - (mins * 60);
193 current_time = sprintf("%d:%.2d:%.2d", hours, mins, secs)
195 current_time = sprintf("%.2d:%.2d", mins, secs)
198 if (seconds_total > 0) {
199 time_percentage = (seconds_current / seconds_total) * 100
200 current_percentage = sprintf("%d%%", time_percentage)
202 current_percentage = "~"
207 state = status["state:"]
209 if (state == "play") {
211 } else if (state == "pause") {
213 } else if (state == "stop") {
221 status["state:"], current_time, current_percentage\
229 | nc
127.0.0.1 6600 \
237 sub("^" key " +", "")
244 title = data["Title:"]
252 last = split(file, parts, "/")
263 weather_station_id
="$1"
264 metar
-d "$weather_station_id" 2>&1 \
266 /METAR pattern not found in NOAA data/ {
272 fahrenheit = (celsius * (9 / 5)) + 32;
273 temperature = fahrenheit
280 print temperature "°F"
285 date +'%a %b %d %H:%M:%S'
291 prefixes_of_net_interfaces_to_show
="$3"
294 -v opt_debug
="$debug" \
295 -v opt_mpd_song_max_chars
=10 \
296 -v opt_prefixes_of_net_interfaces_to_show
="$prefixes_of_net_interfaces_to_show" \
301 db["energy_state"] = $1
302 db["energy_percentage"] = $2
308 db["memory_total"] = $1
309 db["memory_used"] = $2
315 db["fan_status"] = $2
333 db["temperature"] = $1
351 db["disk_space_used"] = msg_body
360 /^in:NET_WIFI_STATUS/\
363 db["net_wifi_status"] = msg_body
366 /^in:BLUETOOTH_POWER/\
369 db["bluetooth_power"] = msg_body
372 /^in:SCREEN_BRIGHTNESS/\
375 set_screen_brightness()
381 db["volume"] = msg_body
388 db["mpd_curr_song_time"] = $2
389 db["mpd_curr_song_percent"] = $3
395 db["mpd_curr_song_name"] = msg_body
401 db["weather_temperature"] = msg_body
407 db["datetime"] = msg_body
416 function set_load_avg( sched) {
417 split($4, sched, "/")
418 db["load_avg_1min"] = $1
419 db["load_avg_5min"] = $2
420 db["load_avg_15min"] = $3
421 db["kern_sched_queue_runnable"] = sched[1]
422 db["kern_sched_queue_total"] = sched[2]
423 db["kern_sched_latest_pid"] = $5
426 function set_disk_io( curr_w, curr_r, prev_w, prev_r) {
429 prev_w = db["disk_io_curr_w"]
430 prev_r = db["disk_io_curr_r"]
431 db["disk_io_curr_w"] = curr_w
432 db["disk_io_curr_r"] = curr_r
433 db["disk_io_diff_w"] = curr_w - prev_w
434 db["disk_io_diff_r"] = curr_r - prev_r
437 function set_net_addr_io( \
438 interface, address, io_curr_w, io_curr_r, io_prev_w, io_prev_r\
445 if (address && io_curr_w && io_curr_r) {
447 io_prev_w = net_io_curr_w[interface]
448 io_prev_r = net_io_curr_r[interface]
450 net_addr[interface] = address
451 net_io_curr_w[interface] = io_curr_w
452 net_io_curr_r[interface] = io_curr_r
453 net_io_diff_w[interface] = io_curr_w - io_prev_w
454 net_io_diff_r[interface] = io_curr_r - io_prev_r
457 net_addr[interface] = ""
458 net_io_curr_w[interface] = 0
459 net_io_curr_r[interface] = 0
460 net_io_diff_w[interface] = 0
461 net_io_diff_r[interface] = 0
466 function set_screen_brightness( max, cur) {
469 db["screen_brightness"] = (cur / max) * 100
472 function split_msg_parts() {
474 sub("^" msg_head " +", "")
476 debug(msg_head, msg_body)
479 function make_bar( position, bar, sep, i, j) {
480 position[++i] = make_status_energy()
481 position[++i] = make_status_mem()
482 position[++i] = make_status_cpu()
483 position[++i] = make_status_disk()
484 position[++i] = make_status_net()
485 position[++i] = sprintf("B=%s", db["bluetooth_power"])
486 position[++i] = sprintf("*%d%%", db["screen_brightness"])
487 position[++i] = sprintf("(%s)", db["volume"])
488 position[++i] = make_status_mpd()
489 position[++i] = db["weather_temperature"]
490 position[++i] = db["datetime"]
493 for (j = 1; j <= i; j++) {
494 bar = bar sep position[j]
500 function make_status_energy( state, direction_of_change) {
501 state = db["energy_state"]
502 if (state == "discharging") {
503 direction_of_change = "<"
504 } else if (state == "charging") {
505 direction_of_change = ">"
507 direction_of_change = "="
509 printf("E%s%s", direction_of_change, db["energy_percentage"])
512 function make_status_mem( total, used, percent, status) {
513 total = db["memory_total"]
514 used = db["memory_used"]
515 # To avoid division by zero when data is missing
517 percent = round((used / total) * 100)
518 status = sprintf("%d%%", percent)
522 return sprintf("M=%s", status)
525 function make_status_cpu( load, temp, fan) {
526 load = db["load_avg_1min"]
527 temp = db["temperature"] / 1000
528 fan = db["fan_speed"]
529 return sprintf("C=[%4.2f %d°C %4drpm]", load, temp, fan)
532 function make_status_disk( bytes_per_sector, bytes_per_mb, w, r) {
533 bytes_per_sector = 512
534 bytes_per_mb = 1024 * 1024
535 w = (db["disk_io_diff_w"] * bytes_per_sector) / bytes_per_mb
536 r = (db["disk_io_diff_r"] * bytes_per_sector) / bytes_per_mb
538 sprintf("D=[%s %0.3f▲ %0.3f▼]", db["disk_space_used"], w, r)
541 function make_status_net( \
543 number_of_interfaces_to_show,
545 array_of_prefixes_of_interfaces_to_show,
556 number_of_interfaces_to_show = \
558 opt_prefixes_of_net_interfaces_to_show,\
559 array_of_prefixes_of_interfaces_to_show,\
562 for (n = 1; n <= number_of_interfaces_to_show; n++) {
563 prefix = array_of_prefixes_of_interfaces_to_show[n]
564 for (interface in net_addr) {
565 if (interface ~ ("^" prefix)) {
566 label = substr(interface, 1, 1)
567 if (net_addr[interface]) {
568 bytes_per_mb = 1024 * 1024 # TODO: option
569 dw = net_io_diff_w[interface] / bytes_per_mb
570 dr = net_io_diff_r[interface] / bytes_per_mb
571 io_stat = sprintf("%0.3f▲ %0.3f▼", dw, dr)
575 if (interface ~ "^w") {
576 label = label ":" db["net_wifi_status"]
578 if (++count_printed > 1) {
583 out = out sep label ":" io_stat
587 return sprintf("N[%s]", out)
590 function make_status_mpd( state, status) {
591 state = db["mpd_state"]
593 if (state == "play") {
594 status = make_status_mpd_state_known("▶")
595 } else if (state == "pause") {
596 status = make_status_mpd_state_known("❚❚")
597 } else if (state == "stop") {
598 status = make_status_mpd_state_known("⬛")
600 status = make_status_mpd_state_unknown("--")
603 return sprintf("[%s]", status)
606 function make_status_mpd_state_known(symbol) {
610 db["mpd_curr_song_time"],
611 db["mpd_curr_song_percent"],
612 substr(db["mpd_curr_song_name"], 1, opt_mpd_song_max_chars)\
616 function make_status_mpd_state_unknown(symbol) {
617 return sprintf("%s", symbol)
624 function debug(location, msg) {
626 print_error(location, msg)
630 function print_error(location, msg) {
631 print(location " ==> " msg) > "/dev/stderr"
646 $cmd |
while read line
; do
647 echo "${msg_head} $line" > "$pipe"
656 dir_data
="$HOME/.khatus"
657 weather_station_id
='KJFK'
658 screen_brightness_device_name
='acpi_video0'
659 prefixes_of_net_interfaces_to_show
='w' # comma-separated
660 disk_space_device
='/'
663 fan_path
='/proc/acpi/ibm/fan'
667 long_options
+='debug'
668 long_options
+=',data_dir:'
669 long_options
+=',weather_station:'
670 long_options
+=',screen_device:'
671 long_options
+=',prefixes_of_net_interfaces_to_show:'
672 long_options
+=',disk_space_device:'
673 long_options
+=',thermal_zone:'
674 long_options
+=',fan_path:'
694 weather_station_id
="$2"
698 screen_brightness_device_name
="$2"
701 --prefixes_of_net_interfaces_to_show)
702 prefixes_of_net_interfaces_to_show
="$2"
706 disk_space_device
="$2"
728 pipe
="$dir_data/khatus_data_pipe"
729 screen_brightness_device_path
='/sys/class/backlight'
730 screen_brightness_device_path
+="/$screen_brightness_device_name"
732 ( echo "Khatus starting with the following parameters:"
733 ( echo " debug|= $debug"
734 echo " dir_data|= $dir_data"
736 echo " screen_brightness_device_name|= $screen_brightness_device_name"
737 echo " screen_brightness_device_path|= $screen_brightness_device_path"
738 echo " weather_station_id|= $weather_station_id"
739 echo " prefixes_of_net_interfaces_to_show|= $prefixes_of_net_interfaces_to_show"
740 echo " disk_space_device|= $disk_space_device"
741 echo " disk_io_device|= $disk_io_device"
742 echo " thermal_zone|= $thermal_zone"
743 echo " fan_path|= $fan_path"
752 cmd_produce_screen_brightness
='produce_screen_brightness'
753 cmd_produce_screen_brightness
+=" $screen_brightness_device_path"
755 cmd_produce_weather
="produce_weather $weather_station_id"
757 cmd_produce_disk_space
="produce_disk_space $disk_space_device"
759 cmd_produce_disk_io
="produce_disk_io $disk_io_device"
761 cmd_produce_temperature
="produce_temperature $thermal_zone"
763 cmd_produce_fan
="produce_fan $fan_path"
765 # TODO: Redirect each worker's stderr to a dedicated log file
766 spawn produce_datetime
"$pipe" 'in:DATE_TIME' 1
767 spawn
"$cmd_produce_screen_brightness" "$pipe" 'in:SCREEN_BRIGHTNESS' 1
768 spawn
"$cmd_produce_weather" "$pipe" 'in:WEATHER' $
(( 30 * 60 ))
769 spawn produce_mpd_state
"$pipe" 'in:MPD_STATE' 1
770 spawn produce_mpd_song
"$pipe" 'in:MPD_SONG' 1
771 spawn produce_volume
"$pipe" 'in:VOLUME' 1
772 spawn produce_bluetooth_power
"$pipe" 'in:BLUETOOTH_POWER' 5
773 spawn produce_net_wifi_status
"$pipe" 'in:NET_WIFI_STATUS' 5
774 spawn produce_net_addr_io
"$pipe" 'in:NET_ADDR_IO' 1
775 spawn
"$cmd_produce_disk_space" "$pipe" 'in:DISK_SPACE' 1
776 spawn
"$cmd_produce_disk_io" "$pipe" 'in:DISK_IO' 1
777 spawn produce_loadavg
"$pipe" 'in:LOAD_AVG' 1
778 spawn
"$cmd_produce_temperature" "$pipe" 'in:TEMPERATURE' 1
779 spawn
"$cmd_produce_fan" "$pipe" 'in:FAN' 1
780 spawn produce_memory
"$pipe" 'in:MEMORY' 1
781 spawn produce_energy
"$pipe" 'in:ENERGY' 1
782 spawn produce_bar_req
"$pipe" 'out:BAR' 1
787 "$prefixes_of_net_interfaces_to_show"