[Perl]Modules From Scratch

There seems to be some interest - let’s do a Perl module.

What I want to do is implement a ring menu extension. I mean the cool circular icon menus from Neverwinter Nights, not the naff curses menus from lotus and informix.

I’ve not written this module yet, so I have no idea if this will work. if it does work, it may be too slow. If fast enough, it may be too clumsy to be useful. OK? Then let’s go.

A skeletal module in perl looks a bit like this:

[code]#! /usr/bin/perl -w
use strict;

get the directory with the FVWM perl mods,

then include the basic fvwm perl module

use lib fvwm-perllib dir;
use FVWM::Module;

my $module = FVWM::Module->new(
Debug => 1,
);
$module->eventLoop;[/code]

That’s a basic, goes-nowhere, does-nothing FVWM module written in perl. It’s a bit simpler than it’s written-in-C counterpart :slight_smile:

The first two lines tell the the OS to where to find the perl executable and turn on lots of useful error checking that’ll stop you making some really stupid mistakes. Then the “use” lines tell perl where to find the FVWM modules and to include the perl module that implements the basic FVWM module interface for Perl.

OK, I can see we better talk about the use of the word “modules”. Perl modules are files with chunks of perl inside. they end in “.pm” and get included in a program with the “use” command. Having explained the distinction, it should be fairly clear when I’m talking about perl mods and when I mean fvwm.

So, the “use” lines are about perl modules. The FVWM::Module line includes the FVWM::Module module, while the

use lib `fvwm-perllib dir`;

line tells perl where to look for it. Backquotes in perl work the same as they do in bash - the enclosed string gets executed as if from the command line, and the output of the command is substituted for the quoted expression. If I run

fvwm-perllib dir

I get

/usr/share/fvwm/perllib

with my command prompt tagged onto the end, because it doesn’t add a newline. So the “use lib” line says to perl to add /usr/share/fvwm/perllib (on my machine anyway) to the list of places to look for perl modules.

That fvwm-perllib command is useful for other things as well. Try

fvwm-perllib cat
fvwm-perllib cat FVWM::Module

For the rest of it:

my $module = FVWM::Module->new( Mask => $mask, Debug => 1, );

This creates an object that handles the FVWM module protocol. Most of the methods you need to write the module are available through this object.

Lastly:

$module->eventLoop;

Calls the module event loop. This is an infinite loop that waits for FVWM events to occur. It never returns.

When an event does occur, it looks to see if a handler routine has been defined for that event, if so, calls it. Since we haven’t defined any event handlers, this module does nothing expect sit there using up a slot in the process table. So, purely in the interests of testing, let’s add a bit of chatter.

Add this in just ahead of the eventLoop call:

#
# a bit of chatter to show its working
#
$module->showMessage("FvwmRingMenu starting up");

# add a handler for string events - this is the SendToModule event
#
$module->addHandler(
        M_STRING, sub {
                my $mod = shift;
                my $event = shift;
                my $text = $event->{ argValues }[3];
                $mod->showMessage( "Message Received: $text" );
        }
);

Now stick the code in a file called FvwmRingMenu and put the file
somewhere in your ModulePath and make it executable with

chmod +x FvwmRingMenu

Now you should be able to launch the module in the usual way. Type

Module FvwmRingMenu

in a console. There should be some output from FVWM. [ Hopefully you’re directing the WM output to a log file. If not it’ll be in .xsession-errors or on the virtual console where you typed startx ]. the output should look like this:

[FvwmRingMenu]: FvwmRingMenu starting up
[FvwmRingMenu]: Message Received: Boo

A Quick Tip: You can run modules that use fvwm-perllib from the command line. They don’t work since they have no connection to FVWM, but it’s a fast way of finding syntax errors in the script.

Right then: let’s take a look at that code. This bit is easy:

$module->showMessage("FvwmRingMenu starting up");

It’s little more than a print statement, but with the module name prefixed. Later on we may get to look at the Gtk toolkit for fvwm-perllib, in which case the same call will as-if-by-magic start generating popup error messages instead of text output.

The event handler needs a bit more explaining:

$module->addHandler(
        M_STRING, sub {
                my $mod = shift;
                my $event = shift;
                my $text = $event->{ argValues }[3];
                $mod->showMessage( "Message Received: $text" );
        }
);

The addHandler routine takes 2 arguments: an event and a subroutine reference. The events are defined using the strings defined on the FVWM module page. The subroutine reference…

Subroutine references (sometimes called coderefs) are like pointers to subroutines. By defining a sub without a name we create an anonymous subroutine reference. Sometimes it’s easier to create a reference with the backslash operator. We could have written the code above like this:

sub m_string_handler
{
        my $mod = shift;        # FVWM::Module object
        my $event = shift;      # FVWM::Event object
#
#       get the actual string
#
        my $text = $event->{ argValues }[3];
#
#       print it!
#
        $mod->send( "Echo 'Message Received: $text'" );
}
$module->addHandler( M_STRING, \&m_string_handler );

That gives a bit more room for comments. In practice people often use the anonymous coderefs for short handler funcs and the second form for more complicated handlers. You’ll see both sorts, so it’s useful to know about them.

However you define the subroutine, it gets called with two arguments. The first is the $module object which was used to set the handler. The second is a FVWM::Event object as defined in the fvwm-perllib documentation. The FVWM::Event is a little frustrating, mainly because a significant part of the man page just says “To Be Written”. On the other hand, it does have an argValues field with the parameters passwd to the event held in an array. To find out what parameters go with which event look here.

The other change I made in that function was to use

        $mod->send( "Echo 'Message Received: $text'" );

to print the message. This sends it’s argument to FVWM which interprets the string as a command. So this approach still prints the message, but it does it using the Fvwm Echo command. So now you know how to send commands to fvwm from perl.

And that seems a good place to leave it for now. We have a working, if useless, perl module. We can see it running and we’ve looked at how to handle events from the window manager, and how to send it commands.

For the next one I’ll see about reading some config data, and maybe look at generating a prototype menu.
[edit]
I meant to add: useful reading: the perl man pages. particularly perldata and perlobj. The object oriented tutorials are useful too.

[edit]
Formatting.

[color=red]Edited by theBlackDragon:
–> Split from Modules from scratch © and moved frome General Fvwm discussion[/color]

Right. Last post we got as far as a null module implemented in perl. The aim is a ring menu module. We’re going to need to define those menus before we can display them, so the next thing to look at is configuration.

If you’re read all the posts in this thread, you may remember how we got config data in C; send a request and catch the config lines from the main event loop. That would work here, but fvwm-perllib offers a better way of doing it.

First though, let’s look at the test config I want to be able to parse. I’m going to set up a test file to kill the module, destroy its configuration, reconfigure it and restart it. The idea is that I just need to type

read ring_menu

from a console to test the module. The file itself looks like this:

DestroyModuleConfig FvwmRingMenu: *
KillModule FvwmRingMenu

*FvwmRingMenu:  Menu TopLevel, Icon l33t_TER_term.png,                  \
                Mouse1 "Exec xterm",  Mouse3 "Module FvwmConsole"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_BRO_firefox.png, Mouse1 "Exec firefox"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_LOG_gentoo.png, Mouse1 "SubMenu Applications"
*FvwmRingMenu:  Menu TopLevel, Icon fvwm.png, Mouse1 "SubMenu Fvwm"
*FvwmRingMenu:  Menu TopLevel, Center l33t_DES_desktop.png, Mouse0 Exit

Module FvwmRingMenu

Icons for a given menu will be aranged equally in a circle with the first item defined at 12 O’Clock. If the keyword Center is used in place of icon, the the icon is placed in the center of the circle. I’d expect that to be used for exit/go back functionality, but creative designers may surprise me :slight_smile: Mouse actions defined what the menu option does for different mouse buttons, with SubMenu and Ecit being special commands to descent or ascend the menu tree. Clear? Don’t worry if not. For now all we need to do is parse it.

But we can’t parse it before we can read it. Which brings us back to trackers. Trackers are a way or mirroring the some aspects of the state of FVWM within the module. If you’re into software patterns, it’s an implementation of the Observer/Observable patter. Otherwise, they’re just lists of useful stuff which get automaically updated. You can create a tracker for the module config like this:

#
# let's have a config tracker
#
my $cfg_tracker = $module->track("ModuleConfig",
        ConfigType => "array"
);
read_config($cfg_tracker);

We also need to write the read_config sub. For a first cut, let’s just print the config so we can make sure we’re getting the right data.

sub read_config
{
        my $tracker = shift or                  # get the function parameter 
                die "read_config: expected a config tracker as argument"
        ;       
#        
#       the data method give us access to the tracker data
#       in this case we expect a list reference containing an array of strings 
#
        my $lref = $tracker->data;
#        
#       now we can loop through the list
# 
        for my $line (@$lref) {
                $module->showMessage($line);
        }              
}                      

If I run that from a console I get this:

[FvwmRingMenu]: FvwmRingMenu starting up
[FvwmRingMenu]: Menu TopLevel, Icon l33t_TER_term.png, Mouse1 Exec xterm
[FvwmRingMenu]: Menu TopLevel, Icon l33t_BRO_firefox.png, Mouse1 Exec firefox
[FvwmRingMenu]: Menu TopLevel, Icon l33t_LOG_gentoo.png, SubMenu Applications
[FvwmRingMenu]: Menu TopLevel, Icon fvwm.png, SubMenu Fvwm
[FvwmRingMenu]: Menu TopLevel, Center l33t_DES_desktop.png, Exit

Which looks about right.

Let’s try and parse all that. A good way to start is with an error message for unexpected options:

for (@$lref) {
        die "can't parse config: $_\n";
}

Since this means nothing works, we better add something to recognise the Menu keyword. let’s change the main loop in read_config like this:

for (@$lref) {
#
#       man perlre for more info regarding perl regular expressin
#
        /       ^Menu   # look for a line starting with "Menu"
                \s+     # followed by one or more white space chars
                (\w+)   # then a word - brackets mean the subpattern
                        # gets stored - in $1 in this case
                \s*,\s* # a comma, optional whitespace either side
                (.*)    # everything else into $2
        /xi and do {    # and make the comparison case insensetive
                add_to_menu($1, $2);
                next;
        };
        die "can't parse config: $_\n";
}

That recognises the menu, plucks the menu name out of the string and calls a sub with the menu name and the unprocessed part of the string as arguments.

The parsing process has two more levels of parser routine, neither of them adding anything new to what we’ve discussed this far; you can find the whole thing in the links at the end of the post.

Meanwhile it’d be nice to check that the config was parsed correctly as well as read correctly.

sub print_config
{
#
#       loop through the menus defined
#
        for my $name ( keys %menus ) {
                $module->showMessage("Menu: $name");
                my $menu = $menus{ $name };
#
#               if a center icon has been defined for this menu,
#               do that now
#
                if($menu->{ center }) {
                        print_icon_config("Center", $menu->{ center });
                }
#
#               loop through the remaining icons, printing them
#
                for my $icon ( @{ $menu->{ icons } } ) {
                        print_icon_config("Icon", $icon);
                }
        }       
}               

sub print_icon_config
{
        my $name = shift;       # "Icon" or "Center"
        my $conf = shift;       # an icon_def hash reference
#
#       print the type of icon and the icon filE
#
        $module->showMessage("  $name   $conf->{ name }");
#
#       loop through the buttons that have actions defined
#
        for my $button (sort keys %{ $conf->{ buttons } } ) {
                my $action = $conf->{ buttons }{ $button};
                $module->showMessage("          Button  $button $action");
        }
}

We can add a bit to the M_STRING handler to call that:

$module->addHandler(
        M_STRING, sub {
                my $mod = shift;
                my $event = shift;
                my $text = $event->{ argValues }[3];
                if($text =~ /^PrintConfig$/i) {
                        print_config();
                        return;
                }
                $mod->send( "Echo Message Received: $text" );
        }
);

Now I can type this into a console:

SendToModule FvwmRingMenu PrintConfig 

And get this:

[FvwmRingMenu]: FvwmRingMenu starting up
[FvwmRingMenu]: Menu: TopLevel
[FvwmRingMenu]:         Center  l33t_DES_desktop.png
[FvwmRingMenu]:                 Button  0       *Exit
[FvwmRingMenu]:         Icon    l33t_TER_term.png
[FvwmRingMenu]:                 Button  1       Exec xterm
[FvwmRingMenu]:                 Button  3       Module FvwmConsole
[FvwmRingMenu]:         Icon    l33t_BRO_firefox.png
[FvwmRingMenu]:                 Button  1       Exec firefox
[FvwmRingMenu]:         Icon    l33t_LOG_gentoo.png
[FvwmRingMenu]:                 Button  1       *SubMenu Applications
[FvwmRingMenu]:         Icon    fvwm.png
[FvwmRingMenu]:                 Button  1       *SubMenu Fvwm

[edit]

Whoops! Forgot the links.

Module Source
Config Script

We’ve got the configuration sorted out. Now it’s time consider the graphics. We have a number of options. There’s an X11 interface class we could use, as well as ones for Tk, Gtk and Gtk2. I’m thinking either GTK, or else to go really low level and do it in X11.

That’s not a descision we need to make right now though. I’d like to get the positioning and logic right before I start delving into X11 or GTK. So as an interim measure, I’m going to use FvwmButtons. Specifically, I’m going to use a number of FvwmButtons instances, each one with a single icon.

To get the idea across, here’s a sample FvwmButtons config that illustrates the idea. Stick it in $HOME/.fvwm, read it from a console and you should get something like this:

That’s a little rough, and it could use some sort of border around the icons, but it’ll do as a template. The next stage is to be able to generate those configuration commands in our module. Having the template will be useful there.

Before we can get to that, we’re going to need some more information. Like where the center of the circle will be. We can get that from the fvwm page data. There’s a tracker class to keep it up to date. Something like this:

        my $page_tracker = $module->track("PageInfo");
#
#       get the height and width of the current viewport
#
        my $vp_h = $page_tracker->data->{ vp_height };
        my $vp_w = $page_tracker->data->{ vp_width };
#
#       divide by two to get the center of the screen
#
        my $origin_x = $vp_w / 2;
        my $origin_y = $vp_h / 2;

One more diversion before we get onto the main issue: All these nested hashes and arrays are getting out of control. I want to take some time and convert them to use proper perl classes. Things are almost complicated enough to warrant creating separate perl modules for each class, but I’d sooner not get into all that just yet. So I’m going to cheat and use the Class::Struct module which lets me create classes without all the usual overhead.

So for configuration data I’m defining this:

struct(
        ConfigData => [
                offset_x        => '$',
                offset_y        => '$',
                radius          => '$',
                colorset        => '$',
                debug           => '$',
                menus           => '%',
        ]
);

That’s offsets from the screen centre for the origin of the ring, the radius of the ring, fvwm colorset to use, a debug flag, and a hash to hold the menus. Most of that lot are going to be set via config options.

Type "man Class::Struct for full details of the class.

Let’s have a look at the menu struct.

struct(
        MenuData => [
                name            => '$',
                center          => 'IconData',
                icons           => '@',
        ]
);

The menudata has a string, a lone IconData struct for the center icon, and then an array of them for the other icons. There’s no way to specify an array of classes under Class::Struct, so I just specified an array.

struct(
        IconData => [
                name            => '$',
                file            => '$',
                alias           => '$',
                actions         => '%',
        ]
);

Same principle: scalars for icon name, file and the alias to use for FvwmButtons and a hash for the actions.

struct(
        ActionData => [
                button          => '$',
                command         => '$'
        ]
);              

And a button number and a command string for the actions. Now we have a clear definition of what data we are storing and where, which can help enormously if you start losing track. We also have method calls to access the data. This not only means that we can write

        $cfg->{ menus }{ icons }[1]{ $button };

as

        $cfg->menus->icons(1)->$button;

but also, it lets us detect typos since the method name has to be correct. That can help too.

One last thing on the data structures: we initialise the setup like this

my $cfg = ConfigData->new(
        offset_x        => 0,
        offset_y        => 0,
        radius          => 100,
        debug           => ".*",
);

The values passed to new() are defaults for the named fields. The debug value isa pettern that has to match the first argument of the debug sub.

OK, enough objects, let’s let’s write a sub to make the menu pop up. We’ll call it by the very sensible name of popup_menu.

sub popup_menu
{
        my $menu_name = shift;
#
#       check the argument
#
        debug("popup_menu", "popup $menu_name\n");
        unless($cfg->menus($menu_name)) {
                $module->showMessage("popup_menu: no such menu: $menu_name");
                return;
        }
#
#       get the menu
#
        my $menu = $cfg->menus($menu_name);
#
#       plot those icons!
#
        if($menu->center) {
                $module->send("Module FvwmButtons ". $menu->center->alias);
        }
        for my $icon ( @{ $menu->icons } ) {
                $module->send("Module FvwmButtons ". $icon->alias);
        }
}

Well that was straightforward. Of course, reason it’s so simple is that we’ve pushed all the tricky bits into the configuration of the FvmwButtons aliases. Let’s have a look at that instead:

sub setup_menu
{
        my $menu = shift;
        my $name = $menu->name;
#
#       if there is a center icon, do that first
#
        if($menu->center) {
                setup_icon(
                        $cfg, $menu, $menu->center,
                        $cfg->offset_x, $cfg->offset_y
                );
        }
        # coming soon...
}

We’ll get to the code for setup_icon in a moment. The reason we’re adding the offsets from origin here is that the sub takes x and y co-ords, (relative to the center of the viewport), as arguments. Since this is the center icon, we just pass the offsets.

For the rest of the icons we need to convert angles (in 1/Nths of a circle) into cartesian co-ords. This is a job for the sine and cosine functions

#
#       get the number of icons
#
        my $n_icons = scalar( @{ $menu->icons } );
#
#       loop through them
#
        for(my $i = 0; $i < $n_icons; $i++) {
                my $icon = $menu->icons($i);
#
#               to get the co-ords of the icon relative to the origin
#               we use the sin and cos funcs. These however take radians
#               where 360 degrees = 2 * PI;
#
                my $rads = $i * 2 * PI / $n_icons;
                my $x = $cfg->offset_x + sin($rads) * $cfg->radius;
                my $y = $cfg->offset_y - cos($rads) * $cfg->radius;

                setup_icon($cfg, $menu, $icon, $x, $y);

Add that in place of the “coming soon” placeholder.

Now we need to look at the setup_icon subroutine. This is where all the complicated stuff finally happens. It’s a bit of a monster:

sub setup_icon
{
        my $conf = shift;
        my $menu = shift;
        my $icon = shift;
        my ($x, $y) = (shift, shift);

        die "usage: setup_icon(conf, menu, icon, x, y);" unless defined $y;
#
#       get the size of the viewport
#
        my $vp_h = $page_tracker->data->{ vp_height };
        my $vp_w = $page_tracker->data->{ vp_width };
#
#       circle origin in the middle of the screen
#       this should be evaluated at plot time so dial head setups
#       get a proper radius
#
        my $origin_x = $vp_w / 2;
        my $origin_y = $vp_h / 2;
#
#       let's have some shorthand variables. method invocation doesn't 
#       interpolate properly - which is to say that "$foo->bar" doesn't work.
#
#       You can make it work by writing "${ \( $foo->bar ) }" but IMO
#       the cure is worse than the disease.
#
        my $menu_name = $menu->name;
        my $icon_name = $icon->name;
        my $icon_file = $icon->file;
#
#       when you use code generation you can use ridiculously long
#       module names 'cos you never have to type them.
#
#       and it minimises potential conflicts with other modules.
#
        my $alias = "FvwmRingMenu-FvwmButtons-$menu_name-$icon_name";
#
#       this should be done when the config is read. Doing it properly,
#       however really should involve shunting a lot of this stuff into
#       perl class methods, and I'm putting that off till later
#
        $icon->alias($alias);
#
#       finally we get to the heart of the matter.
#       generate a config for a single button FvwmButtons setup
#
#       start by destroying any old config, or your menu buttons will
#       quickly look weird
#
        send_config("DestroyModuleConfig $alias: *");
#
#       No frame, and use the configged colorset
#
        send_config("*$alias: Frame 0");
        send_config("*$alias: Colorset " . $conf->colorset);
#
#       determine where the icon will be placed
#
        send_config sprintf("*$alias: Geometry  +%d+%d",
                $origin_x + $x,
                $origin_y + $y
        );
#
#       Now then: we need to generate an action string that can
#       contain an arbitary number of actions. So we need to build it up
#       over a loop.
#
#       start with the bits that don't repeat:
#
        my $action_str = "*$alias: (1x1, Icon '$icon_file', ";
#
#       now loop through the icons
#
        for my $action (values %{ $icon->actions } ) {
#
#               methods don't interpollate - we need shorthand
#
                my $button = $action->button;
                my $command = $action->command;
#
#               format the Action clause
#
                $action_str .= "Action(Mouse $button) ";
                $action_str .= "'$command'"; 
        }
#
#       close the bracket and send it to FVWM
#
        $action_str .= ")";
        send_config($action_str);
}

We need one more sub to make this work. Luckily it’s only a little 'un.

#
# wrapper for the send method - just adds a debug call to monitor the output  
#
sub send_config
{
        my $str = shift;
#
#       debug and call
#
        debug("send_config", $str);
        $module->send( $str );
}

How to invoke the menu? The obvious answer seems to be via the SendToModule interface. Something like:

SendToModule FvwmRingMenu PopUp TopLevel

I think we’re getting to the point where we could do with a un-inlining the M_STRING handler

$module->addHandler( M_STRING, \&m_string_handler);
sub string_handler 
{
        my $mod = shift;
        my $event = shift;
        my $text = $event->{ argValues }[3];
#
#       dump the configuration so we can see what's going on
#
        if($text =~ /^PrintConfig$/i) {
                print_config();
                return;
        }
        if($text =~ /^PopUp\s+(.*)/i) {
                my $menu_name = $1;
                popup_menu($menu_name);
                return;
        }
        $mod->send( "Echo Message Received: $text" );
}      

And with that we’re almost ready to test it. I thought I’d be a bit more ambitious than the five options listed earlier. This is the file I used to test the configuration:

DestroyModuleConfig FvwmRingMenu: *
KillModule FvwmRingMenu
KillModule FvwmButtons FvwmRingMenu-FvwmButtons-*

*FvwmRingMenu:  Offset 0,0
*FvwmRingMenu:  Radius 100
*FvwmRingMenu:  Colorset $[cset_root_trans]

*FvwmRingMenu:  Menu TopLevel, Icon l33t_TER_term.png,                  \
                Mouse1 "Exec xterm",                                    \
                Mouse3 "Module FvwmConsole"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_BRO_firefox.png,               \
                Mouse1 "Exec firefox"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_IMS_gaim2.xpm,                 \
                Mouse1 "Exec gaim"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_OFF_openoffice.xpm,            \
                Mouse1 "Exec ooffice"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_GRA_gimp.xpm,                  \
                Mouse1 "Exec gimp"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_UNK_rox.xpm,                   \
                Mouse1 "Exec rox"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_LOG_gentoo.png,                \
                Mouse1 "*SubMenu Applications"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_UNK_package_settings.png,      \
                Mouse1 "*SubMenu Fvwm"
*FvwmRingMenu:  Menu TopLevel, Center l33t_DES_desktop.png,             \
                Mouse0 *Exit

Module FvwmRingMenu

Style FvwmRingMenu-FvwmButtons-* !Title, !Borders, StaysOnTop, Sticky

SendToModule FvwmRingMenu PopUp TopLevel

And this is what it looks like. Some of the icons even work!

So, progress has been made.
Next time we’ll look at adding the remaining functionality to the menus.

Links:
As usual, my icons are taken from the gentoo icon set. The full source for this version of the module is on http://www.nymar.demon.co.uk/FvwmRingmenu.6.

As of last post, we had a module that could display icons and start some apps. We need make the menu vanish after being clicked, and we need to implement sub menus. Once those two features are in place we’ll have a functional and usable ring menu module.

Let’s tackle withdrawing the menu first. We need the FvwmRingMenu module to be informed of the click for this to work. the easiest way to do this is to have FvwmButtons send the action command back to FvwmRingMenu processing. This is getting complex enough to consider jumping ahead to a GTK/X11 implementation. On the other hand, we’re saving a lot of complexity as well, so I’ll stick to the plan for now.

So then: we need to send the actions back to FvwmRingMenu. Let’s define an “Action” command for our old friend SendToModule. The syntax will be

SendToModule FvwmRingMenu <menu-name> <mouse-button> <fvwm-command-sequence>

To process this we add this block to the m_string_handler func.

#
#       "Action" returns mouse clicks from icons
#       Send
#
        if($text =~ /^Action\s+(.*)/i) {
                process_click($1);
                return;
        }

Here I’ve checked for the keyword, but but the args. This means I have to parset them in the process_click func, but it also allows me to raise an error on missing arguments. That works like this:

sub process_click
{
        my $raw = shift;
#
#       scan the arguments
#
        $raw =~ /
                (\S+) \s+       # a sequencce of non spaces followed by spaces
                (\S+) \s+       # same again
                (.*)            # everything else
        /x or do {
                $module->showMessage("Malformed Action message: '$raw'");
                return;
        };

So we’re getting the raw command string from the arg list and then pattern matching it to separate out the parameters. The expression itself uses the extended syntax (hence the “x” after the trailing slash) which ignores whitespace unless denoted by \s and allows comments. It’s a bit easier to see what’s going on with this syntax. The capitalised version of \s reverses the meaning, so \S matches any non whitespace char. So I’m looking for two strings separated by whitespace, which will go into the variables $1 and $2. Everything after that goes into $3. man perlre for loads of detail on how regexps work in perl.

Having got the fileds, let’s store them somewhere safe

#
#       get the fields
#
        my $menu_name   = $1;
        my $button      = $2;           # not used for the time being
        my $command     = $3;

Then we close the menu (I’m going to defer that through to another function) and send the command to FVWM for execution.

#
#       first we close the menu
#
        withdraw_menu($menu_name);
#
#       now do the command
#
        $module->send($command);
}

I’m not using the button number here because I don’t need it: the information is implicit in the method triggered by fvwm buttons. All the same, it’s nice to have it handy. I mean I can’t think of a situation offhand where I’d want the different buttons to launch the same command with the same argument and still have different semantics, but that doesn’t mean I couldn’t happen.

Anyway, we need to write the withdraw_menu sub

sub withdraw_menu
{
        my $menu_name = shift;
#        
#       send a wildcarded killmodule command to fvwm
#
        $module->send(
                "KillModule FvwmButtons FvwmRingMenu-FvwmButtons-" .
                $menu_name .
                "-*"
        );      
}

Just like when we do it by hand. All we need to do now is modify the codegen stuff to send the new command to the RingMenu module and we can test it. This turns out to be quite easy. Find the loop at the end of setup_icon and change the last two lines to read like this:

                $action_str .= "Action(Mouse $button) ";
                $action_str .= "'SendToModule FvwmRingMenu Action ";
                $action_str .= $menu_name $button $command' "; 

A quick test of that shows it working, except for the submenus (which are still to do) and the terminal launcher. The latter turns out to be because I’m not priniting a comma after the first action. One way to fix this is to modify the start of the loop like this:

        my $sep = "";
        for my $action (values %{ $icon->actions } ) {
#
#               ensure comma separator
#
                $action_str .= $sep; $sep = ", ";

That’ll print an empty string before the first action, and a comma space before all the others. Another quick test and that now works.

One thing that works which I hadn’t expected to work is the center icon which was intended to close the menu without starting anything. The reason it’s closing the menu is that the command is being forwarded to FVWM without being checked, and since it starts with a star, it’s being taken as module config and accpted.This needs to be fixed.

The point of the star prefix incidentally, is to mark commands that should be interpretted by the module rather than fvwm. Currently the close icon sends *Exit. I’m going to change that to *Withdraw since a user might legitimately expect “Exit” to exit from the module.

Having done that, we need to hack the process_click sub:

#       
#       now do the command
#
        if($command =~ /^\*/(.*)) { 
                star_command($menu_name, $button, $1);
        }
        else {
                $module->send($command);
        }      

The general form of the star_command sub will be familiar by this stage:

sub star_command
{
        my $menu_name   = shift;
        my $button      = shift;
        my $command     = shift;

        SWITCH: {
                local $_ = $command;
                /^Withdraw\s*$/ and do {
                        # Do Nothing
                        return;
                };
        }
        $module->showMessage("unrecognized star command: $command");
}

As it turns out we don’t need any dedicated code for *Withdraw - the menu is
withdrawn by default. A quick test and that works.

Next we need to tackle the submenu issue. I had planned an “Applications” submenu, but I seem to have shoehorned all of my regular apps into toplevel. So I think I’ll do remote access subment.

I have four machines I reguarly access via SSH. I call them Victor, Frankie, Igor and Freddie. Surprisingly, the ever bountiful gentoo icon set doesn’t have pre-mades for these icons, so I had to roll my own:

I also need one for remote SSH access

Armed with these icons I can now change the old Applications menu entry to read

*FvwmRingMenu:  Menu TopLevel, Icon l33t_UNK_ssh.png,                   \
                Mouse1 "*SubMenu SSH"

and write the submenu config as well.

*FvwmRingMenu:  Menu SSH, Center l33t_UNK_ssh.png,                      \
                Mouse0 "*PopUp TopLevel"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_freddie.png,                    \
                Mouse0 "Exec xterm -e ssh -X fred"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_victor.png,                     \
                Mouse0 "Exec xterm -e ssh -X victor"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_frankie.png,                    \
                Mouse0 "Exec xterm -e ssh -X frankie"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_igor.png,                       \
                Mouse0 "Exec xterm -e ssh -X igor"

So far so good. Now I need a could of handlers for the star_command switch:

                /^SubMenu\s*(\S+)$/i and do {
                        popup_menu($1);
                        return;
                };

                /^Popup\s*(\S+)$/i and do {
                        popup_menu($1);
                        return;
                };

A quick test and it all seems to work as expected. Well, I’ve still not done the FVWM operations menu, partly because you can figure that out from the rest, but mainly because I can’t be bothered to hunt down or create suitable icons right now.

This is the submenu in all it’s maelific glory:

That’s about it for this session. We’ve now got a fully functional ring menu. Have a play and see what you think. Feedback is always welcome. The next jobs are probably theGTK conversion and before that, breaking the script into separate class files.

Here’s the full module source and the config file

Right then, It’s Refactoring Time! Time to get the code parcelled up ready for the GTK conversion

I don’t want to spend too long on this. Specifically, I don’t want to get sidetracked into writing an Object Oriented Perl tutorial. That’s partly because it’d be offtopic here, but mainly because there are better ones already written and distributed as part of the perl man pages. That’s not to say I won’t discuss the process - just that I’m going to have to assume a basic understanding of OO in Perl.

To start, let’s consider where to put these modules, excuse me, class files which we are about to create. If you “use” a module, perl looks in the directories specified in the PERL5LIB environment variable. This means we can create our own directory to hold these things. Based on a suggestion by Mikhael Goikhman I’m going for $FVWM_USERDIR/perllib.

Create the directory and add it to the PERL5LIB variable. You can do that in .bash_profile, or in .xinitrc/.xsession:

#
# don't use $FVWM_USERDIR in .bash_profile/.xinitrc/.xsession
# it probably won't be set. Unless you set it yourself, that is...
#
export PERL5LIB=$PERL5LIB:$HOME/.fvwm/perllib

If you can’t do that, maybe some boneheaded session manager that doesn’t honour any of these, then you should be able to set it in your fvwm config.

AddToFunc StartFunction I SetEnv PERL5LIB $[PERL5LIB]:$[FVWM_USERDIR]/perllib

Thinking ahead, it’d be useful to organsise the class files with one directory per fvwm module. Accordingly, I’m going to store all the .pm files for this project in $HOME/.fvwm/perllib/FvwmRingMenu.

Let’s extract the ConfigData class from our module. Put it in $FVWM_USERDIR/perllib/FvwmRingMenu/RingMenu.pm:

#! /usr/bin/perl -w
package FvwmRingMenu::RingMenu;
use strict;
use Class::Struct;

struct(
#
#       FVWM interface stuff
#
        module          => 'FVWM::Module',
        page            => 'FVWM::Tracker::PageInfo',
        config          => 'FVWM::Tracker::ModuleConfig',
#
#       Configuration data
#
        offset_x        => '$',
        offset_y        => '$',
        radius          => '$',
        colorset        => '$',
        debug           => '$',
        menus           => '%',
);

1;      # Important - needed by the "use" command

__END__

There’s a few things to note here. First, the change of name: this is going to be more than a simple bundle of config data, so I changed the name accordingly. Similarly, I added the FVWM interface objects into the class definition. I also removed the class name from the struct call - Class::Struct will use the package name.

At the end: the “1;” line ensures that this file returns boolean true to the use statement, without which the inclusion into the perl script will fail. After that END doesn’t do anything, but it does halt parsing of the file at that point. This means I can use the space thereafter for notes, code fragments that I don’t yet have a place for, test runs - even documentation if I’m feeling particularly dilligent :slight_smile:

I’m also retiring the debug data element since a quick scan of the Fvwm::Module man page reveals a built in method that’ll probably do it better. (I could invent a really good wheel, I often think, if it wasn’t for all these great mysterious round things cluttering the place up :))

Movingo on, let’s add a function to initialise some of those data elements. By convention, perl objects are intialised by a sub called “new”, just like C++ and its offspring. However, Class::Struct defines new(), and we cant override that without breaking the mechanism. So what I do is declare an init() sub and call that instead. Add this before the “1;” at the end of the module.

sub init
{
        my $class = shift;
#
#       get the interface object for FVWM
#
        my $module = FVWM::Module->new();
#
#       create the object and intialise the trackers
#
        my $self = $class->new(@_,
                module  => $module,
                page    => $module->track("PageInfo"),
                config  => $module->track(
                        "ModuleConfig", ConfigType => "array"
                ),
        );
#
#       some defaults if not supplied on the initaliser list
#
        $self->offset_x(0)      unless $self->offset_x;
        $self->offset_y(0)      unless $self->offset_y;
        $self->radius(100)      unless $self->radius;
        $self->debug("")        unless $self->debug; 
        $self->colorset(0)      unless $self->debug; 
#                                                   
#       and return the object                       
#                            
        return $self;        
}                    

Because I’m passing @_ to the $class->new invocation, that means that initialisaers passed to the init method will be honoured. Apart from that, everything shoudl be more or less straightforward.

Now let’s switch contexts back to the FvwmRingMenu module file. we need to change a few things. First we need to add the new class file to the use statements:

use FvwmRingMenu::RingMenu;

Then we can delete the definitions for $module, $page_track and $cfg_track, since they’re all in the new class. Having done this, the major task is going to be to find all the references to them and recode those parts. I’m not going to talk you through it line-by-line, but I do want to go through a few bits and pieces.

Anyway - finding references. I could just declare a new $module of type FvwmRingMenu::RingMenu, but that’d probably wind up introducing new bugs. Instead, let’s take the opportunity to tighten up the code with a main() function:

sub main
{
        my $ring_menu = FvwmRingMenu::RingMenu->init();
#
#       a bit of chatter to show its working
#
        $ring_menu->showMessage("FvwmRingMenu starting up");
#
#       read the configuration
#
        $ring_menu->read_config();
#
#       and the event loop
#
        $ring_menu->eventLoop;
}

main();

I’ve decided to forward unrecognised subroutine calls on FvwmRingMenu::RingMenu to the Fvwm::Module instance it contains - purely to save on the number of arrows we have to use. I’ve also decided that registering the M_STRING handler can be moved to the RingMenu class. The call to read_config stays here, but the method itself moves into the class file as well. By the time we’re finished, this sub may well be all that’s left in the original file.

After this point, it’s pretty much a question of fixing the errors and cutting and pasting code into the proper files. While I was at it, I took the opportuity to fix the geometry problem. The Icon class now stores offsets and the circle origin gets passed at plot time. This is useful because I want an option to create the menu under the pointer rather than in the screen centre.

Let’s leave it here for the time being. All the new source file are in this tarball. Next time I’ll start the process of converting all this to GTK.

And so to GTK.

What we’re about to try and do here is to add a chunk of code which we don’t understand very well to a newly debugged (and probably not very stable) piece of software. This can be a recipe for disaster. The solution is to get the new stuff working first and then merge it into the module. That way we minimise the number of unknowns we have to consider when debugging the end result.

Accordingly, this installment will look at writing a script that plots a single icon, at co-ordinates of our choosing. For a starting point, we’ll take the sampe script from the perl Gtk man page. (man Gtk to bring it up).

This is the script from the manual page, somewhat annotated.

#! /usr/bin/perl -w
use strict;
use Gtk '-init';
#
# create window and button objects
#
my $window = new Gtk::Window;
my $button = new Gtk::Button("Quit");
#
# register a handler for mouse clicks
#
$button->signal_connect(
        "clicked", sub {
                Gtk->main_quit
        }
);
#
# GTK treats widgets as containers. To get the button to appear in the window
# we need to add it into the window object
#
$window->add($button);
#
# make the window appear on the desktop
#
$window->show_all;
#
# and enter the GTK event loop
#
Gtk->main;

I created a directory called “lab”, and put the code above into a file called “plot icon”. Then I chmod +x’ed it and ran it. The result is a window like this:

It’s only a little window, so you may have to search for it on your desktop.

Now, we have a window up, but we want an icon rather than a string. As I mentioned earlier, GTK widgets are all containers. So if we want to put an image in the button, we need to create the image and then add it into the button. Here we come to one of the great frustrations of writing Perl-GTK code: the lack of detailed documentation. You’re all right coding in C, but for perl you end up with a list of method names and have to dig out the C function descriptions and then make allowances for the change in language. The best source for info is the GTK web site and the Gtk::reference and Gtk::cookbook man pages.

After some googling and man page searching, I managed to work out how to load a PNG file and display it. I’ve packaged this as a subroutine, purely so as to start thinking about how I’m going to use it in FvwmRingMenu itself. I doubt it’ll stay in this form, but it’ll do for now.

This is the subroutine:

sub plot_icon
{
        my $filename = shift;
        my ($x, $y) = shift, shift;
#
#       we start by reading the file into a pixbuf
#
        my $pixbuf = Gtk::Gdk::Pixbuf->new_from_file ($filename);
#
#       render and mask turns that into a pixmap and a mask for transparency
#       remember: perl can return lists - subs can have mutliple return values
#
        my ($pixmap, $mask) = $pixbuf->render_pixmap_and_mask(127);
#
#       However the pixmap returned is not the same thing as a pixmap widget
#       (I do not understand this yet). To make a pixmap widget suitable for
#       display we need to create one using the mask and the... err, pixmap.
#
        my $pixmapwid = new Gtk::Pixmap( $pixmap, $mask );
[code]
From here on, things are more or less the same as last time
[/code]
#       
#       create a window object. make it quit the main GTK loop if we click
#       the window close icon
#
        my $window = new Gtk::Window;
        $window->signal_connect( "delete_event", sub { Gtk->main_quit });
#
#       create a button containing the pixmap widget
#
        my $button = new Gtk::Button();
        $button->signal_connect( "clicked", sub { Gtk->main_quit });
        $button->add($pixmapwid);
#
#       add the button to the window and make the window appear on the desktop
#
        $window->add($button);
        $window->show_all;
}

Calling the subroutine is straightforward:

my $filename = "$ENV{ FVWM_USERDIR }/Scriptease/icons/l33t_LOG_tux.png";
 
#
# now we can plot the icon. the co-ords are ignored since I don't know
# how to do that bit yet
#
plot_icon($filename, 400, 400);
Gtk->main;

And it looks like this:

Well that’s a lot better. Shame the transparency is broken, really.

The best way to fix that seems to be with the Shape extension to X11. There’s even an example of this in the GTK tutorials and, wonder of wonders, a version of it in the GtkPerl tutorial. To fix the transparency, we need to make two changes. First of all, the window should be created like this:

#
#       (From the tutorial)
#       Create the main window, and attach delete_event signal to terminate
#       the application.  Note that the main window will not have a titlebar
#       since we're making it a popup.
#
        my $window = new Gtk::Window( "popup" );
        $window->signal_connect( "delete_event", sub { Gtk->exit( 0 ); } );
        $window->show();

The popup string is really weird. It causes the window to be displayed as a popup. In GTK terms, this apparently means that not only does the displayed window lack a titlebar, it appears be invisible to FVWM. No windowlist entry, no taskbar, nothing. FvwmIdent cannot see it and I don’t seem to be able to send it close or destroy signals from my window ops menu. In fact the only way I can get rid of it is to control-C the perl script. Not altogether a bad thing, since this is how I would menu elements icons to behave, but it’s a little disconcerting.

The other change we need to make involve using a “Fixed” widget to display the image. I’m not sure what makes a “fixed” different from a button, except that if I use a button, the transparent stuff turns up white. This section also has code to mask out the rest of the window, and to position our button on the screen.

#      
#       (back with the tutorial again) To display the pixmap, we use a
#       fixed widget to place the pixmap
#
        my $fixed = new Gtk::Fixed();
        $fixed->set_usize( 100, 100 );
        $fixed->put( $pixmap, 0, 0 );
#                                   
#       add the "fixed" into the window and show it
#                                      
        $window->add( $fixed );        
        $fixed->show();       
#                             
#       This masks out everything except for the image itself
#                                      
        $window->shape_combine_mask( $mask, 0, 0 );
#                                      
#       set position and show          
#                           
        $window->set_uposition( $x, $y );
        $window->show();               

This is what it looks like:

That’s not bad. To make it catch clicks we need to modify the widget’s event mask. After some more googling I settled on this:

        $fixed->add_events( [ 'button_press_mask' ] );
        $fixed->signal_connect( "button_press_event", sub { Gtk->exit( 0 ) });

We’re just about ready to start importing the code into FvwmRingMenu. Before we do though, there’s an experiment I want to try. I’ve mentioned on a few occasions that these menu icons could do with some sort of border to help them stand out against the background. Gtk::Gdk has some routines for image composition. So I might as well get this worked out in the context of my simple script.

Basically, I want to be able to specify a background pixmap, and to have the icon chosen composited on top of it. There’s a bit of work do be done to implement this as a general case, but for the time being, I’ll settle for giving my round icons a purple-and-black frame.

I found a nice gimp tutorial that described how to make nice 3D effect buttons. So I whomped up a background for my icons:

To composite them together, we need two Gtk::Gdk::Pixbuf objects. The sub to composite them looks like this:

sub composite_icon
{
        my $src_file = shift;
        my $dest_file = shift;
#
#       create two new pixbufs
#
        my $src = Gtk::Gdk::Pixbuf->new_from_file($src_file);
        my $dest = Gtk::Gdk::Pixbuf->new_from_file($dest_file);
#
#       these are the parameters to the composite routine.
#
        my $dest_x = 9;
        my $dest_y = 9;
        my $dest_width = $src->get_width();
        my $dest_height = $src->get_height();
        my $offset_x = 9;
        my $offset_y = 9;
#
#       do the compositing (composition?).
#
        $src->composite($dest,
                $dest_x, $dest_y,
                $dest_width, 
                $dest_height,
                $offset_x, $offset_y,
                1.0, 1.0,       # scaling factors
                0,              # interpolation type - I think
                255             # overall alpha
        );
        return $dest;
}

The best reference for composite appears to be this one. Look for the big picture. For all that, there’s a surprising degree of mystery attaches to the arguments. For instance if dest_ and offset_x are not the same, and likewise with the _y numbers, all sorts of odd effects happen. I only got this to work by virtue of brute persistence. If anyone has a better reference for this, I’d love to know about it.

Anyway, that code does the compositing. All that remains is to define our background sprite.

my $icon_file = "$ENV{ FVWM_USERDIR }/Scriptease/icons/l33t_LOG_tux.png";
my $back_file = "$ENV{ FVWM_USERDIR }/Scriptease/icons/menu_setting.png";

and to change the call that creates the pixbuf in plot_icon()

        my $pixbuf = composite_icon($filename, $back_file);

One last thing before we start integration. If you run the script as it stands, you might notice a flicker. The icon appears at the top left of the display (although that may be just be my placement policy) and then jumps to the desired location.

To fix this, remove all the ->show() lines and add a $window->show_all() at the end. There, I knew I’d figure it out eventually.

So, our objective for this session is complete. We have a GTK app capable of plotting icons to a given screen co-ord and ready for integration. I’d like to push on with that integration, but first I think I’ll post this installment as it stands.

That makes two installments in a row without any noticeable FVWM content. Next time I promise - back on topic.

No change to the module, but you can download the plot_icon script in its final form for reference purposes.

Next time, integration and maybe some more eye candy.

Time to integrate our GTK plot_icon script. Some big changes here and, also some new ideas. So this time I’m going to go through them in some detail - we may be in for a long haul here.

Item one on the agenda is a Module object that understands about GTK. The problem is that both GTK and FVWM::Module expect to be in charge of the main loop. Luckily there’s a GTK aware subclass of FVWM::Module which does the hard work for us. In fact all we need to do is change the use statements for FvwmRingmenu::Ring.pm

#! /usr/bin/perl -w
package FvwmRingMenu::Ring;
use strict;
use lib `fvwm-perllib dir`;
use FVWM::Module::Gtk;
use Gtk -init;
use Class::Struct;
use FvwmRingMenu::Menu;

The FvwmRingMenu file itself shoudn’t need any changes at all.

Next, let’s think about images. By relying on FvwmButtons, we saved ourselves a good deal of work in this department. In particular, before we can display an icon we need to find it (using ImagePath) read it into a Pixbuf, composite it and render it. Then we need to store the image and the mask ready for plotting. All this suggests that we could do with an new perl class: FvwmRingMenu::Image.pm

#! /usr/bin/perl -w
package FvwmRingMenu::Image;
use strict;
use Class::Struct;
use Gtk;
use Gtk::Gdk::Pixbuf;

struct (
        module  => '$',         # reference to Fvwm::Module object
        file    => '$',         # image file 
        pixbuf  => '$',         # this is the GDK pixbuf
        pixmap  => '$',         # this is the GDK pixmap
        widget  => '$',         # this is the GTK pixmap widget
        mask    => '$',         # this is the mask that goes with it
#
#       These should be in a subclass, but that's awkward using Class::Struct
#
        comp_x  => '$',         # x-offset for composition
        comp_y  => '$',         # y-offset for composition
);


1;

__END__

Basically, we’re storing most of the stuff from last time’s plot icon script. Most of the pixmap and pixbuf processing ends up in here too. Before I get carried away however, let’s get back to the problem of finding an icon file.

Fvwm uses the ImagePath command to manage its search path for image files. We can get at this with a global configuration tracker - there’s one supplied as part of perllib. I’m going to make the tracker global, so I can set it once and then refer to it as required.

#
# these are a global configuration tracker and a directory list from
# ImagePath
#
my $globals = undef;
my @dir_list = ();

The @dir_list variable is going to hold the list of directories. We set the variables with a func like this one.

sub set_globals
{
        my $self = shift;
#        
#       first set the global variable that holds the global tracker  
#       (why does FVWM namespace always seem to get so congested?)
#
        $globals = $self->module->track("GlobalConfig");
#        
#       now we extract the image path from the tracker
#
        my $image_path = $globals->data->{'ImagePath'};
#        
#       split the path into component directories and store the list 
#
        @dir_list = split /:/, $image_path;
#        
#       should never happen
#
        die "no image path" unless scalar( @dir_list );
}

We call that from the Image init subroutine to set the variables. Once that’s been done, we can call a search for image files like this:

sub find_image
{
        my $proto = shift;      # may be called as a class method
        my $file = shift;
#
#       return the file name if it starts with a slash
#
        return $file if $file =~ m[^/];
#
#       search image path for file
#
        for my $dir ( @dir_list ) {
                next unless -f "$dir/$file";
                return "$dir/$file";
        }
        die "$file not found in @dir_list\n";
        return undef;                        
}                                            

The init code for the image class starts out with the usual arg processing:

sub init
{
        my $proto = shift;
        my $self = $proto->new(@_);
#
#       make sure we got a referenec to FVWM::Module or suitable subclass
#
        $self->module or die "FvwmRingMenu::Image: a module must be specified";
        $self->file or die "FvwmRingMenu::Image: an icon file is required";

Then invokes the sub to set the ImagePath globals and expands the image file to a full path:

#
#       if need be, init the package variables for the global config tracker
#       and the image path
#
        unless( $globals ) {
                $self->set_globals();
        }
#       
#       make sure the file is stored as a fully qualified path
#
        my $full_path = $self->find_image($self->file);
        $full_path or do {
                $self->module->debug(
                        "FvwmRingMenu::Image: can't find " .
                        $self->file
                );      
        };              
        $self->file($full_path);

And lastly, it constructs a GDK Pixbuf from the file:

#       
#       now - read the file into a GDK pixbuf
#
        $self->pixbuf( Gtk::Gdk::Pixbuf->new_from_file($full_path) );
        die "undefined pixbuf for $full_path" unless defined $self->pixbuf;
#       
#       And that'll do for now
#
        return $self;
}       

You may be wondering why stop there? Why not go on and construct all the bits and pieces needed right up to the windows? Well there are a few reasons. one of them is that I want to keep that for a second initialisation pass after the config file processing is complete. Another is that I want to use this class for the setting images as well, and I’ll never need to take them past pixbuf level.

All of which becomes apparent in the second pass function, prepare_pixbuf:

sub prepare_pixbuf
{
        my $self = shift;
        my $setting = shift;
#       
#       make sure we already have a pixmap initialised
#
        die "undefined pixbuf" unless defined $self->pixbuf;
#        
#       if we have been supplied with a setting image
#       compose the final pixuf
#
        if($setting) {
                my $composite = do_composition(
                        $self->pixbuf, $setting->pixbuf->copy()
                );
#
#               overwrite our pixbuf with the composition
#
                $self->pixbuf( $composite );
        }            
        die "undefined pixbuf" unless defined $self->pixbuf;

The $setting parameter refers to the optional background image that may be passed as a parameter to the subroutine. If it is set, we compisite the two images and replace the pixbuf with the composited one.

The rest of the sub is pretty much straight out of plot_icon

#
#       get a GDK pixmap and mask from the pixbuf, and store them
#
        my ($map,$mask) = $self->pixbuf->render_pixmap_and_mask(127);
        $self->pixmap($map);
        $self->mask($mask);
#
#       make a Gtk::Pixmap ready for display
#
        $self->widget(
                new Gtk::Pixmap( $map, $mask )
        );
}

The do_composite sub is also lifted from plot_icon. The only real change is that I’m calculating the x and y offsets from the sizes of the two images involved. I’ll not go into the details - you can get them from the linked source. There’s nothing new in there that isn’t really self-explanatory.

There is one thing in the Image.pm file that needs a little explanation:

sub DESTROY 
{
        my $self = shift;
        $self->module ( undef );
}

I added this to try and stop the “new instance of dead object” errors I was getting whenever I shut down the module. Perl has a very good garbage collector that recycles an object once it has no more references. Normally it just works, but if you get loops in reference tree then it can’t free objects because something else is pointing at them. So the idea here is to set the module ref to undefined when the image object is garbage collected so as to allow the module to be freed up properly. I’m not at all sure that makes any sort of sense, but it seems to be working at the mo’ so it can stay for now.

In the Icon.pm file, the class def changes:

struct(
        module          => '$',
        name            => '$',
        menu_name       => '$',
        file            => '$',
        image           => 'FvwmRingMenu::Image',
        actions         => '%',
        offset_x        => '$',
        offset_y        => '$',
        window          => '$',
);

That’s storing the icon position as offsets from the circle origin (which will now be supplied by parameter) an Image object for the icon, and a Gtk window object to display the icon itself.

The setup sub in this file imports the rest of the code from plot_icon. It creates a Fixed widget:

#
#       we need to compose the final image
#
        $self->image->prepare_pixbuf($setting);
        my $fixed = new Gtk::Fixed();
        $fixed->set_usize( 100, 100 );
        $fixed->put( $self->image->widget, 0, 0 );

And then adds a click handler to the window. This is a GTK handler, rather than a FVWM::Module one, although they work in similar ways.

#
#       Add button presses to the event mask, and then catch the event
#
        $fixed->add_events( [ 'button_press_mask' ] );
        $fixed->signal_connect(
                "button_press_event" => sub {
                        $self->resolve(@_);
                        return 1
                },
        );

That resolve call starts the process of resolving mouse click. We’ll get to that in a minute.

Lastly, the setup subroutine creates the window and and applies the mask.
All we need do now is postion it and show() it.

        my $window = new Gtk::Window( "popup" );
        $self->window( $window );
        $window->signal_connect( "delete_event", sub { Gtk->exit( 0 ); } );

        $window->add( $fixed );
        $window->shape_combine_mask( $self->image->mask, 0, 0 );

The resolve function does some Gtk stuff to get the button number, checks to see if there is a action defined that can resolve it, and passes the resolution to the action if found. Otherwise the click is ignored.

The subroutine opens with a bit of standard Gtk-Perl hoodoo:

sub resolve
{
        my $self = shift;
        my ( $widget, @data ) = @_;
        my $event = pop( @data );

That gets GTK objects for the widget that generated the event and the event itself. The @data array means that if we decide to specify arguents in the handler callback, we will be able to find then in that array. As it is we don’t specify any, and the array probably only contains the event object. All the same, it’s good practice with Gtk.

The $event object has a field called “type” which should tell us the kind of event that caused the sub to be invoked. Let’s do some checking, just to be safe:

#
#       make sure type is defined - it should be.
#
        unless( defined( $event->{'type'} ) ) {
                $self->module->showError("can't tell event type");
                return;
        }
#
#       make sure it was a mouse click event - it should be!
#
        if ( $event->{'type'} ne 'button_press'  ) {
                $self->module->showError("can't cope with event type " .
                        $event->{'type'}
                );      
                return; 
        }       

Once we’re sure of our ground, we can get the button number:

#
#       get the button number
#
        my $button = $event->{'button'};

Check for an action for the button that was clicked:

#
#       if there is an action for this button number, resolve the action
#
        if($self->actions($button)) {
                return $self->actions($button)->resolve();
        }

And, failing that, check for a generic action for the icon:

#
#       otherwise, check for a button zero action and use that
#
        if($self->actions(0)) {
                return $self->actions(0)->resolve();
        }
#
#       if no action is specified, ignore the event
#      
        return;
}      

We also have a withdraw method that calls hide() to make the icon go away when the menu closes, and a DESTROY method like the one in Image.pm.

Next, let’s look at Action.pm, which is the source of some wierdness. The problem is that to resolve some actions, we need access to the Ring object and its list of menus. We could pass a reference to the ring down to the action level, but I’ve been trying to avoid this. For one thing, it’s another circular reference, and for another it’s a proper pain to add in.

So what we’re going to do is declare a variable as a reference to a subroutine.
Perl allows you to takes references to almost anything, subroutines included. The problem is that the ring class needs to supply the subroutine. I’ll explain why when we get to the ring class. Meanwhile we’ll do this

my $process_click_func;

sub set_process_click_func
{
        my $class = shift;
        my $func = shift;

        $process_click_func = $func;
}      

And rely on Ring.pm calling us to set the $process_click_func variable so we can pass commands back up the chain.

Once that’s set, we can call it to send commands back to the Ring to be resolved:

sub resolve
{
        my $self = shift;
        my $command = $self->command;

        $process_click_func->($command);
}

Over in the Ring class, the set_process_click_func sub gets called in the init func:

        FvwmRingMenu::Action->set_process_click_func(
                sub {
                        my $command = shift;
#
#                       withdraw any old menu
#
                        $self->withdraw();
#
#                       if it starts with a star it's for us
#                       otherwise it gets sent to FVWM proper.
#
                        if($command =~ /^\*/) {
                                $self->star_command($command);
                        }
                        else {
                                $self->module->send($command);
                        }
                }
        );

This is what the perl man pages refer to as a closure. Closures are one of the areas where perl starts getting medium weird[1], but the priciple is simple enough. Any variable that can be accessed when an anonymous subroutine is defined, is still in scope when that sub gets called. Which in this case means that the sub we’re passing back to Action.pm can access the local $self variable from the init subroutine, even though the subroutine itself has exited and the variable would normally have been garbage collected.

Overall, it’s more elegant than passing a reference to the Ring object right down the tree and it saves us from cyclic reference chains.

Anyway, since we’re in Ring.pm, let’s look at the rest of it. First of all the structure itself has seen some changes:

struct(
#
#       FVWM interface stuff
#
        module          => '$',
        page            => 'FVWM::Tracker::PageInfo',
        config          => 'FVWM::Tracker::ModuleConfig',
#
#       Configuration data
#
        offset_x        => '$',                 # offset from ring origin
        offset_y        => '$',                 # ditto
        radius          => '$',                 # radius of ring
        origin_policy   => '$',                 # "Pointer" or "Screen"
        icon_size       => '$',                 # advisory for off screen plots
        debug           => '$',                 # set debug level: 0 is off
        menus           => '%',                 # hash of defined menus
        current         => '$',                 # current displayed menu
#
#       for icon composition
#
        setting_file    => '$',                 # image file
        setting_x       => '$',                 # offset within the image
        setting_y       => '$',                 # say it again, brother!
        setting         => 'FvwmRingMenu::Image',
);      

the first new function is simple enough. get_image_path returns the ImagePath setting so Image.pm can call it and get the directory list.

sub get_image_path
{
        my $class = shift;

        return $globals->data( "ImagePath" );
}

What else? There are subs to parse and set the origin policy (do you want the ring centered on the mouse pounter or the middle of the screen?) and the Setting option. Nothing new there - even the setting option just stores the filename. After the parse loop in read_config - that’s where the setting file gets initialised:

#
#       we may as well initialise the menus while we're here
#
        my $setting;
        if($self->setting_file) {
                $setting = FvwmRingMenu::Image->init(
                        module  => $self->module,
                        file    => $self->setting_file,
                        comp_x  => $self->setting_x,
                        comp_y  => $self->setting_y,
                );
                $self->setting( $setting );
        }

Which is straightforward enough, really.

The main change to the popup sub involves where to put the origin:

#
#       where to center the ring? mid screen or on the mouse pointer.
#       fixed user co-ords should be an option too
#
        my ($x, $y);
        if($self->origin_policy eq "Pointer") {
#
#               use an origin based upon the position of the mouse pointer
#
                ($x, $y) = $self->pointer_origin();
        }
        else {
#
#               use the screen centre as the origin of the ring
#
                $x = $self->page->data->{ vp_height } / 2;
                $y = $self->page->data->{ vp_width } / 2;
        }
        $menu->popup($x, $y);

The interesting bit there is farmet out to the pointer_origin func. Basically we need to get the screen co-ords of the mouse pointer

sub pointer_origin
{
        my $self = shift;
        my $irad = $self->icon_size;
#
#       this morsel of occult data is the way to get the mouse pointer co-ords
#       using GTK and perl. Use this knowledge wisely.
#
        my ($x,$y) = Gtk::Gdk->ROOT_PARENT()->get_pointer();

Then we need to check and make sure half the menu isn’t printing off-screen. If that happens, there’s this weird reflection effect that kicks in and spoils the effect. So we need to check the x co-ord and adjust if necessary:

#
#       if the current x co-ordinate would result in the icons being plotted
#       of the desktop, adjust the origin to fit them in
#
        if($x - ($self->radius + $irad) < 0) {
                $x = $self->radius + $irad;
        }
        elsif($x + $self->radius + $irad > $self->page->data->{ vp_width } ) {
                $x = $self->page->data->{ vp_width };
                $x -= $self->radius;
                $x -= $irad * 2;
        }

And exactly the same for the y co-ord. I’ll not print that bit here.

The rest of Ring.pm should be familiar enough by now. So on to our final file - Menu.pm. Again, most of it shoudl be starting to feel familar. The icon plotting code changes since we’re not using FvwmButtons any more:

        for my $icon ( @{ $self->icons } ) {
                $icon->plot(
                        $origin_x + $icon->offset_x,
                        $origin_y + $icon->offset_y,
                );
        }

With a similar if(){} for the center icon. There’s a withdraw sub that does more or less the same thing, passing the job on down to the icons:

sub withdraw
{
        my $self = shift;
        my $menu_name = $self->name;
#
#       chatter on a bit
#
        $self->module->debug("withdraw: $menu_name");
#
#       do centre first
#
        if($self->center) {
                $self->center->withdraw();
        }
        for my $icon ( @{ $self->icons } ) {
                $icon->withdraw();
        }
}

And that’s really about it. The setup subroutine is changed to calculate offsets to the menu rather than absolute co-ords, but that’s simple enough.

        for(my $i = 0; $i < $n_icons; $i++) {
                my $icon = $self->icons($i);
#
#               to get the co-ords of the icon relative to the origin
#               we use the sin and cos funcs. These however take radians
#               where 360 degrees = 2 * PI;
#
                my $rads = $i * 2 * PI / $n_icons;
                my $x = $ring->offset_x + sin($rads) * $ring->radius;
                my $y = $ring->offset_y - cos($rads) * $ring->radius;

                $icon->setup(
                        $self->name,
                        $self->setting || $setting,
                        $x, $y
                );
        }       

There’s nothing else we haven’t already convered elsewhere.

A quick look at the config file is in order:

DestroyModuleConfig FvwmRingMenu: *
KillModule FvwmRingMenu
KillModule FvwmButtons FvwmRingMenu-FvwmButtons-*

#
# IconSize is an advisory value when calculating how much margin to allow
# when popping menus at the edge of the screen
#
*FvwmRingMenu:  Debug           0
*FvwmRingMenu:  IconSize        90
#
# offset lets you nudge the center of the ring to compensate for 
# icon width. Shouldn't need it with IconSize, really.
#
*FvwmRingMenu:  Offset          -22,-22
*FvwmRingMenu:  Radius          60
*FvwmRingMenu:  Setting menu_setting.png, Geometry +0-2
#
# Where to center the ring: Two possible arguments - "Pointer" and "Screen"
# should really allow a geometry argument as well...
#
*FvwmRingMenu:  OriginPolicy            Pointer

#
# actions can invoke fvwm functions
#
*FvwmRingMenu:  Menu TopLevel, Icon l33t_TER_term.png,                  \
                Mouse1 "InvokeAterm"                                    \
                Mouse3 "InvokeConsole"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_BRO_firefox.png,               \
                Mouse1 "Exec firefox"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_IMS_gaim2.xpm,                 \
                Mouse1 "Exec gaim"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_OFF_openoffice.xpm,            \
                Mouse1 "Exec ooffice"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_GRA_gimp.xpm,                  \
                Mouse1 "Exec gimp"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_UNK_rox.xpm,                   \
                Mouse1 "Exec rox"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_UNK_ssh.png,                   \
                Mouse1 "*SubMenu SSH"
*FvwmRingMenu:  Menu TopLevel, Icon l33t_UNK_package_settings.png,      \
                Mouse1 "*SubMenu Fvwm"
*FvwmRingMenu:  Menu TopLevel, Center l33t_DES_desktop.png,             \
                Mouse0 *Withdraw

*FvwmRingMenu:  Menu SSH, Center l33t_UNK_ssh.png,                      \
                Mouse0 "*PopUp TopLevel"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_freddie.png,                    \
                Mouse0 "InvokeSSH $[USER] fred"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_victor.png,                     \
                Mouse0 "InvokeSSH $[USER] victor"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_frankie.png,                    \
                Mouse0 "Exec xterm -e ssh -X frankie"
*FvwmRingMenu:  Menu SSH, Icon l33t_UNK_igor.png,                       \
                Mouse0 "Exec xterm -e ssh -X igor"

#
# A left mouse button click launches a normal menu,
# a right mouse click launches a ring menu
#
Mouse 3 R A SendToModule FvwmRingMenu PopUp TopLevel

That’s still not a complete config since I want to think about my layout a bit more. It does to illustrate how stuff is used for now.

And after all that, and lots of debugging, it works. I think I could have made that setting a bit smaller though

There’s a little bit more work to do. I need to write the man page for the module, and I have a cool idea for implementing hangons for ring menus. Then I fancy trying some animation options for menu launch time - but there are a free other projects I need to attend to before that, so it may be sometime before I get there.

Meanwhile, I think this baby is about ready to be released on an unsuspecting world. See you next time.

[1] For seriously weird, checkout some of the ACME packages on cpan.org. Especially the ones written by Damian Conway.