Close
0%
0%

Using Zenity for GUI Command Line Programs (in C)

I need a very simple GUI for my command line programs. Pipes and Zenity provide a reasonable solution.

Similar projects worth following
Dialog Designer
==============
The DeltaCad Dialog Designer was my first introduction to the GUI interface.
(Okay I have been living in a cave fro the last forty years!)
A few years ago I installed xForms and wrote my first full GUI application (gCodeSender).
I went with xForms because it was well documented and it had a Dialog Designer.
xForms is rather old and dated, there are many other candidate libraries they you could use today.

But what I want is something very simple that can be used ad-hoc. The use of pipes and Zenity was a good option for me. Simple but limited.

Pipes are built into C and Zenity is very likely already installed on your Linux OS (as it is used as a GUI for shell scripts).

Zenity

Zenity is a command line GUI that is probably already install on your Linux OS:

  which zenity

Should return:

  /usr/bin/zenity

If not you can install zenity with:
  sudo apt-get update -y

  sudo apt-get install -y zenity

Zenity Help

You can get help for Zenity by typing:

  zenity --help

To get topics, then for specifics:

zenity --help-forms

Testing if Zenity Exists

Here are the top few lines of my Zenity test program where I test if Zenity is installed:

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

int main(int argc, char *argv[])
{
  char line[256];
  FILE *fp;

  // Test if Zenity is installed (get version)
  fp=popen("zenity --version","r");
  if (fp==NULL) {
    perror("Pipe returned a error");
  } else {
    while (fgets(line,sizeof(line),fp)) printf("%s",line);
    pclose(fp);
  }
  ...

Pretty simple, open a pipe (popen()) and sent the command "zenity --version" via the pipe to the OS, and then read any output from zenity via the pipe, which should be the version number (in my case it was):

  3.32.0

 The pipe (i.e. fp) will return a NULL if not installed.

Is that it, let go!

No, not all results are via the standard output (i.e. stdout).

In the case of a button press, Zenity uses the return code, we can access the return code using:

  WEXITSTATUS(pclose(fp))

Here is the code:

  // Test Zenity Question Message Popup
  fp=popen("zenity --question \
    --title 'Confirm to Proceed' \
    --width 500 --height 100 \
    --text 'Proceed with Installation (Yes/No).' \
    ","r");
  if (fp==NULL) {
    perror("Pipe returned a error");
  } else {
    printf("Exit code: %i\n",WEXITSTATUS(pclose(fp)));
  }

The return codes are 0 for "Yes" and 1 for "No". Here is the GUI:

Something important to note:

Inside the double quotes (" ") I have use single quotes (' ').

This is the way C likes it, most Zenity examples are for shell scripts which don't mind  inside double quotes, more double quotes..

Here is the code for a Form:

  // Test Zenity Form Popup
  fp=popen("zenity --forms \
    --title='Add Member' \
	  --text='Member information' \
	  --separator=',' \
	  --add-entry='First Name' \
	  --add-entry='Family Name' \
	  --add-entry='Email' \
	  --add-calendar='Birthday' \
    ","r");
  if (fp==NULL) {
    perror("Pipe returned a error");
  } else {
    while (fgets(line,sizeof(line),fp)) printf("%s",line);
    printf("Exit code: %i\n",WEXITSTATUS(pclose(fp)));
  }

And the GUI:

Adding Other Components

This where we discover limitations. I have added a Combo-Box for Sex and a List Selection:

The main problem is there does not appear to be a way to initialise the Combo-Box or the List Selection. No big deal. There are other Widgets you can use but this is mostly all I need.

Here is the code:

  // Test Zenity Complex Form Popup
  fp=popen("zenity --forms \
    --title='Add Member' \
    --text='Member information' \
    --separator=',' \
    --add-entry='First Name' \
    --add-entry='Family Name' \
    --add-entry='Email' \
    --add-combo='Sex' --combo-values='Male|Female' \
    --add-calendar='Birthday' \
    --add-list='Program' \
    --list-values='Expert|Intermediate|Beginner' \
    ","r");
  if (fp==NULL) {
    perror("Pipe returned a error");
  } else {
    while (fgets(line,sizeof(line),fp)) printf("%s",line);
    printf("Exit code: %i\n",WEXITSTATUS(pclose(fp)));
  }

And here is the output:

  Alan,Cooper,AlanX@Hackaday.com,Male,15/02022,Intermediate

and the exit code was:

  0

which means "OK".

I hope you found this useful, AlanX

  • Porting a Visual Basic Excel Program to C and Zenity

    agp.cooper02/18/2022 at 04:56 0 comments

    Boat Offset Table

    I have an DeltaCAD macro for importing Boat Offset Tables from Excel as a CSV file. The macro creates 3D DXF models, unfolded panels and various sections.

    The problem is that DeltaCad is Windows only and is not 100% WINE compatible (no macro support).

    I ported the macro to Excel but I update my computer every few years so keeping a copy of Excel for Linux is a problem.

    Thus I am slowly migrating my code to "C". I have already written a DXF Out header library to export a "primitive" version of DXF. Therefore I use QCad or DeltaCad to import my DXF files.

    What I need is a simple GUI to input files names and parameters. (In the past I used a batch file, or xForms as a full featured GUI). Zenity is an acceptable GUI interface (not perfect but good enough).


    Here is the Zenity function:

    char** zenity(char* cmd,int n)
    {
      // create string array
      char** str=calloc(n+1,sizeof(char*));
      for (int i=0;i<=n;i++) *(str+i)=(char*)calloc(256,sizeof(char));
      char line[256]={0};
    
      // Run Zenity
      FILE* fp=popen(cmd,"r");
      if (fp==NULL) {
        // Error
        perror("Pipe returned a error");
        exit(0);
      } else {
        fgets(line,sizeof(line),fp);
        sprintf(*str,"%d",WEXITSTATUS(pclose(fp)));
      }
    
      // Remove non-characters
      for (int i=0;(line[i]>0);i++) if (line[i]<32) line[i]=0;
      int ptr=1;
      char* token=line;
      for (int i=0;(line[i]>=32);i++) if (line[i]==',') {
        line[i]=0;
        strcpy(*(str+ptr),token);
        ptr++;
        token=&line[i+1];
      }
      strcpy(*(str+ptr),token);
    
      return str;
    }
    

    It is short and to the point. The main limitation is that the maximum line length is 256 characters (including the end of line marker).

    Here is an example of use:

      // Test if Zenity is installed (get version)
      if ((fp=popen("zenity --version","r"))!=NULL) {
    
        // Get CSV Input File
        parameters=zenity("zenity --file-selection \
          --file-filter='Boat Offsets *.csv' \
          --title='Select a CSV File' \
          ",1);
        if (atoi(*parameters)==1) {
          // Cancel
          exit(0);
        } else {
          strcpy(offsetNamePath,*(parameters+1));
        }
    
        // printf("str ptr %ld\n",(long)parameters);
        // for (int i=0;i<=1;i++) {
        //   printf("%s %ld\n",*(parameters+i),(long)*(parameters+i));
        // }
        for (int i=0;i<=1;i++) free(*(parameters+i));
        free(parameters);
        ...
    

    The first zenity call "zenity --version" checks if zenity is installed.

    	if ((fp=popen("zenity --version","r"))!=NULL) {
    

     
    The second zenity call "zenity --file-section ..." gets a CSV file for processing:

        parameters=zenity("zenity --file-selection \
          --file-filter='Boat Offsets *.csv'
          --title='Select a CSV File' \
          ",1);

    The last parameter of the zenity call is 1, meaning only one parameter is returned, accessed as string "*(parameter+1)". Actually, two parameters are returned, the exit code is accessed as the string "*parameter". This code prints out the parameters:

        for (int i=0;i<=1;i++) {
          printf("%s\n",*(parameters+i));
        }

    Finally, when your finished with the parameter, you should free the memory space with:

        for (int i=0;i<=1;i++) free(*(parameters+i));
        free(parameters);

    What does the GUI look like?

    Here is the file selection GUI:

    And here is the Options form:

    The only problem with the form is that I cannot populate the entry boxes with default values.

    Reading Comma Separated Values (CSV) in C

    Reading CSV files is quite difficult as CSV is not really a standard. How should we read:

    1,"hello, ",,  A  B  ,

    1 Is the second comma part of the string of a separator?

    2 How should we deal with leading spaces?

    3 How should we deal with embedded spaces?

    4 How should we deal with trailing spaces?

    5 How should we deal with multiple delimiters?

    6 How should we dealt with end of line characters?

    My answer is that the embedded , is illegal and the ,, should read a NULL string.

    What is the problem with C input?

    Consider this fscanf(F1,"%d,%s,%s,%s",&i,str1,str2,str3); to import a line from a file. Note I don't care for the last value after str3. The %d will work fine, the next %s with read the remainder of the line!

    The problem is the %s. C does provide a way out:

      fscanf(F1,"%d,[^,],[^,],[^,]",&i,str1,str2,str3);...

    Read more »

  • Tokenising the StdOut String

    agp.cooper02/15/2022 at 10:33 0 comments

    Tokenising the StdOut String

    I first tried strtok() but it skips consecutive delimiters:

      char* token;
      // Get the first token
      n=0;
      token=strtok(line,",");
      while (token!=NULL) {
        printf("%d %s\n",++n,token);
        token=strtok(NULL,",");
      }
    

    So I wrote my own:

    // Tokenise Line
    // Note: Returns NULL or Space for absent data)
    // Note: A new array is created each time this function is called
    char** tokenise(char* line,int n)
    {
      char** str=calloc(n,sizeof(char**));
      int ptr=0;
      // Remove non-characters
      for (int i=0;(line[i]>0);i++) if (line[i]<32) line[i]=0;
      char* token=line;
      for (int i=0;(line[i]>=32);i++) if (line[i]==',') {
        line[i]=0;
        *(str+ptr++)=token;
        token=&line[i+1];
      }
      *(str+ptr++)=token;
    
      return str;
    }

    Now you just need to create you own parameter types from the strings.

    Here is the complete listing:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    char** zenity(char* cmd,int n)
    {
      // create string array
      char** str=calloc(n+1,sizeof(char**));
      for (int i=0;i<=n;i++) *(str+i)=(char*)calloc(256,sizeof(char));
      char line[256]={0};
    
      // Run Zenity
        FILE* fp=popen(cmd,"r");
      if (fp==NULL) {
        // Error
        perror("Pipe returned a error");
        for (int i=0;i<=n;i++) free(*(str+i));
        free(str);
        return NULL;
      } else {
        fgets(line,sizeof(line),fp);
           sprintf(*str,"%d",WEXITSTATUS(pclose(fp)));
      }
    
      // Remove non-characters
      for (int i=0;(line[i]>0);i++) if (line[i]<32) line[i]=0;
      int ptr=1;
      char* token=line;
      for (int i=0;(line[i]>=32);i++) if (line[i]==',') {
        line[i]=0;
        strcpy(*(str+ptr),token);
        ptr++;
        token=&line[i+1];
      }
      strcpy(*(str+ptr),token);
    
      return str;
    }
    
    int main(int argc, char *argv[])
    {
        char** list=NULL;
    
      list=zenity("zenity --forms \
          --title='Add Member' \
          --text='Member information' \
          --separator=',' \
          --add-entry='First Name' \
          --add-entry='Family Name' \
          --add-entry='Email' \
        --add-combo='Sex' --combo-values='Male|Female' \
          --add-calendar='Birthday' \
          --add-list='Program' \
          --list-values='Expert|Intermediate|Beginner' \
        ",6);
      if (list!=NULL) {
        for (int i=0;i<=6;i++) printf("%d %s\n",i,*(list+i));
        for (int i=0;i<=6;i++) free(*(list+i));
        free(list);
      }
      return 0;
    }
    

    So that is it, AlanX

View all 2 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates