Introduction to GUI creation with Zenity

15/04/2009

It’s no big secret that I absolutely love Bash. It’s such a simple, yet versatile tool. Ever since I first discovered the power contained within it, I’ve written plenty of scripts to automate tasks that would otherwise take me a long time to do manually. Sometimes I write scripts so that my little brother can do certain things that would otherwise require him to use relatively complex command-line tools. But what would be the point of replacing a command-line tool with another command-line tool?

Scary terminal

Sometimes a graphical user interface is what’s needed - and that’s where Zenity comes into play!

Zenity is a rewrite of gdialog, the GNOME port of dialog which allows you to display dialog boxes from the commandline and shell scripts. In other words, it’s a super simple way of adding a GUI to your Bash scripts!

Summary

In this introduction I will walk you through the different type of dialog boxes that can be created with Zenity, as well as show you how you could implement some of them in a script.

  1. Introduction to Zenity
  2. The different types of dialogs
    1. Message dialogs
      1. Question
      2. Information
      3. Warning
      4. Error
    2. Text information
    3. Text entry
    4. Progress bar
    5. List
    6. File selection
    7. Notification icon
    8. Calendar
  3. Access keys
  4. Further reading
  5. Putting Zenity to use
  6. Source code


Introduction to Zenity

Zenity is a very simple toolkit that allows you to do two things:

  1. Get information from the user.
  2. Giving the user information.

It does this by providing you with a number of different dialog windows:

  • Message dialogs (question, information, warning, error)
  • Text information (what I like to call text box)
  • Text entry
  • Progress bar
  • List
  • File selection
  • Notification icon
  • Calendar

For example, in order to display a simple text message to the user, you would write:

zenity --info --text="Hello user! This is a text information dialog."

All the different windows support these options:

--title="title"

The window title to be used.

--width="width"

The default width of the window.

--height="height"

The default height of the window.

--window-icon="path/to/icon"

The icon that is displayed in the window decoration. There are also four stock icons that you can use by entering “info”, “warning”, “question” or “error” instead of a path to an icon.

Exit codes

Zenity has three different exit codes that it spits out whenever a dialog is exited. Of course, if you prompt the user to input text, after which the user pressed OK, the returning string won’t be 0, but instead the text the user inputed.

0 - Means the user has pressed OK or Close.
1 - Means the user has either pressed Cancel or used the window functions to close the window.
-1 - Error

The different types of dialogs


Message dialogs


Question

Example question dialog

zenity --question --title="Question" --text="This is an example question. Are you enjoying this article?

As you can see, we can’t change what’s on the buttons. So we can’t really make a yes or no question. However, these kinds of dialogs are typically used for confirmation dialogs (”Are you sure you want to remove youngblondeswithhuge.png?”). If the user presses OK the returning string will be 0. If the user presses Cancel the returning string will be 1.

Information

Example information dialog

zenity --info --title="Information Dialog Title" --text="This is an example information dialog. I tend to use this alot for providing the user with simple messages."

There’s really not much need for any exit codes with this one, as it’s just there to provide the user with textual information. But still, it can return 0, 1 or -1. Just like all other dialogs.

Warning

Example warning dialog

zenity --warning --title="Warning" --text="This is an example warning dialog"


Error

Example error dialog

zenity --error --title="Error" --text="This is an example error dialog."


Text information

Example text information

zenity --text-info --editable --title="Text information example" --filename="/full/path/to/file"

You can’t use the option –text with this one, unfortunantely. Instead you can either call it without any text by simply not using the –text or –filename options. Or you can use –filename to fetch the text from another file. The –editable option decides whether you want the text to be editable or not. If you choose to make it editable, it won’t return the normal exit codes, but instead it will return the edited text. So in this example, it would return “This is an example text information dialog. The text is fetched from an external file.” Note that if you edit the text and then close it, the file that you fetched the text from won’t be edited.  For that you need some more Bash magic. If you don’t want it to be editable, just omit the –editable option. It will then return the standard exit codes.

Text entry

Example text entry dialog

zenity --entry --title="Example Text Entry" --text="What is this?"

This is used to get textual information from the user. It returns 1 when cancel is clicked or the window is closed, or it returns the text entry if OK is clicked. If you don’t want the text to be shown, you can use the –hide-text option.

Progress bar

Example progress bar

