0

This is quite naive but I'll give it a shot.
I would like to launch gimp from bash with gimp -i -b - & then read dbus signals in endless loop and post data obtained from these signals back to gimp I launched. The gimp -i -b - starts command line gimp and awaits for user input, like gnuplot etc. But is it possible to access its stdin from bash after command execution?

Ideally I would like something like that to work:

gimp -i -b - &

dbus-monitor --profile "..." --monitor |
while read -r line; do
gimp -b '(mycommand '$line')' &
done

gimp -b '(gimp-quit 0)' &

where all gimp cmd & are sent to same gimp instance. Would be even better if I could close gimp instance if it's not used for long enough and start again when it's needed.

Is it possible with bash without writing some daemon app?

truf
  • 2,843
  • 26
  • 39

1 Answers1

4

Simple Solution

You could us a simple pipe. Wrap the command sending part of your script into a function and call that function while piping its output to gimp:

#! /bin/bash

sendCommands() {
    dbus-monitor --profile "..." --monitor |
    while read -r line; do
        echo "(mycommand $line)"
    done
    echo "(gimp-quit 0)"
}

sendCommands | gimp -i &

sendCommands and gimp -i will run in parallel. Each time sendCommands prints something, that something will land in gimp's stdin.
If that's your complete script, you can omit the & after gimp -i.

Killing and Restarting Gimp

Would be even better if I could close gimp instance if it's not used for long enough and start again when it's needed.

This gets a bit more complicated than just using the timeout command because we don't want to kill gimp while it is still processing some image. We also don't want to kill sendCommands between the consumption of an event and the sending of the corresponding command.

Maybe we could start a helper process to send a dbus-event every 60 seconds. Let said event be called tick. The ticks are also read by sendCommands. If there are two ticks without commands in between, gimp should be killed.

We use FIFOs (also called named pipes) to send commands to gimp. Each time a new gimp process starts, we also create a new FIFO. This ensures that commands targeted at the new gimp process are also sent to the new process. In case gimp cannot finish the pending operations in less than 60 seconds, there may be two gimp processes at the same time.

#! /bin/bash

generateTicks() {
     while true; do
         # send tick over dbus
         sleep 60
     done
}

generateTicks &

gimpIsRunning=false
wasActive=false
sleepPID=
fifo=

while read -r line; do
    if eventIsTick; then # TODO replace "eventsIsTick" with actual code
        if [[ "$wasActive" = false ]]; then
            echo '(gimp-quit 0)' > "$fifo" # gracefully quit gimp
            gimpIsRunning=false
            [[ "$sleepPID" ]] && kill "$sleepPID" # close the FIFO
            rm -f "$fifo"
        fi
        wasActive=false
    else
        if [[ "$gimpIsRunning" = false ]]; then
            fifo="$(mktemp -u)"
            mkfifo "$fifo"
            sleep infinity > "$fifo" & # keep the FIFO open
            sleepPID="$!"
            gimp -i < "$fifo" &
            gimpIsRunning=true
        fi
        echo "(mycommand $line)" > "$fifo"
        wasActive=true
    fi
done < <(dbus-monitor --profile "..." --monitor)

echo '(gimp-quit 0)' > "$fifo" # gracefully quit gimp
[[ "$sleepPID" ]] && kill "$sleepPID" # close the FIFO
rm -f "$fifo"

Note that the dbus-monitor ... | while ... done is now written as while ... done < <(dbus-monitor ...). Both versions do the same thing in terms of looping over the output of dbus, but the the version with the pipe | creates a subshell which doesn't allow to set global variables inside the loop. For a further explanations see SC2031.

Socowi
  • 25,550
  • 3
  • 32
  • 54
  • Thank you @Socowi, that's an excellent answer! I just have 2 notes to add: Firstly I made a mistake and it's a `gimp -i -b - ` not just `gimp -i` who runs gimp and awaits for user input from stdin. Secondly, the last part of your answer is fits for any generic app, not only gimp. But in case of gimp we have one advantage - we can make it print warnings to stderr with `(gimp-message (string-append "The processing is done!"))`. So I guess we can force gimp to communicate the fact that it's in idle mode now. – truf Mar 17 '17 at 15:13
  • I think then I should be able to run something like `sendCommands | gimp -i -b - 3>&1 1>&2 2>&3 | processReply &` and grep message about processing end in`processReply()`. Thus among with the `gimpIsRunning` I should be able to get `gimpIsWorking` global varaiable and stick with `timeout` approach. Of course such approach is possible with gimp only and not general case. I wonder if result code will be simpler in this case? – truf Mar 17 '17 at 15:19
  • Good point. Unfortunately things get not much simplier when scanning gimp's stderr. Since we don't know the timeout in advance (it depends on the incoming jobs from the future) `timeout` is still of no use. `sendCommands | gimp ... | processReply &` can also not be used. We cannot stop and replace the gimp process in the middle of the pipeline with a new gimp process. Only benefit of scanning gimp's stderr: We could ensure that only one gimp process runs at a time. – Socowi Mar 18 '17 at 09:49