mostly fixes, some improvements
[distro-setup] / brc2
diff --git a/brc2 b/brc2
index 12586845d49bad0468673dd5d041ae5463830367..73dc3daaed782cbffc1f3c00e6111a4adc50bb1c 100644 (file)
--- a/brc2
+++ b/brc2
@@ -290,7 +290,10 @@ tback() {
 # s sshfs bu@$host:/bu/home/md /bu/mnt -o reconnect,ServerAliveInterval=20,ServerAliveCountMax=30 -o allow_other
 
 eqgo() {
-  enn -M "$(exiqgrep -i -r.\*)"
+  local -a array tmpstr
+  tmpstr=$(exiqgrep -i -r.\*)
+  mapfile -t array <<<"$tmpstr"
+  enn -M "${array[@]}"
 }
 eqgo1() {
   enn -M "$(exipick -i -r.\*|h1)"
@@ -349,7 +352,7 @@ alerts() {
 ralerts() { # remote alerts
   local ret shell
   # this list is duplicated in check-remote-mailqs
-  for h in bk je li frodo kwwg x3wg x2wg kdwg sywg; do
+  for h in bk je li frodo x3wg kdwg sywg; do
     echo $h:
     shell="ssh $h"
     if [[ $HOSTNAME == "${h%wg}" ]]; then
@@ -525,7 +528,15 @@ EOF
 
 # beet playlist. use beetag with a playlist name
 bpl() {
-  eval beetag -r "$@" "${bpla[${@: -1}]}"
+  local playlist playlist_regex
+  playlist="${*: -1}"
+  playlist_regex='[a-z0-9_]'
+  if [[ ! $playlist =~ $playlist_regex ]]; then
+    echo "bpl: error unexpected chars in playlist: $playlist"
+    return 1
+  fi
+  # all but last arg as options
+  eval beetag -r "${*:1:$# - 1}" "${bpla[$playlist]}"
 }
 complete -W "${!bpla[*]}" bpl
 
@@ -538,31 +549,101 @@ beetmq() {
   sed 's/^format_item:.*/format_item: ignore_this/' ~/.config/beets/config.yaml >$tmpf
   beet -c $tmpf modify -y "$@" > >(grep -vFx -e 'ignore_this' -e 'Modifying 1 items.' ||:)
   rm "$tmpf"
-  scrolled+=1
+  beetag-nostatus 1
+}
+
+kill-bg-quiet() {
+  # https://stackoverflow.com/a/5722874
+  kill %% 2>/dev/null ||:; wait %% 2>/dev/null ||:
+}
+
+# debug variables
+dv() {
+  for arg; do
+    printf "%s=%s " "$arg" "${!arg}"
+  done
+  echo
 }
 
 # Must be called from beetag for variables to be setup
 beetag-help() {
+  local -i i j col_total row col button_total row_total remainder_cols remainder_term
+  col_total=4
+  button_total=${#button_map[@]}
+  row_total=$(( button_total / col_total ))
+  remainder_cols=$(( button_total % col_total ))
+  # for debugging
+  #dv button_total row_total remainder_cols
+  beetag-nostatus
   #   - 3 is just a constant that helps things work in practice.
   if [[ $LINES ]] && (( LINES - 3 < scrolled )); then
     hr
-    for (( i=0; i<${#button_map[@]}; i++)); do
-      if (( i % 3 == 2 )); then
-        printf "%s %s\n" ${buttons[i]} ${button_map[i]}
+    for (( i=0; i<button_total; i++)); do
+      row=$(( i / col_total ))
+      col=$(( i % col_total ))
+      remainder_term=$remainder_cols
+      if (( col < remainder_term )); then
+        remainder_term=$col
+      fi
+      j=$(( col * row_total + row + remainder_term ))
+      # avoid double newline when we have exactly row * col buttons
+      if (( i == button_total - 1 )); then
+        printf "%s %s" ${buttons[j]} ${button_map[j]}
+      elif (( i % col_total == col_total -1 )); then
+        printf "%s %s\n" ${buttons[j]} ${button_map[j]}
       else
-        printf "%s %-15s" ${buttons[i]} ${button_map[i]}
+        printf "%s %-15s" ${buttons[j]} ${button_map[j]}
       fi
     done
-    echo
     cat <<'EOF'
-y other genres   z fg player      ' = toggle play
-; previous       _ = delete       -/+ volume   ->/<- skip
+
+
+y other genres   z fg player      ' = toggle play  1-5 rate
+; previous       _ = delete       up/down skip     mpv vol,pause,seek
 EOF
     hr
     scrolled=10
   fi
 }
 
+# Must be called from beetag for variables to be setup
+beetag-nostatus() {
+  if (( $# )); then
+    scrolled=$(( scrolled + $1 ))
+  fi
+  if $erasable_line; then
+    # https://stackoverflow.com/a/71286261
+    printf '\033[1A\033[K'
+  fi
+  erasable_line=false
+}
+# meant to be called from beetag
+beetag-status() {
+  if $erasable_line; then
+    # https://stackoverflow.com/a/71286261
+    printf '\033[1A\033[K'
+  fi
+  erasable_line=true
+}
+
+# meant to be called from beetag
+mpvrpc() {
+  if jobs -p | grep -q . &>/dev/null; then
+    printf "%s\n" "$*" | socat - /tmp/mpvsock >/dev/null ||:
+  fi
+}
+# meant to be called from beetag
+# o for get output
+mpvrpco() {
+  # note: testing for background jobs will output nothing if we are in a pipeline
+  printf "%s\n" "$*" | socat - /tmp/mpvsock ||:
+}
+
+# meant to be called from beetag
+mpvrpc-percent-pos() {
+  mpvrpco '{ "command": ["get_property", "percent-pos"] }' | jq .data | sed 's/\..*/%/' 2>/dev/null ||:
+}
+
 # tag with beets.
 # usage: beetag [-r] [-s] QUERY
 # it lists the query, reads an input char for tagging one by one.
@@ -582,19 +663,23 @@ EOF
 # q quit
 # ret next
 beetag() {
-  local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random
+  local last_genre_i fstring tag id char new_item char_i genre tag remove doplay i j random path
   local do_rare_genres read_wait help line lsout tmp ls_line skip_lookback
-  local escape_char escaped_input expected_input skip_input_regex
-  local -a pl_tags buttons button_map ids tags rare_genres tmp_tags initial_ls ls_lines
+  local escape_char escaped_input expected_input skip_input_regex right_pad erasable_line seek_sec
+  local pl_state_path pl_state_dir pl_state_file tmpstr
+  local new_random pl_seed_path seed_num seed_file fmt first_play
+  local -a pl_tags buttons button_map ids tags tmp_tags initial_ls ls_lines paths
   local -A button_i
   local -i i j volume scrolled id_count line_int skip_start pre_j_count head_count skip_lookback
   local -i overflow_lines overflow
 
-
+  first_play=true
+  erasable_line=false
   escape_char=$(printf "\u1b")
   scrolled=999 # more than any $LINES
   ### begin arg processing ###
   random=false
+  new_random=false
   case $1 in
     -r)
       random=true
@@ -604,6 +689,10 @@ beetag() {
       random=false
       shift
       ;;
+    -x)
+      new_random=true
+      shift
+      ;;
   esac
   if (( ! $# )); then
     echo beetag: error expected a query arg >&2
@@ -617,17 +706,9 @@ beetag() {
   volume=70
   read_wait=2
   doplay=true
-  # because we were destined to run out of single key buttons.
-  rare_genres=(
-    jazz
-    musical
-    noise
-    skit
-    spoken-w
-  )
 
   last_genre_i=$(( ${#common_genres[@]} - 1 ))
-  buttons=( {a..p} {r..w} 0 {6..9} , . / )
+  buttons=( {a..p} {r..w} {6..8} , . / - "=")
   button_map=(${common_genres[@]} ${pl_tags[@]})
   fstring=
   for tag in "${pl_tags[@]}"; do
@@ -638,37 +719,122 @@ beetag() {
     button_i[${buttons[i]}]=$i
   done
 
-  # TODO: use shuf --random-source=FILE to save the random sort order
-  # for reusing later, so we can pickup where we left off in a playlist.
+  # note: this structure of files is rather haphazard.
+  seed_num=1 # later we might want a few
+  seed_file=seed$seed_num
+  if $random; then
+    pl_state_file=$seed_num
+  else
+    pl_state_file=sorted
+  fi
+  pl_state_dir=/i/info/pl-state
+  if [[ $playlist ]]; then
+    pl_state_dir=$pl_state_dir/nopl
+  else
+    pl_state_dir=$pl_state_dir/$playlist
+  fi
+  pl_state_path=$pl_state_dir/$pl_state_file
+  pl_seed_path=$pl_state_dir/$seed_file
+
+
+  if $new_random || [[ ! -r $pl_seed_path ]]; then
+    mkdir -p $pl_state_dir
+    { base64 < /dev/urandom | head -c 200 ||:; echo; } > $pl_seed_path
+  fi
 
+
+
+  # PijokVipiotOzeph is just a random string for a delimiter
+  fmt='%ifdef{rating,$rating }'"$fstring"'$genre | $title - $artist - $album   $length  $id PijokVipiotOzeph $path'
   # shellcheck disable=SC2016 # obvious reason
-  mapfile -t initial_ls < <(beet ls -f '%ifdef{rating,$rating }'"$fstring"'$genre | $artist - $album - $title   $length  $id' "$@" | { if $random; then sort -R; else cat; fi; } )
-  j=0
-  # i only care to see the head of the list.
-  head_count=$(( LINES - 20 ))
+  tmpstr=$(beet ls -f "$fmt" "$@" | { if $random; then sort -R --random-source=$pl_seed_path; else cat; fi; } )
+  mapfile -t initial_ls <<<"$tmpstr"
+  id_count=${#initial_ls[@]}
   for line in "${initial_ls[@]}"; do
-    id="${line##* }"
+    path="${line#*PijokVipiotOzeph }"
+    # https://github.com/koalaman/shellcheck/issues/2171
+    # shellcheck disable=SC2190 # bug in shellcheck, looking at paths from an earlier function
+    paths+=("$path")
+    line_no_path="${line% PijokVipiotOzeph*}"
+    id="${line_no_path##* }"
     ids+=("$id")
-    right_pad="${line%% |*}"
-    ls_line="$(printf %-11s "$right_pad")${line#$right_pad}"
+    right_pad="${line_no_path%% |*}"
+    ls_line="$(printf %-11s "$right_pad")${line_no_path#"$right_pad"}"
     ls_lines+=("$ls_line")
-    if (( j < head_count )); then
+    i=$(( i+1 ))
+  done
+
+
+
+
+  j=0
+  if [[ $playlist ]]; then
+    if [[ -r $pl_state_path ]]; then
+      j=$(cat $pl_state_path)
+    fi
+  fi
+
+  # i only care to see a smallish portion of the list when starting.
+  head_count=$(( LINES - 20 ))
+  head_start=$(( j - head_count / 2 ))
+  if (( head_start < 0 )); then
+    head_start=0
+  fi
+  for (( i=head_start; i < head_count && i < id_count; i++ )); do
+    ls_line="${ls_lines[$i]}"
+    if (( i == j )); then
+      echo "* $ls_line"
+    else
       echo "$ls_line"
     fi
-    j=$(( j+1 ))
   done
-  id_count=${#ids[@]}
-  for (( j=0; j < id_count; j++ )); do
+  if $doplay; then
+    #{ mpv --profile=a --volume=$volume --idle 2>&1 & } 2>/dev/null
+    mpv --profile=a --volume=$volume --idle &
+    # if we dont sleep, can expect an error like this:
+    # socat[1103381] E connect(5, AF=1 "/tmp/mpvsock", 14): Connection refused
+    sleep .1
+  fi
+
+  while true; do
     id=${ids[j]}
+    path="${paths[$j]}"
     lsout="${ls_lines[j]}"
     tags=( ${lsout%%,*} )
     beetag-help
     printf "██ %s\n" "$lsout"
-    scrolled+=1
+    beetag-nostatus 1
     if $doplay; then
       # https://stackoverflow.com/a/7687716
       # note: duplicated down below
-      { beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
+      #
+      # notes on old method of invoking mpv each time:
+      # https://superuser.com/questions/305933/preventing-bash-from-displaying-done-when-a-background-command-finishes-execut
+      # we can't disown or run in a subshell or set +m because all that
+      # disabled job control from working properly in ways we want.
+      # todo: figure out some kind of answer to this. I think the solution
+      # is that we are waiting in 2 second intervals and checking if the
+      # background job exists. Instead, we should make mpv just idle
+      # when it is done with a song and then send it a command to play a new track.
+      #{ mpv --profile=a --volume=$volume "$path" 2>&1 & } 2>/dev/null
+      # old
+      #{ beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
+
+      # on slow systems, we may need to wait like .3 seconds before mpv
+      # is ready. so impatiently check until it is ready
+      if $first_play; then
+        first_play=false
+        for (( i=0; i<20; i++ )); do
+          if [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' 2>/dev/null | jq .data) == true ]]; then
+            mpvrpc '{ "command": ["loadfile", "'"$path"'"] }' 2>/dev/null
+            break
+          fi
+          sleep .1
+        done
+      else
+        mpvrpc '{ "command": ["loadfile", "'"$path"'"] }'
+      fi
+      erasable_line=false
     fi
     while true; do
       char=
@@ -679,7 +845,8 @@ beetag() {
         # Automatically skip to the next song if this one ends, unless
         # we turn off the autoplay.
         if (( ret == 142 )) || [[ ! $char ]]; then
-          if bg %% &>/dev/null; then
+          if jobs -p | grep -q . &>/dev/null && \
+              [[ $(mpvrpco '{ "command": ["get_property", "idle-active"] }' | jq .data) == false ]]; then
             continue
           else
             break
@@ -690,13 +857,10 @@ beetag() {
       fi
       beetag-help
       if [[ $char == $'\n' ]]; then
-        # https://stackoverflow.com/a/5722874
-        kill %%; wait %% 2>/dev/null ||:
         break
       fi
       case $char in
         ";")
-          kill %%; wait %% 2>/dev/null ||:
           j=$(( j - 2 ))
           break
           ;;
@@ -706,44 +870,43 @@ beetag() {
             doplay=false
           else
             doplay=true
-            kill %%; wait %% 2>/dev/null ||:
-            { beet play "--args=--volume=$volume" "id:$id" 2>&1 & } 2>/dev/null
+            mpvrpc '{ "command": ["loadfile", "'"$path"'"] }'
+            erasable_line=false
           fi
-          scrolled+=1
+          beetag-nostatus 1
           continue
           ;;
         _)
-          kill %%; wait %% 2>/dev/null ||:
           m beet rm --delete --force "id:$id"
-          scrolled+=4 # guessing. dont want to test atm
+          beetag-nostatus 4 # guessing. dont want to test atm
           break
           ;;
         [1-5])
           beetmq "id:$id" rating=$char
           continue
           ;;
-        -)
+        9)
           volume=$(( volume - 5 ))
           if (( volume < 0 )); then
             volume=0
           fi
-          echo volume=$volume
-          scrolled+=1
-          continue
-          ;;
-        q)
-          kill %%; wait %% 2>/dev/null ||:
-          return
-          ;;
-        +)
+          ;;&
+        0)
           volume+=5
           if (( volume > 130 )); then
             volume=130
           fi
+          ;;&
+        0|9)
+          mpvrpc '{ "command": ["set_property", "volume", '$volume'] }'
+          beetag-status
           echo volume=$volume
-          scrolled+=1
           continue
           ;;
+        q)
+          kill-bg-quiet
+          return
+          ;;
         y)
           if $do_rare_genres; then
             do_rare_genres=false
@@ -764,7 +927,7 @@ beetag() {
           continue
           ;;
         z)
-          scrolled+=3
+          beetag-nostatus 3
           # if we ctrl-z, it will put the whole function into sleep. so
           # basically, we can't return from a foregrounded mpv like we
           # would like to without some strange mechanism I can't think
@@ -776,17 +939,35 @@ beetag() {
           fi
           continue
           ;;
+
+        #
+        " ")
+          # output time if we aren't already paused
+          if [[ $(mpvrpco '{ "command": ["get_property", "pause"] }' | jq .data) == false ]]; then
+            # minutes/seconds
+            #date -d @"$(mpvrpco '{ "command": ["get_property", "playback-time"] }' | jq .data)" +%M:%S ||:
+            beetag-status
+            mpvrpc-percent-pos
+          fi
+          # originally found this solution, which worked fine.
+          #kill -STOP %% &>/dev/null
+          #
+          mpvrpc '{ "command": ["cycle", "pause"] }'
+          continue
+          ;;
         "$escape_char")
           expected_input=true
           read -rsn2 escaped_input
           skip_input_regex="^[0-9]+$"
           case $escaped_input in
-            '[D')
-              skip_lookback=5
+            # up char: show all the songs, use less
+            '[A')
               skip_start=0
+              skip_lookback=5
               if (( j - skip_lookback > skip_start )); then
                 skip_start=$(( j - skip_lookback ))
               fi
+              beetag-nostatus $(( id_count - skip_start - 1 ))
               {
                 line_int=0
                 for (( i=skip_start; i < id_count; i++ )); do
@@ -798,18 +979,20 @@ beetag() {
                   line_int+=1
                 done
               } | less -F
-              scrolled+=$(( id_count - skip_start - 1 ))
               ;;
-            '[C')
+            # down char
+            '[B')
               # skip forward, but show the last few songs anyways.
               skip_start=0
               skip_lookback=3
               if (( j - skip_lookback > skip_start )); then
                 skip_start=$(( j - skip_lookback ))
               fi
+              beetag-nostatus $(( id_count - skip_start - 1 ))
+
               line_int=0
               overflow_lines=$LINES
-              for (( i=skip_start; i < overflow_lines - 1; i++ )); do
+              for (( i=skip_start; i < overflow_lines - 1 && i < id_count; i++ )); do
                 ls_line="${ls_lines[i]}"
                 overflow=$(( ${#ls_line} / ( COLUMNS - 1 ) ))
                 overflow_lines=$(( overflow_lines - overflow ))
@@ -820,7 +1003,21 @@ beetag() {
                 echo "$line_int | $ls_line"
                 line_int+=1
               done
-              scrolled+=$(( id_count - skip_start - 1 ))
+              ;;
+            # left key
+            '[D')
+              seek_sec=-8
+              ;;&
+            # right key
+            '[C')
+              seek_sec=8
+              ;;&
+            '[C'|'[D')
+              beetag-status
+              mpvrpc-percent-pos
+              erasable_line=true
+              mpvrpc '{ "command": ["seek", "'$seek_sec'"] }'
+              continue
               ;;
             *)
               expected_input=false
@@ -828,9 +1025,9 @@ beetag() {
           esac
           if $expected_input; then
             read -r skip_input
-            case skip_input in
+            case $skip_input in
               q)
-                kill %%; wait %% 2>/dev/null ||:
+                kill-bg-quiet
                 return
                 ;;
             esac
@@ -841,7 +1038,6 @@ beetag() {
                 j=$(( j - 1 ))
               fi
             fi
-            kill %%; wait %% 2>/dev/null ||:
             break
           fi
           ;;
@@ -873,6 +1069,14 @@ beetag() {
         fi
       fi
     done
+    if (( j < id_count - 1 )); then
+      j+=1
+    else
+      j=0
+    fi
+    if [[ $playlist ]]; then
+      echo $j >$pl_state_path
+    fi
   done
 }
 
@@ -1081,7 +1285,7 @@ scr() {
 # tried to use ceb2txt but it failed because of schema
 # slightly different than what it expected.
 cheogram-get-logs() {
-  adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
+  #adb shell rm -r /storage/emulated/0/Download/Cheogram/Backup
   read -r -p "do cheogram backup on phone, do not enable extra cheogram data. press any key when done"
   cd /p/cheogram
   rm -rf Backup b
@@ -1112,9 +1316,17 @@ order by timeSent;"
 mycheologs() {
   local days q
   days=${1:-16}
+  # timezone compared to utc. note: this takes the current offset, so if daylight savings change
+  # happened in the looking back period, this won't account for it.
+  zone_offset=$(( $( date +%z | sed 's/[^1-9-]*//g' ) * 60 * 60))
+  case $zone_offset in
+    -*) : ;;
+    *) zone_offset="+ $zone_offset"
+  esac
+  echo zone_offset=$zone_offset
   q="
 select
- datetime(substr(timeSent,0,11),  'unixepoch'),
+ datetime(substr(timeSent,0,11) $zone_offset,  'unixepoch'),
  body
 from messages
 where timeSent > $(( (EPOCHSECONDS - days * 60 * 60 * 24) * 1000 ))
@@ -1785,7 +1997,65 @@ tl() {
   t s w
 }
 
-arbttlog() { arbtt-dump "$@" | grep -v '( )\|Current Desktop' | sed -rn '/^[^ ]/{N;s/^(.{21})([0-9]*)[0-9]{3}m.*\(\*/\1\2/;s/^(.{21})[0-9]*.*\(\*/\1/;s/\n//;p}' ;   }
+focus() {
+  /p/c/proc/focus/linux-amd64/focus &
+  watcharb5
+  kill %%
+}
+
+
+watcharb5() {
+  local char ret
+  killall arbtt-capture ||:
+  rm -f ~/.arbtt/capture.log
+  arbtt-capture --sample-rate=10 &
+  clear
+  while true; do
+    arb5
+    ret=0
+    # i first thought to sleep and capture ctrl-c, but it seems we can't
+    # capture control-c, unless maybe we implement the commands in a
+    # separate script or maybe add err-cleanup to err. Anyways, this
+    # method is superior because any single char exits.
+    read -rsN1 -t 5 char || ret=$?
+    if (( ret == 142 )) || [[ ! $char ]]; then
+      # debug
+      #e ret=$ret char=$char
+      :
+    else
+      killall arbtt-capture ||:
+      return 0
+    fi
+    clear
+  done
+
+}
+
+arb5() {
+  local i l sec
+  i=0
+  if [[ ! -e ~/.arbtt/capture.log ]]; then
+    sleep 5
+  fi
+  # https://stackoverflow.com/questions/56486272/how-to-concat-multiple-fields-to-same-line-with-jq
+  arbtt-dump -l 30 -t json | jq -r '.[] | [ ( .inactive  / 1000 | floor ) , ( .windows[] | select (.active == true) |.title) ] | @tsv' \
+    | tac | while read -r sec l; do
+    if (( i % 6 == 0 && i >= 2 )); then
+      echo == $(( i / 6 + 1 )) ==
+    fi
+    if (( sec > 10 )); then
+      printf "%3d %s\n" $sec "$l"
+    else
+      printf "    %s\n" "$l"
+    fi
+    i=$(( i + 1 ))
+  done
+}
+
+arbttlog() {
+  # from the log, show only the currently active window, and the number of
+  # seconds of input inactivity.
+  arbtt-dump "$@" | grep -v '( )\|Current Desktop' | sed -rn '/^[^ ]/{N;s/^(.{21})([0-9]*)[0-9]{3}m.*\(\*/\1\2/;s/^(.{21})[0-9]*.*\(\*/\1/;s/\n//;p}' ;   }
 
 idea() {
   /a/opt/idea-IC-163.7743.44/bin/idea.sh "$@" & r
@@ -2982,6 +3252,25 @@ vspicy() { # usage: VIRSH_DOMAIN
 wian() {
   cat-new-files /m/4e/INBOX/new
 }
+wakehours() {
+  local sec
+  if (( $# != 1 )) ; then
+    echo wakehours: error: expected 1 arg, got $# >&2
+    return 1
+  fi
+  sec=$(( EPOCHSECONDS -  $( date +%s -d $1am ) ))
+  printf "%d:%02d\n" $(( sec / 60 / 60)) $(( (sec / 60) % 60 ))
+}
+
+calvis() { # calendar visualize
+  install -m 600 /dev/null /tmp/calendar-bytes
+  while read l; do
+    for char in $l; do
+      printf "\x$(printf "%x" $char)" >>/tmp/calendar-bytes
+    done
+  done < <(grep -v '[#-]' /p/calendar-data)
+  /p/c/proc/calendar/linux-amd64/calendar
+}
 
 wtr() { curl wttr.in/boston; }