tags:

views:

308

answers:

4

I want to show the output of the Linux command dialog --menu from my C program so that the user can select an option from the menu. Also, the last line of output from the program is the option the user selected, so I need to capture that.

I've tried using popen() and system() to accomplish this, and looked on the web, but couldn't find anything that led to a successful result.

If I can't find a way to use dialog, I will have to use a much simpler approach (a simple "Type your choice and press enter") and it won't be as cool.

Thank you in advance, Brian

A: 

I'm sure I'm asking for a firestorm of criticism but nevertheless here is the basic idea. I didn't check this for compiler errors and didn't supply the header files. It's just a code snippet I whipped up while taking a drink.

Basically, you fork(), in the child process redirect stderr and call exec(), call waitpid() in the parent process and get the return value of "dialog," and if there was no error read the file to which you redirect stderr.

pid_t pid;
char cmd[] = "dialog";
char *args[] = {"dialog", "--menu", NULL};
int status;
int fd;

if((pid = fork()) == 0)
{
  /* child */

  /* I believe dialog prints to stderr the answer chosen 
   * also you should check the return value of open()
   */
  fd = open("some_file_name", O_WRONLY | O_CREAT | O_TRUNC, 0);

  close(STDERR_FILENO);

  dup(fd);

  execvp(cmd, args);
  perror("execvp()");
  exit(1);
}
else if(pid < 0)
{
  perror("fork()");
  exit(1);
}
else
{
  /* parent */

  /* you should also check the return of waitpid() 
   * this is just for example
   */
  waitpid(pid, &status, 0);

  /* if everything was ok then status has the return value 
   * also the file "some_file_name" should have the output
   */  
}
BobbyShaftoe
a reasonably simple solution. a possible enhancement would be `unlink()`ing the fd from the filesystem so that only the child and parent can access it, and using `mkstemp() to randomize the file name used
Matt Joiner
A: 

You could use pipes to get the standard output and give the child application input (controlled by your main application). You would need to fork and use a conventional exec rather than popen or system, however.

Matthew Iselin
+3  A: 

The dialog command prints the result of the user's selections on stderr. This means you will have to capture stderr, not stdout. This is a bit tricky. I'm going to go test this myself, but my guess is that the easiest thing to do is use popen like this:

FILE *dialog = popen("(dialog --menu plus other arguments >/dev/tty) 2>&1");

Then you can read from the dialog file (provided it's not NULL of course).

This works because the argument of popen is actually passed to an invocation of sh. This means that popen really runs sh -c <argument of popen> so all the standard shell redirections work. So you use parenthesis to get exactly what you want, which is for the dialog program itself to send its output to its controlling terminal, and for its stderr to be redirected to the place where you can read it with popen.

There is another method that has the same disadvantages as the popen solution, and has the additional disadvantage of requiring you to open and read another file after dialog finishes. But it has the advantage of simplicity. Unfortunately it also requires you to be able to write to the filesystem, and the most natural place to do that (/tmp) is fraught with security issues relating to making sure someone else doesn't somehow hijack your file. That method is to do system("dialog --menu plus other arguments 2>temp_file");. Then you read from temp_file when it's done.

These are both a bit ugly, especially since dialog takes a lot of arguments that are likely to have shell metacharacters in them. So even if the above work, I would highly recommend using a combination of pipe, fork, execvp, fdopen, and waitpid to get the result you're after.

A skeleton for that would look something like this:

#include <stdio.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int main(int argc, const char *argv[])
{
   const char *dia_args[] = {
      "dialog",
      "--output-fd",
      NULL,
      "--menu",
      "Hi there",
      "60", "15", "15",
      "t1", "i1",
      "t2", "i2",
      "t3", "i3",
      "t4", "i4",
      "t5", "i5",
      NULL
   };
   int pipefds[2];

   if (pipe(pipefds) < 0) {
      perror("pipe failed");
      return 1;
   } else {
      const pid_t child = fork();
      if (child < 0) {
         perror("fork failed");
         return 1;
      } else if (child == 0) {
         char pipefdstr[60];
         close(pipefds[0]);
         if (snprintf(pipefdstr, sizeof(pipefdstr) - 1, "%u", pipefds[1]) < 0) {
            perror("snprintf failed");
            return 1;
         } else {
            pipefdstr[sizeof(pipefdstr) - 1] = '\0'; /* Protect against bugs in snprintf */
            dia_args[2] = pipefdstr;
            execvp(dia_args[0], dia_args);
            perror("Unable to exec dialog command");
            return 1;
         }
      } else { /* child > 0 */
         FILE *dialog = fdopen(pipefds[0], "r");
         char inbuf[200];
         int waitresult = 0;
         if (dialog == NULL) {
            perror("Unable to fdopen");
            kill(child, SIGKILL);
            return 1;
         }
         close(pipefds[1]);
         while (fgets(inbuf, sizeof(inbuf) - 1, dialog)) {
            inbuf[sizeof(inbuf) - 1] = '\0';
            printf("Got [%s] from dialog.\n", inbuf);
         }
         fclose(dialog);
         if (waitpid(child, &waitresult, 0) < 0) {
            perror("waitpid failed");
            return 1;
         }
         if (!WIFEXITED(waitresult) || (WEXITSTATUS(waitresult) != 0)) {
            fprintf(stderr, "dialog exited abnormally.");
            return 1;
         }
      }
   }
   return 0;
}
Omnifarious
I used the first option, it worked excellently.Thank you very much.
HalfBrian
+1  A: 

well, i had done that pretty well. see the code sample below:

fp = fopen(SCRIPT_FILE, "w+");
fprintf(fp,
    "#!/bin/sh\\n\\n"
    "dialog --clear --title \""BRAND_INFO"\""
    " --inputbox \""TITLE"\\n\\n"
    "Please input the name[/tmp/input]:\" "
    BOX_HEIGHT" "BOX_WIDTH" 2> "RESULT_FILE"\n");
fclose(fp);
system("bash "SCRIPT_FILE); 
fp = fopen(RESULT_FILE, "rt");
// here read the results from the RESULT_FILE 
//    into which dialog had written the results.
...

a little boring plus a little perfromance loss but doable for all components of dialog such as checklist and menu etc.

You could control all details of the scripts which would be stored in the SCRIPT_FILE, and the overall action and flow controlling is simple.

EffoStaff Effo