#!/bin/bash
 (
 echo "10" ; sleep 1
 echo "# Doing lots of important stuff" ; sleep 1
 echo "20" ; sleep 1
 echo "# Still doing important stuff" ; sleep 1
 echo "50" ; sleep 1
 echo "About half-way done now" ; sleep 1
 echo "75" ; sleep 1
 echo "# Almost done" ; sleep 1
 echo "100" ; sleep 1
 ) | zenity --progress --title="Example Progress Bar" --text="Doing important things..." --percentage=0

The progress bar is kind of tricky. First of all it needs to be fed data from somewhere. In this case we’ve piped all those echos into Zenity to read. It reads the data line by line, looking for anything prefixed with a #. Whenever it finds that, the text is updated with the text after #. If a number is printed (without a prepending #) the percentage is updated to that number. So in this example the progress bar jumps to 10% and then changes the text to “Doing lots of important stuff”. After a second, it jumps to 20% and updates the text again. And so on.

When it’s done, the Cancel button is greyed out and the OK button becomes clickable. However, if you’ve used the –auto-close option, it automatically closes when it’s done. When you don’t quite know when it will be done, you can use the –pulsate option. That makes the progress bar pulsate until it reads an EOF character.

List

Example list dialog

#!/bin/sh

zenity --list \
--title="Your favorite websites" --height="180" --width="520" \
--column="Priority" --column="URL" --column="Description" \
 1 "www.blastfromthepast.se" "Great blog for anything techy" 2 "www.wikipedia.org" "Knowledge is everything" 3 "www.depraverad.nu" "Personal blog of Swedish super mega rapstar, Martin Hall"

For some reason, I find the lists even trickier than the progress bars. But once you get over that initial confusion, it’s not that hard at all. By using the option –column we define the name of a column. In this example I’ve defined three columns. After I’ve used all the options I want to, all I need to do is enter the list data in the correct order. Since I have three columns, every row will consist of three pieces of data in the same order as the columns.

You can also use lists to make radio- or checkboxes, so that the user can select the rows he or she wants, using –radiolist or –checklist. When used, it will return the data from the second column of the selected rows, separated by a pipe character. So if I were to replace my column “priority” with a column of checkboxes and check the first and last entries, it would return:

www.blastfromthepast.se|www.depraverad.nu

If you want the returned entries to be separated by something other than a pipe character, you can specify that with the –separator option. You can also make the list editable using the –editable option.

File selection

Example file selection dialog

(Example taken from BoxBlaze. Click for full view.)

zenity --file-selection --title="Please choose your iso"

The file selection dialog is great. It allows you to select multiple files (if using the –multiple option) and return their full file namnes and paths, separated by a character of your choice (default is a pipe character). You can also use the –directory option to have the user select only directories. If you use the –filename option you can specify what directory or file is selected when the dialog opens. Last but not least, you can use the –save option to put the dialog into save mode, where it… acts just like you’d expect a file save prompt to act.

Notification icon

Example notification icon applet

zenity --notification --window-icon="info" --text="Example notification icon."

(Note that it’s the leftmost icon in my example that is created by Zenity)

Notification icons can be nifty for quietly letting the user know that some task is finished, or that the user needs to interact with the script in some way. It doesn’t really have any special options, so generally you’re just using –window-icon and –text for this one. Remember that if you don’t have a special icon for it to use, Zenity has four stock icons available.

Calendar

Example calendar dialog

zenity --calendar --title="Example Calendar Dialog" --text="Please select a date"

I don’t think I’ve ever used the calendar. However, I suppose it has it’s applications sometimes (I guess it would be neat for configuring cronjobs). It’s quite simple to use. It has three options relating to what date is selected by default: –day –month and –year. They must all be specified by number (so to select October 31st 1990, you’d write –day=”31″ –month=”10″ –year=”1990″).

It also has a special option called –date-format. By default the calendar returns the selected date according to your locale. So for me being a Swede, it returns the date at the time of writing as 16/04/2009. However, if you’re in some other part of the world, the returning data may look completely different. That can complicate things when writing a script that is meant to be used anywhere in the world. That’s why we use have the –date-format option. By using that, we can set the way we want the returning data to be formatted. Valid formats are anything that is accepted by the strftime function For example, if I want today’s date returned as “Thursday 2009.04.16″ I’d write:

zenity --calendar --date-format="%A %Y.%m.%d"

Psst! Did you know that…

If you prepend a certain character in the –text option with an underscore (_) that character becomes an access key. So if you for example want the user to be able to press O instead of the OK button, just write:

zenity --info --text="Press _OK please."

To be honest, it’s one of those features that sound kind of neat, but that I’ve never actually used. There’s not all that much to read about it on the internet either, so the only real way of learning how to use them is by trying it out.

Further reading

While I’ve listed pretty much everything you can do with Zenity, you can still check out these links for further reading.

Putting Zenity to use

Now you know pretty much everything there is to know about using Zenity. However, I find that it’s alot easier to understand concepts like this if you can see what it looks like in a “real-world” example. That’s why we’re going to add a GUI to a CLI script using Zenity.

I’ve written a very simple script that allows the user to install packages either from repositories or from the local harddrive. It’s completely useless, but it provides us with something to build on.

Toggle source code visibility

#!/bin/bash
# A simple script to install packages either from the repositories or a local
# package.

## Functions
function welcome {
    # Welcomes the user and lets him or her choose between installing from
    # the repositories or from a local file.
    echo "Welcome to InStallman! Where do you want to install the package from?";
    echo "1) Repositories using apt"
    echo "2) Local .deb file"
    read CHOICE1

    case $CHOICE1 in
        1)
            packageSelector
        ;;
        2)
            localPackage
        ;;
        *)
            echo "Invalid choice!";
            echo "";
            welcome
        ;;
    esac
}

function packageSelector {
    # Promts the user to input a path to a package and then installs the
    # package from the repositories, using apt.
    echo "What is the name of the package you wish to install? (Type exit to exit)";
    read PACKAGE1
    if [ $PACKAGE1 = "exit" ]; then
        echo "Thank you for using InStallman!";
        exit
    fi
    echo "Are you sure you want to install $PACKAGE1? (y/n)";
    read CHOICE2

    case $CHOICE2 in
        [Yy])
            gksudo apt-get install $PACKAGE1
        ;;
        *)
            packageSelector
        ;;
    esac
}

function localPackage {
    # Prompts the user to input the path to a local .deb package and then
    # installs it using dpkg.
    echo "Please enter full path to package. For example /home/user/Desktop/package.deb":
    # The -e option treats the input as a file path, which allows for
    # auto-completion.
    read -e PKGPATH
    echo "$PKGPATH - is this correct? (y/n)"
    read CHOICE3
    case $CHOICE3 in
        [Yy])
            gksudo dpkg -i $PKGPATH
        ;;
        *)
            localPackage
        ;;
    esac
}

## Runtime
welcome

Remember how  I said that Zenity was for giving information to the user, and getting information from the user? With this in mind, we’re going to start from the beginning and replace the parts that just give the user information without asking for any with message dialogs.

In the very first case-statement we can replace the “invalid choice!” printout with a warning dialog, simply by replacing the echo with:

zenity --warning --text="Invalid choice!"

In the function packageSelector we can substitute the if-statement with:

if [ $? = 1 ]; then
    zenity --info --title="Quitting" --text="Thank you for using InStallman!"
    exit
fi

In the same function we can also replace the part where we ask if the user is sure about installing a package with a question dialog.

zenity --question --title="Confirmation needed" --text="Are you sure you want to install $PACKAGE1?"
case $? in
    0)
        gksudo apt-get install $PACKAGE1
        zenity --info --text="Installation is finished. Please use your package manager to see if the installation was successful.";
    ;;
    1)
        packageSelector
    ;;
    -1)
        zenity --warning --text="An unexpected error has occured. Now exiting."
        exit
    ;;
esac

We can do the same thing in the localPackage function.

zenity --question --title="Confirmation needed" --text="$PKGPATH - is this correct?"
case $? in
    0)
        gksudo dpkg -i $PKGPATH
    ;;
    1)
        localPackage
    ;;
    -1)
        zenity --warning --text="An unexpected error has occured. Now exiting."
        exit
    ;;
esac

Now we’re going to substitute the spot where we prompt the user for a package name with a proper text entry.

PACKAGE1=$(zenity --entry --title="Package name" --text="What is the name of the package you wish to install?")
if [ $? = 1 ]; then
    zenity --info --title="Quitting" --text="Thank you for using InStallman!"
    exit
fi

In the function localPackage we ask the user to input a file path. That sounds like a perfect job for a file selection dialog!

zenity --info --title="Instructions" --text="Please select package to install."
PKGPATH=$(zenity --file-selection)
if [ $? = 1 ]; then
    zenity --info --title="Quitting" --text="Thank you for using InStallman!"
    exit
fi

The only thing left to guify now is the main menu. Since that is a choice between three options, I suggest we use a list with radiobuttons for that.

CHOICE1=$(zenity --list \
    --title="Welcome to InStallman" \
    --text="Where do you want to install the package from?" \
    --radiolist \
    --column="" --column="Option" \
    FALSE "Repositories using apt" \
    FALSE "Local .deb file" \
    TRUE "Exit")

case $CHOICE1 in
    "Repositories using apt")
        packageSelector
    ;;
    "Local .deb file")
        localPackage
    ;;
    "Exit")
        zenity --info --title="Quitting" --text="Thank you for using InStallman!"
        exit
    ;;
esac

That’s it! In just a few minutes we’ve turned a command-line only script into a GUI application. Granted, it’s still not very useful or even full-featured, but it shows you how easy it is to add a GUI to any script you want.

Source code

Here’s the full source code for our guified example. It’s licensed under the “dowhatevertheheckyouwant” license.

Toggle source code visibility

#!/bin/bash
# A simple script to install packages either from the repositories or a local
# package.

## Functions
function welcome {
    # Welcomes the user and lets him or her choose between installing from
    # the repositories or from a local file.
    CHOICE1=$(zenity --list \
        --title="Welcome to InStallman" \
        --text="Where do you want to install the package from?" \
        --radiolist \
        --column="" --column="Option" \
        FALSE "Repositories using apt" \
        FALSE "Local .deb file" \
        TRUE "Exit")

    case $CHOICE1 in
        "Repositories using apt")
            packageSelector
        ;;
        "Local .deb file")
            localPackage
        ;;
        "Exit")
            zenity --info --title="Quitting" --text="Thank you for using InStallman!"
            exit
        ;;
    esac
}

function packageSelector {
    # Promts the user to input a path to a package and then installs the
    # package from the repositories, using apt.
    PACKAGE1=$(zenity --entry --title="Package name" --text="What is the name of the package you wish to install?")
    if [ $? = 1 ]; then
        zenity --info --title="Quitting" --text="Thank you for using InStallman!"
        exit
    fi
    zenity --question --title="Confirmation needed" --text="Are you sure you want to install $PACKAGE1?"
    case $? in
        0)
            gksudo apt-get install $PACKAGE1
            zenity --info --text="Installation is finished. Please use your package manager to see if the installation was successful.";
        ;;
        1)
            packageSelector
        ;;
        -1)
            zenity --warning --text="An unexpected error has occured. Now exiting."
            exit
        ;;
    esac
}

function localPackage {
    # Prompts the user to input the path to a local .deb package and then
    # installs it using dpkg.
    zenity --info --title="Instructions" --text="Please select package to install."
    PKGPATH=$(zenity --file-selection)
    if [ $? = 1 ]; then
        zenity --info --title="Quitting" --text="Thank you for using InStallman!"
        exit
    fi
    zenity --question --title="Confirmation needed" --text="$PKGPATH - is this correct?"
    case $? in
        0)
            gksudo dpkg -i $PKGPATH
        ;;
        1)
            localPackage
        ;;
        -1)
            zenity --warning --text="An unexpected error has occured. Now exiting."
            exit
        ;;
    esac
}

## Runtime
welcome

Take me back to the top!

Tagged: , in Code, Design
Show/hide

Written by Tommy Brunn

Tommy Brunn is the author of blastfromthepast.se. Currently he is living pretty much as close to the north pole as you can get (Luleå, Sweden). He devotes his spare time to learning about programming, developing a FOSS point-and-click adventure game, and is currently studying software engineering at Luleå University of Technology.

There are 5 comments on this article:

  1. 26/03/2010Sarahana said:

    Hi is there a way that we use zenity –info without ok button,i want to pop up an information when ever i click an icon

  2. 26/03/2010Tommy Brunn said:

    No, there is not. Without an ok button, there’s no way for the user to get rid of the window.

  3. 24/04/2010Drewsus said:

    @Sarahana
    You could try using notify-osd
    requires the package libnotify-bin

    then do: man notify-send
    to see its man page to find out how to use it

  4. 26/04/2010Alberto said:

    Tommy,

    Good article (content and presentation). Do you know if it possible to create a three-button zenity dialog with custom text for the buttons? For instance: “Stop Apache”, “Restart Apache”, “Cancel”.

    Thanks,
    Alberto

  5. 23/06/2010skvmb said:

    This is excellent. Thank you.

Write a comment