Find the answer to your Linux question:
Results 1 to 7 of 7
The commandline program CCC accepts commands from its stdin and generates results on its stdout. The source code is not available for CCC, only the binary. The GUI program GGG ...
Enjoy an ad free experience by logging in. Not a member yet? Register.
  1. #1
    Just Joined!
    Join Date
    Feb 2010
    Posts
    4

    How to wrap a GUI around a commandline program?


    The commandline program CCC accepts commands from its stdin and generates results on its stdout. The source code is not available for CCC, only the binary.

    The GUI program GGG uses its stdin and stdout for communication with CCC. GGG stdin connects to the output of CCC and GGG stdout connects to CCC stdin like pipes. GGG starts CCC. The source has to be written for GGG.

    GGG catches GUI events which are translated to to CCC input commands that are sent on GGG stdout and GGG stdin data goes to GUI widgets.

    I am not sure how to make this 'crossed-cable' type of connection between GGG and CCC.

    Here is an incomplete starter main for GGG:

    Code:
    int main ( int argc , char * argv[] )
       {
       pid = fork ( ) ;
       if ( pid == 0 ) // child process
          {
          execl ( "CCC" , ... ) ; // starts process for comandline CCC
          }
       else if ( pid != -1 )  // the gui process
          {
          init_GUI() ;
          // what goes in here for the crossed-cable stuff?
          }
       return 0 ;
       }
    Is the main above a reasonable approach? or are there better ways?

  2. #2
    Just Joined! djap's Avatar
    Join Date
    Jul 2005
    Location
    Beijing
    Posts
    99
    What are you using for writing the GUI?

    I have written one sort of GUI for mplayer with Qt and could post some examples about that later when I get back to my own computer.

    If it's not Qt you're using, I just might have some code for doing this by using standard popen() ( popen ).

  3. #3
    Just Joined!
    Join Date
    Feb 2010
    Posts
    4
    Yes, many thanks an example would be great!

    I will be using GTK+ for the GUI. Parsing/processing text coming as responses from the CCC commandline program and bringing up the widgets is not a problem, nor is handling GUI events and sending commands to CCC. The difficult bit is to set up the plumbing initially with the crossed pipes.

  4. #4
    Linux Guru coopstah13's Avatar
    Join Date
    Nov 2007
    Location
    NH, USA
    Posts
    3,149
    this question is worded very much like a homework question...

  5. #5
    Linux Newbie theNbomr's Avatar
    Join Date
    May 2007
    Location
    BC Canada
    Posts
    155
    I think you've got the fundamental part figured out. The missing ingredient would seem to be a way of communicating between the parent GUI and the child process. For this, pipes would seem to be appropriate.
    See Beej's Guide to IPC for examples and more detail (lookup with Google; my post count too low to post URLs).
    --- rod.
    Stuff happens. Then stays happened.

  6. #6
    Just Joined!
    Join Date
    Feb 2010
    Posts
    4
    theNbomr:
    Many thanks for the reference. It helped me to look in the right direction.

    Here is code that sets up plumbing to GDB;

    Code:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/wait.h>
    
    enum
       {
       MAXSZTEXT = 3000 ,
       IN = 0 ,
       OUT = 1 ,
       } ;
    
    
    //  protocol states
    
    typedef enum
       {
       IDLE_STATE ,            // not expecting any text
       WAIT_NOTIF_STATE ,      // wait for notification text
       WAIT_CMDRESP_STATE ,    // wait for command response text
       } PROTOCOLSTATE ;
    
    
    static PROTOCOLSTATE state = WAIT_NOTIF_STATE ;
    
    static const char * cccpath = "/usr/bin/gdb" ;
    static const char * cccargc0 = "gdb" ;
    static const char * cccargc1 = "/home/ken/projects/guiwrap/bin/Debug/guiwrap" ;
    static const char * cccprompt = "(gdb)" ;
    
    static void printfunbuf ( const char * prefix , const char * msg )
       {
       write ( STDOUT_FILENO , prefix , strlen(prefix) ) ;
       write ( STDOUT_FILENO , msg , strlen(msg) ) ;
       }
    
    static void err_sys ( const char * msg )
       {
       printf ( "System error: %s\n" , msg ) ;
       exit ( 2 ) ;
       }
    
    
    static void sig_pipe(int signo)
       {
       printf("SIGPIPE caught\n");
       exit(1);
       }
    
    
    static void gui_handletextfromccc ( char * textfromccc )
       {
       printfunbuf ( "Text from CCC: " , textfromccc ) ;
       }
    
    static bool readtoprompt ( int fd , char * textfromccc , int maxlen )
       {
       int lenprompt = strlen ( cccprompt ) ;
       int spaceleft = maxlen ;
       int lensofar ;
       int o = 0 ;
       int n ;
       while ( true )
          {
          n = read ( fd , textfromccc+o , spaceleft ) ;
          if ( n > 0 )
             {
             lensofar = o + n ;
             textfromccc [ lensofar ] = 0 ;
             if ( strncmp ( textfromccc + lensofar - lenprompt-1 , cccprompt ,
                            lenprompt ) == 0 )
                {
                return true ;
                }
             o = lensofar ;
             spaceleft -= n ;
             }
          else if ( n == -1 )
             {
             perror ( "Error reading CCC responses/notifications" ) ;
             break ;
             }
          else if (n == 0)
             {
             err_sys("CCC closed pipe");
             break;
             }
          }
       // will add timeout recovery later
       return false ;
       }
    
    
    int main( int argc , char * argv[] )
       {
       int n ;
       int parenttochildfd[2], childtoparentfd[2];
       pid_t   pid;
       char    textfromccc [MAXSZTEXT];
       char cmd [ 102 ] ;
    
       if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
            err_sys("signal error");
    
       if (pipe(parenttochildfd) < 0 || pipe(childtoparentfd) < 0)
            err_sys("pipe error");
    
       if ((pid = fork()) < 0)
          {
          err_sys("fork error");
          }
       if ( pid == 0 )  // child
          {
          close(parenttochildfd[OUT]);
          close(childtoparentfd[IN]);
          if (dup2(parenttochildfd[IN], STDIN_FILENO) != STDIN_FILENO)
             err_sys("dup2 error to stdin");
          close(parenttochildfd[IN]);
          if (dup2(childtoparentfd[OUT], STDOUT_FILENO) != STDOUT_FILENO)
              err_sys("dup2 error to stdout");
          close(childtoparentfd[OUT]);
          if (execl( cccpath , cccargc0 , cccargc1 , (char *)0) < 0)
             err_sys("execl error");
          }
       else if ( pid > 0 )  // parent
          {
          close(parenttochildfd[IN]);
          close(childtoparentfd[OUT]);
          while ( true ) // read responses or notificatons from CCC
             {
             if ( readtoprompt ( childtoparentfd [IN] , textfromccc ,
                                 MAXSZTEXT ) )
                {
                gui_handletextfromccc ( textfromccc ) ;
                state = IDLE_STATE ;
                }
             if ( state == IDLE_STATE ) // no gui yet - just type in gdb command
                {
                n = read ( STDIN_FILENO , cmd , 100 ) ;
                if ( n > 0 )
                   {
                   cmd [ n ] = 0 ;
                   printfunbuf ( "cmd: " , cmd ) ;
                   n = write ( parenttochildfd[OUT] , cmd , n ) ;
                   state = WAIT_CMDRESP_STATE ;
                   }
                else
                   {
                   err_sys ( "Bad stdin read") ;
                   }
                }
             }
          }
       return 0 ;
       }

    Eventually the GUI will generate the gdb commands and then there will have to be a gtk_main eventloop that passes control to gtk callbacks. The input pipe childtoparent is read in a while(true) loop above, but it is not possible to have two event loops. How to handle this?

    1. Put readtoprompt into gtkcallback

    Protocol = command-to-ccc, repsonse-from-ccc, command,response...
    for example for setting breakpt from the GUI
    Code:
    bool setbreakpointcallback (...) 
       {
       write ( parenttochildfd , breakptcmdtext,...);  // send command to GDB
       readtoprompt(...) ;  // blocks until command response text from gdb available
       // while blocked, gtkmain loop cannot process any gtk events, but there
       // is nothing else to do except waiting for the response. OK?  
       updateGUIdisplay();
       }


    2. Put readtoprompt into gtkmain loop

    I am uncertain about this:

    Code:
    int main (...)
       {
       //  bring up the widgets
       //  set callbacks
       ...
       gtk_idle_add (readtoprompt , userdata);  
       gtk_main();
       }
    The manual says the function passed in gtk_idle_add will be called when there are no (higher priority) events pending in the default main loop.

    If there is no data in the pipe, readtoprompt would block and the effect would be the same as in 1 above.

    I can make readtprompt non-blocking by using fcntl(childtoparentfd[IN]..) adding flag O_NONBLOCK. Then readtprompt would return immediately if there is no data and will be called in a busy loop until there is pipe data. Would the effect be that the cpu usage shoots up to 100%? Bad or acceptable?


    3. get pipe data via separate thread.

    Overkill?


    4. use SIGIO signal emitted on there-is-data on readtoprompt

    Tried, but could not make it work. The readtoprompt potentially contains several calls of read and I only want the signal handler to be called for the first call. I tried assigning an empty dummy signal handler from inside the original signal handler and tried setting flags to stop recursive calls, but it still got itself stuck. Abandoned.


    5. other ways?


    I need to make a choice.
    Comments would be most welcome.

  7. #7
    Linux Newbie theNbomr's Avatar
    Join Date
    May 2007
    Location
    BC Canada
    Posts
    155
    Okay, to restate your problem in summary, the read() call by the parent process blocks, effectively killing the event loop of the GUI. So, you need a method of making non-blocking reads. For this, the select() function should be your friend. You call select() periodically in the idle callback, and then only when it returns TRUE, do you call read() on the STDIN stream. No busy-loop chewing up CPU, and the GUI event loop continues to run.
    Hope I've understood your question correctly.

    --- rod.
    Stuff happens. Then stays happened.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •