Creating User Interfaces with dialog and xdialog
Another slight problem with yad
is that you can only use it on machines that have a desktop environment installed. But, many Linux, Unix, and Unix-like servers are set up with a full text-mode environment and don’t have to use graphical desktops. Another slight problem with yad
is that even on desktop-type operating systems, it’s not always available for installation. However, if yad
isn’t available and you still need a GUI solution, you might be able to use xdialog
, which is more universally available. Let’s begin with a look at dialog
, which can be used on text-mode machines.
The dialog Basics
dialog
used to be installed by default on all Linux systems, but it no longer is. However, it’s available for installation for pretty much every Linux distro. So, you can install it with your distro’s normal package manager. On most Unix and Unix-like systems, such as OpenIndiana and FreeBSD, it still comes installed by default.
The basic dialog
building blocks are known as widgets. Each widget has a set of parameters that you need to specify when you use it. For example, open the dialog
man page and scroll down to the --msgbox
paragraph, where you’ll see this:
--msgbox text height width
So, this widget requires you to specify three parameters. (Unlike yad
, specifying the dimensions of a dialog
box isn’t optional.) Let’s see how that looks in the dialog-hello.sh script
:
#!/bin/bash
dialog --title "Dialog message box" \
--msgbox "\n Howdy folks!" 6 25
As you’ve seen before in the yad
section, I used a backslash to break the command into two lines, which makes things a bit more readable. On the --msgbox
line, we see the message text, the height as defined by the number of rows in the box, and the width as defined by the number of characters that fit on a line.
Running the script looks like this:
Figure 16.11: Running the dialog-hello.sh script
To close the window, just hit the Enter key, which activates the OK button by default. (On a desktop machine, you also have the option of clicking on the OK button with your mouse.) Once the window is closed, run the clear
command to remove the blue background from your terminal.
By default, dialog
windows always appear in the center of your terminal, and the blue background won’t clear until you clear it yourself. Fortunately, you can change both behaviors. Here’s a slightly modified version of our script, which places the window at the top left corner of the terminal:
#!/bin/bash
dialog --title "Dialog message box" \
--begin 2 2 \
--msgbox "\nHowdy folks!" 6 25
clear
Here’s how that looks:
Figure 16.12: Placing the dialog box in the upper-left corner
The --begin
option takes two parameters. The first one denotes the vertical position of the box, and the second one denotes the horizontal position. For example, by changing the line to --begin 15 2
, the box would show up in the lower left corner. To place it into the upper right corner, you could change it to --begin 2 50
. The clear
command at the end will clear away the blue background when you hit the OK button.
Of course, this script isn’t very useful, but that’s okay. I’ll show you some more useful concepts in just a bit. First though, allow me to say a few words about xdialog
.
The xdialog Basics
If you need a GUI-type of user interface and yad
isn’t available for your desktop system, xdialog
could be a good alternative. (I say could be, because neither yad
nor xdialog
is available for OpenIndiana.) It should be in the normal repository of your Linux or Unix-like distro, so just use your normal package manager to install it. The reason I can talk about both dialog
and xdialog
in the same section is because for the most part, code that’s written for dialog
can also run with xdialog
.
There’s one thing to look out for that could trip you up. For some strange reason, the package name is xdialog
, in all lower-case letters. But, after it’s installed, you’ll need to invoke the program by typing Xdialog
, with an upper-case X. (After I installed it, it took me a while to figure out why I couldn’t get it to work.)
For the most part, changing a dialog
script to run as a GUI-type program is a simple matter of changing all instances of dialog
to Xdialog
, as you see here:
#!/bin/bash
Xdialog --title "Dialog message box" \
--begin 2 50 \
--msgbox "\nHowdy folks!" 6 25
clear
Running the script on a desktop system gives you this:
Figure 16.13: Running the script with xdialog
The first thing to note is that xdialog
ignores the --begin
option, and just places the box in the center of the terminal. There’s also the fact that xdialog
often requires you to create boxes with larger dimensions so that you can see everything. So, let’s change that in the final xdialog-hello.sh
script, like this:
#!/bin/bash
Xdialog --title "Dialog message box" \
--begin 2 2 \
--msgbox "\nHowdy folks!" 15 50
clear
As you see here, this makes things look much better:
Figure 16.14: The improved xdialog script
With xdialog
, the clear command at the end of the script is no longer necessary, but it doesn’t hurt anything to leave it. In fact, we’ll need it for the next demo.
Automatically Choosing Either dialog or xdialog
Now, here’s something that’s really cool. With only a few extra lines of code, you can make your script automatically detect whether it’s running on a desktop or text-mode machine, and whether xdialog
is installed. Here’s the xdialog-hello2.sh
script to show how it works:
#!/bin/bash
command -v Xdialog
if [[ $? == 0 ]] && [[ -n $DISPLAY ]]; then
diag=Xdialog
else
diag=dialog
fi
$diag --title "Dialog message box" \
--begin 2 2 \
--msgbox "\nHowdy folks!" 15 50
clear
There are several ways to detect if a program is installed. In Chapter 12, Automating Scripts with here Documents and expect, I showed you this method in the system_info.sh
script:
if [[ -f /usr/local/bin/pandoc ]] || [[ -f /usr/bin/pandoc ]]; then
pandoc -o sysinfo.pdf sysinfo.html
rm sysinfo.html
fi
The pandoc
executable that we needed is in the /usr/local/bin/
directory on FreeBSD, and in the /usr/bin/
directory on everything else. So, I set up this if..then
construct to detect if the executable is in either place. That works, but I’d now like to show you an easier way.
In the xdialog-hello2.sh
script, the command -v Xdialog
command detects if the Xdialog
executable file is present, regardless of which directory it’s in. If it is, the command will return exit code 0. If it’s not, the command will return exit code 1. To see how this works, go ahead and run this on the command line. Here’s how it looks if the Xdialog
executable isn’t detected:
donnie@ubuntu2204:~$ command -v Xdialog
donnie@ubuntu2204:~$ echo $?
1
donnie@ubuntu2204:~$
And, here’s how it looks if it is detected:
donnie@fedora:~$ command -v Xdialog
donnie@fedora:~$ echo $?
0
donnie@fedora:~$
In the next line, you see an if..then
construct that checks for two things. First, it checks the exit code from the command
command, and then it checks to see if a graphical desktop environment is installed. If the value of the DISPLAY
environmental variable is of a non-zero length, then a desktop environment is installed. You can see how this works by running the echo $DISPLAY
command yourself. Here’s how it looks on a desktop machine:
donnie@fedora:~$ echo $DISPLAY
:0
donnie@fedora:~$
On a text-mode machine, you’ll get no output at all, as you see here on this Ubuntu Server virtual machine:
donnie@ubuntu2204:~$ echo $DISPLAY
donnie@ubuntu2204:~$
For our purposes, we can say that the value of DISPLAY
is zero characters long on this text-mode machine.
Now, let’s take another look at our if..then
statement:
if [[ $? == 0 ]] && [[ -n $DISPLAY ]]; then
diag=Xdialog
else
diag=dialog
fi
This means that if the exit code from the command -v Xdialog
line is 0, and the value of the DISPLAY
environmental variable is of a non-zero length, then the value of the diag
variable becomes Xdialog
. If either the Xdialog
executable is missing or the value of DISPLAY
is not a non-zero length, then the value of diag
becomes dialog
. What makes this even more cool is that this works the same on all Linux, Unix, or Unix-like systems. I’ve tested this script on FreeBSD, GhostBSD, DragonflyBSD, OpenIndiana, and on both desktop and text-mode implementations of Linux. On all of them, the script correctly detects everything that it’s supposed to detect, and correctly chooses whether to run either dialog
or Xdialog
.
Here’s something that caused me a bit of consternation. I accidentally found out that the order in which you test for things sometimes matters. In the if..then
construct of this script, I originally checked for the value of DISPLAY
first, and then checked for the exit code of the command
command. The script wouldn’t run correctly like that, because the test for the DISPLAY
value was setting the exit code to 0. When I reversed the order of the tests, everything began to work correctly.
Go ahead and try running this script on a variety of systems, just to see what happens.
Next, let’s build on what we’ve already done by adding another widget.
Adding Widgets
You can add more functionality by adding more widgets. Take for example the dialog-hello2.sh
script, which you can download from the Github repository. I can’t show the entire file at once due to formatting constraints, so I’ll show it to you a section at a time. Here’s the top section:
#!/bin/bash
command -v Xdialog
if [[ $? == 0 ]] && [[ -n $DISPLAY ]]; then
diag=Xdialog
else
diag=dialog
fi
We’ve already seen this in the previous script, and I’ve already explained it. So, let’s move on. Here’s the next section:
$diag --title "Dialog message box" \
--begin 2 2 \
--msgbox "\n Hello world!" 20 50
$diag --begin 4 4 --yesno "Do you believe in magic?" 0 0
The first $diag
line creates the initial message box in the top left corner, as specified by the --begin 2 2
option. The second $diag
line creates a box with two buttons that are labeled as Yes and No. The --begin 4 4
option positions the yesno
box just slightly lower and slightly more to the right of where the initial message box was. After --yesno
, we see the text that we want the box to display and the height and width parameters. (Setting the height and width to 0 and 0 causes the box to size itself automatically.)
Next, we have a case..esac
construct that assigns commands to the two buttons. Remember that when clicked, the Yes button returns exit code 0, and the No button returns exit code 1. Pressing the Esc key returns exit code 255. We can use these exit codes to trigger a desired command, as you see here:
case $? in
0)
clear
echo "Cool! I'm glad that you do." ;;
1)
clear
echo "I'm sorry that you live such a dull life." ;;
255)
clear
echo "You pressed the ESC key." ;;
esac
Okay, all this script does is display a message when you press a button. But, it does serve to demonstrate the concept, so it’s all good.
You’ve already seen the initial message box, so I won’t show that again. Instead, I’ll show the yesno
box that comes up next:
Figure 16.15: The yesno box
When I click the Yes button, I’ll see the appropriate message, as you see here:
Figure 16.16: After clicking on the Yes button
And of course, this script runs equally well with xdialog
on a machine that supports it.
Next, let’s create something that will actually do some useful work for us.
Creating an SSH Login Interface
Let’s say that you have a fleet of Linux, Unix, or Unix-like servers that you need to administer remotely via SSH. Trying to keep track of the server IP addresses is a confusing mess, so you’ve decided to simplify things. You’ve decided to create the xdialog-menu-test.sh
to help you out. It’s also too long to show here in its entirety, so I’ll break it up into sections. Here’s the top part:
#!/bin/bash
command -v Xdialog
if [[ $? == 0 ]] && [[ -n $DISPLAY ]]; then
diag=Xdialog
else
diag=dialog
fi
This is the same as it was in the previous script, so you already know about it. Here’s the next section:
cmd=($diag --keep-tite --menu "Select options:" 22 76 16)
options=(1 "Remote SSH to Debian miner"
2 "Remote SSH to Fedora miner"
3 "Remote SSH to Fedora Workstation"
4 "Remote SFTP to Fedora Workstation")
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
The cmd=
line creates a command within a pair of parentheses, and then assigns that command to the cmd
variable array. This command, which will be invoked in the final line of the script, will build either a dialog
or xdialog
menu window. According to the dialog
man page, the --keep-tite
option is desirable when running the script in a graphical terminal emulator on a desktop machine, because it prevents the script from switching back and forth between the graphical terminal and the underlying shell terminal. After the --menu
option, we see the text that is to be displayed, the height and width of the menu box, followed by the maximum number of menu entries that will be displayed at a time. (In this example, the user would need to scroll down to see past the first 16 menu entries.) The menu entries would be designated with a tag and an item string. For example, the first menu entry we see has a 1 as the tag, and Remote SSH to Debian miner as the item.
Next, we have the options
stanza, which inserts all of the menu entries into an array. The choices=
line uses the 2>&1>
redirection operator to dump the contents of the array onto the terminal screen (/dev/tty
). The @
in ${options[@]}
is a form of variable expansion, because it allows an action to be carried out according to which menu item that a user chooses. The @
represents the index number of the array item that corresponds with a menu choice.
In Chapter 8, Basic Shell Script Construction, I showed you how to use either the *
or the @
in place of a specific index number to show all elements in an array. In the cmd=
line, each component of the $diag --keep-tite --menu "Select options:" 22 76 16
command that’s within the parentheses is a separate element of the cmd
array. For this reason, we need to use either the *
or the @
as the index for the cmd
array in the choices=
line at the bottom, so that the entire command will get invoked. The same thing is true for the list of options that get assigned to the options
array in the options=
line, as you’ll see in a moment.
Now that we have a menu, we need to make it do something. That comes in the next section, which you see here:
for choice in $choices
do
case $choice in
1)
ssh donnie@192.168.0.3
;;
2)
ssh donnie@192.168.0.12
;;
3)
ssh donnie@192.168.0.16
;;
4)
sftp donnie@192.168.0.16
;;
esac
done
This is nothing that you haven’t seen before. It’s just a normal for
loop that operates a case..esac
construct. What is a bit different is that the value of the choices
variable is the index number of the array item that corresponds with the chosen menu item. Each of the listed options in the options array consists of a number, followed by a complete phrase, such as 1 "Remote SSH to Debian miner"
. In this example, the number 1 represents element number 0 of the options array, and the following words in the phrase represent elements 1 through 5. In order for the options to properly display in the menu, you’ll have to invoke the array with either *
or @
in place of the index number. Here’s the complete line where that happens:
choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty)
Since only one index value is ever assigned to choices
, the for
loop exits after its first run.
Now that everything is built, let’s see if it works. Here’s how it looks using xdialog
on GhostBSD with the Mate desktop:
Figure 16.17: The xdialog-menu-test.sh script on GhostBSD
And, here’s the exact same script on Fedora with dialog
:
Figure 16.18: The xdialog-menu-test.sh script on Fedora with dialog
When you choose a menu item, the script will open a remote login prompt in the terminal, and then exit. That’s okay, because if you want to connect to multiple remote servers at once, you’ll need to open other terminals anyway.
Of course, you can fancy this script up to make it even more functional. For example, you can configure your SSH client to use different profiles or different encryption keys for different server sessions, and modify the commands in the menu accordingly. Or, you can use this as a template for something else altogether. As I keep saying, let your imagination run wild!
All right, I think that this about does it for yad
, dialog
, and xdialog
. Let’s wrap up and move on.