简体   繁体   中英

Realize a console in a GTK3 GUI programming in C

I realized a GUI with GTK3 that, essentially, generates an input text file for an exe program that with these inputs can do elaborations. This exe is put in executions in the GUI by mean of a System call ( system("exe input.dat &") ).

This exe can print on screen message of information or error.

What I want to do is redirect these message on a GtkTextView.

The idea that I had is to redirect output and error on a file ( system("exe input.dat > output_file.txt 2>&1 &") ) and in the GUI read line by line this file and send this strings in the textView.

I was not sure that 2 process can write and read the same file and to test this concept I used these 2 simple programs: the writer (used like ./writer > out_file.txt):

      #include <stdio.h>
      #include <unistd.h>

      main()
      {
         int a;

         while(1)
         {
            fprintf(stdout,"a=%d\n",a);
            fflush(stdout);
            sleep(1);
            a++;
         }
      }

and the reader:

      #include <stdio.h>
      #include <string.h>

      int main()
      {
         FILE *fp;
         fp = fopen("out_file.txt","r");
         char string_new[1024];
         char string_old[1024];
         strcpy(string_old," ");

         while(1)
         {
            fgets(string_new,1024,fp);
            if ( strlen(string_new) != 0 )
            {
               if ( strcmp(string_new, string_old) != 0 )
               {
                  fprintf(stdout,"%s",string_new);
                  fflush(stdout);
                  strcpy(string_old,string_new);
               }
            }
         }
      }

This two programs run correctly and the second one print the output of the first one.

Putting in the GUI a similar code, the GUI read only the first line of the file.

How I can solve this issue? Thank you

You should use popen instead of executing system("exe input.dat &") , then it's easy to read from the stdout output of the program.

Like this:

#include <stdio.h>

int main(void)
{
    FILE *fp = popen("ls -lah /tmp", "r");
    if(fp == NULL)
        return 1;

    char buffer[1024];

    int linecnt = 0;
    while(fgets(buffer, sizeof buffer, fp))
        printf("Line: %d: %s", ++linecnt, buffer);

    putchar('\n');
    fclose(fp);
    return 0;
}

which outputs:

$ ./b 
Line: 1: total 108K
Line: 2: drwxrwxrwt  8 root    root     12K Mar 10 02:30 .
Line: 3: drwxr-xr-x 26 root    root    4.0K Feb 15 01:05 ..
Line: 4: -rwxr-xr-x  1 shaoran shaoran  16K Mar  9 22:29 a
Line: 5: -rw-r--r--  1 shaoran shaoran 3.6K Mar  9 22:29 a.c
Line: 6: -rw-------  1 shaoran shaoran  16K Mar  9 22:29 .a.c.swp
Line: 7: -rwxr-xr-x  1 shaoran shaoran  11K Mar 10 02:30 b
Line: 8: -rw-r--r--  1 shaoran shaoran  274 Mar 10 02:30 b.c
Line: 9: -rw-------  1 shaoran shaoran  12K Mar 10 02:30 .b.c.swp
Line: 10: drwx------  2 shaoran shaoran 4.0K Mar  9 20:08 firefox_shaoran
Line: 11: drwxrwxrwt  2 root    root    4.0K Mar  9 20:06 .ICE-unix
Line: 12: srwx------  1 mongodb mongodb    0 Mar  9 20:07 mongodb-27017.sock
Line: 13: prwx------  1 shaoran shaoran    0 Mar  9 20:08 oaucipc-c2s-1874
Line: 14: prwx------  1 shaoran shaoran    0 Mar  9 20:08 oaucipc-s2c-1874
Line: 15: drwxrwxr-x  2 root    utmp    4.0K Mar  9 20:06 screen
Line: 16: drwx------  2 shaoran shaoran 4.0K Mar  9 20:07 ssh-XueH0w8zWCSE
Line: 17: drwx------  2 shaoran shaoran 4.0K Mar  9 20:08 thunderbird_shaoran
Line: 18: -r--r--r--  1 root    root      11 Mar  9 20:07 .X0-lock
Line: 19: drwxrwxrwt  2 root    root    4.0K Mar  9 20:07 .X11-unix

If you need more control and want also to read stderr , then you would have to create pipes for stdout and stderr , make a fork and the child dup2 the pipes to stderr & stdout and then execute exec (or any other function of that family) to execute the program.

Like this:

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

int main(void)
{

    int stdout_pipe[2];
    int stderr_pipe[2];

    pipe(stdout_pipe);
    pipe(stderr_pipe);

    pid_t pid = fork();

    if(pid < 0)
        return 1;

    if(pid == 0)
    {
        // closing reading ends and duplicating writing ends
        close(stdout_pipe[0]);
        dup2(stdout_pipe[1], STDOUT_FILENO);

        close(stderr_pipe[0]);
        dup2(stderr_pipe[1], STDERR_FILENO);

        execlp("ls", "ls", "-alh", "a.c", "kslkdl", NULL);

        exit(1);
    }

    // closing writing end
    close(stdout_pipe[1]);
    close(stderr_pipe[1]);

    int status;

    if(waitpid(pid, &status, 0) < 0)
    {
        fprintf(stderr, "could not wait\n");
        return 1;
    }

    if(WIFEXITED(status) == 0)
    {
        fprintf(stderr, "ls exited abnormally\n");
        close(stdout_pipe[0]);
        close(stderr_pipe[0]);
        return 1;
    }

    puts("STDOUT:");
    char buffer[1024];

    ssize_t len;
    while((len = read(stdout_pipe[0], buffer, sizeof(buffer) - 1)) > 0)
    {
        buffer[len] = 0;
        printf("%s", buffer);
    }
    putchar('\n');
    close(stdout_pipe[0]);

    puts("STDERR:");

    while((len = read(stderr_pipe[0], buffer, sizeof(buffer) - 1)) > 0)
    {
        buffer[len] = 0;
        printf("%s", buffer);
    }
    putchar('\n');

    close(stderr_pipe[0]);


    return 0;
}

which outputs:

$ ./b 
STDOUT:
-rw-r--r-- 1 shaoran shaoran 3.6K Mar  9 22:29 a.c

STDERR:
ls: cannot access 'kslkdl': No such file or directory

Pablo's answer is correct, you need to use pipe(7) -s.

And you could probably use GTK & Glib's g_spawn_async_with_pipes (which is based on pipe and fork and execve on Linux) for that (instead of fork or popen ). In a GTK interactive program, it is better than the usual popen because the forked program would run concurrently with your event loop.

You could even consider using g_source_add_unix_fd on the (or on some) of the pipe fd-s given by pipe(2) or by g_spawn_async_with_pipes which use that pipe(2) call. But you might prefer g_io_channel_unix_new and g_io_add_watch

Be aware that the GTK main loop (and Gtk Input and Event Handling Model ), ie GtkApplication and the related g_application_run or the older gtk_main are an event loop around some multiplexing system call like poll(2) (or the older select(2) ) and you probably need that loop to be aware of your pipes. When some data arrives on the pipe, you probably want to read(2) it (and then call some GtkTextBuffer insert function).

You should make design choices: do you want the GUI interface and the other process to run concurrently? Or is the other exe process always so quick and with a small output (and no input) that you might just use popen ?

On current GUI applications, the event loop should run quickly (at least 30 or 50 times per second) if you want a responsive GUI app.

Look also for inspiration inside the source code of some existing free software GTK application (eg on github or from your Linux distro).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM