Search code examples
command-line-interfaceposixzshsleepcurses

Alternative for sleep in a zsh sub shell inner loop


I use zsh (zsh 5.9 (x86_64-apple-darwin22.0)) and zsh/curses for a small command line interface. The content of the view should automatically adjust when the window size changes. I tried a solution with trap SIGWINCH, but with zsh (I tried it on Ubuntu and macOS) trap SIGWINCH obviously doesn't work. So I use a background shell with a busy loop (see monitorwindowsize below) that sends SIGUSR1 to the parent shell to signal it to resize the window. The only solution I've found to make the inner loop wait a while is to call sleep (e.g. call read -t 10 does not work here), but - you know - sleep creates a new process each time it's called. And of course I want to call the resize function as fast as possible, so I'm interested in keeping the sleep time as short as possible, i.e. calling sleep quite often. So my question is if there is an alternative for sleep under zsh that works in a sub-shell. Or is there a better solution than using the busy loop with this zsh/curses code.

The code:

#!/bin/zsh

zmodload zsh/curses

loop ()
{
    monitorwindowsize $1 &
    view
    while true; do
        read -t 1000
    done
}

view ()
{
    zcurses addwin mywin $(( LINES-4 )) $(( COLUMNS-4 )) 2 2 
    zcurses clear mywin
    zcurses border mywin
    zcurses refresh mywin
    zcurses move mywin $(( (LINES-4)/2 )) $(( (COLUMNS-8)/2 ))
    zcurses string mywin "Box"
    zcurses refresh mywin
}

resetview ()
{
    zcurses end
    tput clear
    zcurses clear mywin
    zcurses refresh mywin
    zcurses delwin mywin
    view
}

monitorwindowsize ()
{
    currentLines=$LINES
    currentColumns=$COLUMNS
    while true; do
      if (( currentLines != LINES || currentColumns != COLUMNS )); then
        currentLines=$LINES
        currentColumns=$COLUMNS
        kill -SIGUSR1 $1
      fi
      sleep 0.1
    done
}

TRAPUSR1() {
    resetview
}

TRAPINT() {
    for jobs in $jobstates ; do
      jobid=${${jobs##*:*:}%=*}
      kill ${${jobs##*:*:}%=*}
    done
    zcurses end
    exit
}

zcurses init
loop $$
for jobs in $jobstates ; do
  jobid=${${jobs##*:*:}%=*}
  kill ${${jobs##*:*:}%=*}
done
zcurses end

enter image description here


Solution

  • I have now found a way to make the SIGWINCH trap work. Tested with zsh 5.8.1 (x86_64-ubuntu-linux-gnu) (with ssh) and zsh 5.9 (x86_64-apple-darwin22.0):

    zmodload zsh/curses
    
    loop ()
    {
        view
        while true; do
            unset raw
            unset key
            zcurses timeout mywin 50
            zcurses input mywin raw key
            now=$(date)
        done
    }
    
    view ()
    {
        zcurses addwin mywin $(( LINES-4 )) $(( COLUMNS-4 )) 2 2 
        zcurses clear mywin
        zcurses border mywin
        zcurses refresh mywin
        zcurses move mywin $(( (LINES-4)/2 )) $(( (COLUMNS-8)/2 ))
        zcurses string mywin "Box"
        zcurses refresh mywin
    }
    
    resetview ()
    {
        zcurses end
        tput clear
        zcurses clear mywin
        zcurses refresh mywin
        zcurses delwin mywin
        view
    }
    
    TRAPWINCH() {
        resetview
    }
    
    TRAPINT() {
        zcurses end
        exit
    }
    echoti civis
    zcurses init
    loop
    zcurses end
    

    enter image description here

    I now use zcurses input instead of read. But the strange thing is that it also depends on now=$(date) (maybe another command works too). If now=$(date) is missing, the SIGWINCH trap will not work and the window will not be redrawn.

    So if now=$(date) is missing, it looks like this:

    enter image description here