Packages | |
Gnome | https://www.gnome.org/ |
Gtk-Perl | CPAN, https://projects.prosa.it/gtkperl/ |
Gnome is the desktop environment of choice on my home Linux system because it's feature-packed and user friendly. Gnome is also flexible, and thanks to the Gtk-Perl module and associated desktop toolkit bindings I can use my favorite programming language to further customize and extend my Gnome environment.
This article shows how a useful Gnome tool can be be built in an afternoon. It is also an example of some common techniques one employs when doing this sort of GUI programming, including widget creation, signal handling, timers, and event loops. It also reviews some Perl basics. So read on and you may be inspired with notions of your own.
Gnome
On a Gnome desktop, the panel contains a variety of buttons and other widgets that launch applications, display menus, and so on. It's standard desktop fare, just like the Microsoft Windows Start menu.
An applet is a particular kind of Gnome application that resides within and operates on the panel itself. The Gnome distribution comes with several of these, such as a variety of clocks, the game of Life, and system resource utilization monitors. The Gtk-Perl module enables a Perl programmer to create custom Gnome panel applets.
The Gnome panel applet we'll build finds the local host's default TCP/IP gateway and affixes the gateway's status to the label of a button. When the button is in an OFF position, the gateway is not polled (Figure 1). When the button is ON, the gateway is polled at scheduled intervals and the button's label is updated with the result: response or non-response (Figures 2a and 2b). This diagnostic may be used to regularly and unobtrusively report the status of the local network relative to the machine's default gateway. Users can check the button's label to see how things are faring on the upstream network.
The applet uses the netstat and ping commands familiar to Unix users.
Figure 1
Figure 2a
Figure 2b Program Overview
The top-level code in the program (shown in Listing 1) is contained in lines 1 through 21; lines 23 through 68 establish subroutines. One subroutine is called by our code (fetch_gateway()
), but two others are callbacks (check_gateway()
and reset_state()
). A callback is a subroutine that will be called by the Gnome code when something happens, such as a timer expiring or a button clicking. Now, let's learn about how the application is set up.
Initialization
Line 3 indicates that we'll be using the Gnome module. Gnome.pm is distributed with the Gtk-Perl package. As of this writing, the latest version of Gtk-Perl on CPAN is 0.7003. Gnome.pm has to be installed separately: after downloading, unpacking, and installing Gtk-Perl, change directories into the Gnome distribution and install that too. If you want to develop panel applets (as we're doing here) you'll need to append the build option --with-panel
to the end of the usual perl Makefile.PL
portion of the install process:
perl Makefile.PL --with-panel
Although Gnome.pm hasn't made it to Version 1.0 yet, I've found it to be stable. The biggest problem is the lack of documentation.
Lines 5 and 6 initialize a new AppletWidget object in $a. This object is the container for all the doodads that will be part of our applet. Line 8 creates a label for use when our button is in the OFF position.
Line 10 creates a timer. The prototype for the Gtk->timeout_add()
function is:
Gtk->timeout_add( $interval, \&function, @function_data );
Here our interval is 20,000 (this value is in milliseconds, so the timer will go off every 20 seconds), and the function to be called when the timer goes off is check_gateway()
. We could use the third parameter to pass some data into the check_gateway()
function if it were appropriate to do so. In this case it isn't.
Line 11 creates a new ToggleButton object in $b
, and labels it OFF.
Line 12 registers the other callback in this application. This one, signal_connect()
, will be called when a particular signal occurs within Gnome. These aren't normal Unix signals like SIGINT
and SIGCHLD
, but specific GUI events. In this case, the event is a button click. The ToggleButton widget also has the signals "pressed", "released", "enter", and "leave", each of which is emitted in response to either mouse actions or direct function calls such as $b->pressed()
. In our application, Gnome will call reset_state()
whenever ToggleButton $b
is clicked.
Line 14 sets the button's size to be a square with sides of 50 pixels, a good fit for the default Gnome panel. Gnome references theme and style information in shared libraries which specifies how the button is to be drawn: line, color, shadow, and so on. Line 15 calls the button's show()
method, indicating that we're finished setting its attributes and that it is ready for display. Line 16 adds the button to the applet. Technically, we've packed the ToggleButton widget into the AppletWidget container by invoking the AppletWidget's add()
method on the ToggleButton. The ToggleButton is now a child of its container. In line 17, the applet is made visible by calling its show() method as well. A widget's children are not displayed until the parent's show()
method is invoked.
Line 19 calls the fetch_gateway()
routine to gather the local host's default TCP/IP gateway. In that subroutine, netstat -r
captures the local routing table. The comparison in line 28 forces a value into the scalar $hostname
when the first field within the captured text matches the string "default
". Finally, we translate this value to lowercase, so it will look better when we finally display it on the button. Then fetch_gateway()
returns.
At line 21 we're ready to hand off to the gtk_main
event loop, which is responsible for drawing the application on the screen and managing user interaction. At this point our only interface with the application will be through signal handling and callback functions. The Gnome Toolkit (GTK) is event driven: once we enter gtk_main
, the application stays put until an event occurs (caught via a signal) and the associated callback function is invoked. Therefore, we'd better have completed all of our initialization beforehand.
The Callbacks
Figure 3 Now let's examine the two callback functions: reset_state()
on line 35, which catches a "clicked" signal on our button (line 12) and check_gateway()
on line 43, which catches timer expirations (line 10).
On line 37, we query the state of the toggle button by invoking its get_active()
method. This returns 0 if the button is OFF and 1 if it's ON. By default, the ToggleButton widget has one child -- its own label. So we label the button with the contents of $off_label
if it is in the OFF position, or the string "Wait..." (shown in Figure 3) if it is the ON position, because we know that check_gateway()
is going to be called within the next 20 seconds. Part of check_gateway()
's job is to update the button with the status of our TCP/IP gateway, which is, after all, the whole point of this applet.
Line 47-51 store in $uphost
either the value in $hostname
or the string "gateway
", depending upon whether $hostname
is longer then eight characters. (Any longer than eight characters, and it won't fit comfortably within the button's label.)
If the button is in an ON position (line 54) we go ahead and attempt to ping
the gateway with a single ICMP packet. We only care about the return value of the ping
command and not its output, so we execute the command with system
. ping
returns 0 upon success (the gateway is alive) and something else if it fails (probably because the gateway is dead), so we check the result and update the button's label appropriately in lines 58-62.
We want check_gateway()
to continue to be called, every 20 seconds, until the application terminates. So at line 66, the function returns a true value. That true return value keeps the timer alive.
Note that 20 seconds is enough time to allow the call to ping to timeout and return a value to the application. It is also an appropriate level of resolution for this kind of discovery activity: if information about the status of my gateway is less than 20 seconds old, I'm happy. Your Mileage May Vary.
Conclusion
It's easy to write Gnome applets in Perl. This simple example showed you the basic elements of Gnome programming, including the event model and callbacks. Go forth and hack your own applet!
Joe Nasal is a Sr. Software Engineer with C-COR.net in State College, Pennsylvania.
packages used
Gnome https://www.gnome.org/ Gtk-Perl CPAN, https://projects.prosa.it/gtkperl/