[C]Modules From Scratch

User avatar
Nick Fortune
CatCoder
CatCoder
Posts: 315
Joined: Thu Nov 04, 2004 2:11 am
Location: Stockton, England

[C]Modules From Scratch

Postby Nick Fortune » Sat Apr 02, 2005 5:04 pm

Ever wonder how how modules actually work in FVWM? I did. So I decided to learn about them, by writing one of my own. And since I'm learning, I thought I'd share the experience.

My intention is to write a module called FvwmSchedule to do schedule commands for subsequent execution. As a warm up though, and to get the hang of this module writing lark, I'm going to start with a more modest goal: FvwmNull does absolutely nothing, although it will do so in the manner proper for FVWM modules. Once we have our null module working, it will be a lot easier to add functionality to create our scheduler.

My implementation language is going to be C, and I'm going to assume a basic familiarity with that language. If you are planning a module in Perl/Bash/Python/Whatever you can probably still make use of a lot of this information.

Ready? Then let's go. First of all let's create a working directory:

Code: Select all

cd ~/.fvwm
mkdir mods
cd mods


Then this line to the end of your config to include it in your ModulePath:

Code: Select all

ModulePath $[HOME]/.fvwm/mods:+


You'll also have to restart FVWM or else paste the command into a console. Once this is done we're ready to start coding.

Our first port of call in writing our module is the Fvwm module documentation. This tells us almost everything we need to know.

To begin with it tells us the arguments our module should expect. Basically, arg zero is the file name, as always, and arg 1 and 2 are the file descriptors used to respectively write to and read from FVWM. There are another three args which we can safely ignore, and any after that are command line arguments from the module invocation

So our first cut at the module looks like this:

Code: Select all

#include <stdio.h>

static char *mod_name = NULL;

main(int ac, char *av[], char **envp)
{
        int i;
        char *tmp;
/*
 *      OK the first (subscript zero) argument is the full module path
 */
        mod_name = av[0];
/*
 *      we'll need to chop the path off the name
 */
        if ((tmp = strrchr (mod_name, '/')) != NULL) {
                mod_name = tmp + 1;
        }
        fprintf (stderr, "module name: %s\n", mod_name);
/*
 *      check args - should be at least 6
 */
        if (ac < 5) {
                fprintf (stderr, "%s: must be started by FVWM\n");
                exit (1);
        }
/*
 *      we're only really interested in the first three args - the module
 *      name and the two file descriptors - but let's print this stuff anyway
 */
        for (i = 0; i < ac; i++) {
                fprintf (stderr, "arg %d: <%s>\n", i, av[i]);
        }
        return 0;
}


Create a file called FvwmNull.c containing the code above in the mods subdirectory you created in ~/.fvwm. Then compile it by typing

Code: Select all

Make FvwmNull


Congratulations! You just created a working module! You can start it in from a console window like this:

Code: Select all

Module FvwmModule


Output from the module goes to standard out. That means it'll probably end up in .xsession-errors, or on the screen of the virtual console that launched FVWM. I redirect stdout and stderr to a log file

Code: Select all

FVWM_DIR=$HOME/.fvwm/
exec /usr/bin/fvwm -f $FVWM_DIR/.fvwm2rc > $FVWM_DIR/log 2>&1


Anyway, however you do it, take a look at the output from FVWM and you'll see the output from your module. It should look a bit like this:

Code: Select all

module name: FvwmNull
arg 0: </home/nick/.fvwm/mods/FvwmNull>
arg 1: <23>
arg 2: <4>
arg 3: <none>
arg 4: <0>
arg 5: <8>


And that's it. The module finishes its initialisation and then terminates. That's somewhat useless even by the standards of FvwmNull. We need a main loop. According to the web site, we should be able to read on the file descriptor passed in av[2] and we'll get stuff (to use the technical term) from FVWM. Add some variables:

Code: Select all

        char buff[1024];
        int fd_write, fd_read;


and then at the end of main:

Code: Select all

        fd_write = atoi(av[1]);
        fd_read = atoi(av[2]);

        for(;;) {
                int rc = read(fd_read, buff, sizeof(buff));

                fprintf(stderr, "read: %d bytes\n", rc);
        }


Compile it and run it. In theory, if we send a command using SendToModule from the console, the read statement should pick it up. We're not going to see more than the number of bytes read but we should see something.


Try this:

Code: Select all

SendToModule FvwmNull Hello Fvwm!






Did you try it? Did it work? Nope, me neither :(

I found the problem by digging through source code for a few modules, FvwmDebug in particular. Basically you have to send the command "NOP FINISHED STARTUP" to FVWM before it will start talking to your module. This isn't mentioned anywhere on the module page, and is barely eluded to in the fvwm man page. Nevertheless, now we know the secret we can master this process for ourselves. Assuming we can figure out how to figure out how to send messages back to FVWM that is.

Back to the module page on the website. There's a function called SendText which outlines the code we need. I fiddled with it a bit, but this is the same basic function.

Code: Select all

void send_to_fvwm(int fd, char *message)
{
        int len, tmp;
        long window = 0;                /* no window context */
/*
 *      no message? no problem!
 */
        if (message == NULL) {
                return;
        }
/*
 *      send the window context. there isn't one in this case,
 *      and so we send zero
 */
        write (fd, &window, sizeof(window));
/*
 *      get the length of the message and send it to  FVWM
 *
 *      we really should check the return codes from these...
 *      what should we do on an error though?
 *      I'll come back to that...
 */
        len = strlen(message); 
        write (fd, &len, sizeof (len));
/*
 *      now send the message
 */
        write (fd, message, len);
/*
 *      send 1, indicating that this module will keep going
 *      0 would mean that this module is done
 */
        tmp = 1;
        write (fd, &tmp, sizeof(tmp));
}


Add that in and then call it just before the loop. An important note here: don't try and stop the module with KillModule just yet. We don't have the code in place to deal with that, and all you will do is send your module into a busy loop and fill up your logfile with "read: 0 bytes" lines. Instead use

Code: Select all

killall FvwmNull


That said, recompile and launch the module again. the log file should show something like

Code: Select all

module name: FvwmArt
arg 0: </home/nick/.fvwm/mods/FvwmArt>
arg 1: <23>
arg 2: <4>
arg 3: <none>
arg 4: <0>
arg 5: <8>
read: 36 bytes
read: 80 bytes
read: 36 bytes
read: 80 bytes
read: 36 bytes
read: 80 bytes
read: 36 bytes
read: 80 bytes


And all that before you even try sending anything! What's going on?

Well, what happens is that FVWM sends notification of all events to all modules by default. What you see in all those different read lengths are things like raise-window, lower window - anything that FvwmEvent might pick up. Which is how EvemEvent works, of course.

Still, we're not interested in all of those events. For some modules we'd need to track this stuff, but for our present purposes we can mask this stuff off. The FVWM module page describes how to mask events. It says you send the bitwise OR of all the event types you want to receive. The following example is provided.

Code: Select all

SetMessageMask(fvwm_fd, M_STRING | M_CONFIG_INFO | M_SENDCONFIG | ...);
SetMessageMask(fvwm_fd, MX_VISIBLE_ICON_NAME);


The function SetMessageMask is defined in Modules.c in the libs directory of the FVWM standard distribution. I'm not going to use the FVWM library in this article however. Partly that's because I don't have the library installed as part of my FVWM install, and partly because you learn more working at low-level anyway. That said, if you want to build the library and compile and link against it, you can save a bit of time over they way I'm doing it. End of digression.

Looking at the SetMessageMask function, all it does is send a string to FVWM consisting of "SET_MASK" followed by the mask value. We only want to get SendToCommand strings. These are M_STRING commands, which are defined on the module page as

Code: Select all

#define M_STRING               (1 << 22)


So if we add that definition to the top of the module source, we can then set the event mask, just before the "NOP FINISH STARTUP" send:

Code: Select all

sprintf(buff, "SET_MASK %lu", M_STRING);
fprintf(stderr, "%s: setting mask: <%s>\n", mod_name, buff);
send_to_fvwm(fd_write, buff);


Compile it and start it up. your log shouldn't show any mode events from focus changes or window raising. In fact you won't see any events unless you issue a SendToModule command from a console.

Code: Select all

SendToModule FvwmNull foo


Play around with that for a bit. You'll find from the read sizes reported that there are 32 bytes worth of overhead for each message and that all read sizes are in multiples of four. We'll investigate the structure of these messages in just a moment.

Before we do that, let's fix that infinite loop on KillModule bug. This turns out to be quite simple. All fvwm does is close the pipe from its end. To us, that looks like an end-of-file condition, with zero bytes being returned. So after the read add

Code: Select all


        if(rc == 0) {
                fprintf(stderr, "pipe closed - exiting\n");
                break;
        }


And that will take care of that problem. Strictly we need to catch SIGPIPE as well, but we don't really need to worry about that in FvwmNull, so I'll defer that discussion until later on.

Now then - about those packets from FVWM. According to the developer pages on the FVWM website the message header is an array of four four byte long integers.

The first long should always hold the value 0xffffffff. If not it means we had a partial read somewhere. If we wanted to we could read scan forward for the next 0xffffffff byte and use that to re-synchronise with FVWM, but I'm not going to worry about that unless I have to.

The second long describes the packet type using the constants listed on the module developer web page. We only expect M_STRING packets, since that's the mask we set. You can get the authoritative list from libs/Module.h in your FVWM source directory.

The third long is the total length of the packet in unsigned longs, including the header we just read. So to get the remaining bytes to be read, we subtract 4 and multiply the result by 4.

The last long in the header is an X-Windows time stamp in microseconds. I expect that could be useful for double-click type events and so forth. We can safely ignore it though.

Let's have a struct to hold those values:

Code: Select all

typedef struct fvwm_head {
        long    sync;
        long    pkt_type;
        long    pkt_length;
        long    timestamp;
} FVWM_HEAD;


Then we can rewrite our main loop like this:

Code: Select all

        for(;;) {       
                int rc, len;
                FVWM_HEAD header;
/*
 *              normally we'd use select() for this, but since we're
 *              quite happy to hang around waiting for FVWM,
 *              we can use read() instead
 */
                rc = read(fd_read, &header, sizeof(header));
                if(rc < 0) {
                        fprintf(stderr, "error on read\n");
                        break;
                }
                if(rc == 0) {
                        fprintf(stderr, "pipe closed - exiting\n");
                        break;
                }
/*
 *              check for proper synchronisation, then
 *              get the length of the remainder of the packet
 */
                assert(header.sync == 0xffffffff);
                len = (header.pkt_length - 4) * 4;
/*
 *              now, what we do next depends on the packet type
 */
                switch(header.pkt_type) {
                case M_STRING:
                        read_m_string(fd_read, len);
                        break;
                default:
/*
 *                      working on the principle that a crash causing bug
 *                      gets fixed fastest, I'll make this one fatal >:>
 */
                        fprintf(stderr, "%s: fatal error: unexpected packet type
 %lu",
                                mod_name, header.pkt_type
                        );
                        exit(1);
                }
        }                                             


There's quite a lot there, but it's just what I've described above. I've hived off reading the remainder of the packet into a new function called read_m_string. To find out how to write this we need to go back to that web page again and read the format for M_STRING packets. These turn out to be three window ids of 4 bytes each, in case the module was called in a window context, and also in case we cared. The rest of the packet is, finally, our string.

So all we need to do is allocate some memory, read the message, ignore the first 12 bytes, print the string to the log (so we know we got it right) and free the memory!

Something like this:

Code: Select all

typedef struct fvwm_m_string {
        long    appl_win_id;
        long    frame_win_id;
        long    fvwm_internal_id;
        char    message[1];             /* over allocate to message length */
} FVWM_M_STRING;

void read_m_string(int fd, int len)
{
        FVWM_M_STRING *spt;
/*
 *      allocate some memory - calloc sets it to binary zeroes. Which is nice.
 *      add one byte for the message null terminator
 */
        if((spt = calloc(len+1, 1)) == NULL) {
                fprintf(stderr, "%s: out of memory!\n", mod_name);
                exit(1);
        }
/*
 *      read the message - don't use sizeof(*spt) or sizeof(FVWM_M_STRING)
 *      since that'll only read one byte of the actual message
 */
        if(read(fd, spt, len) <= 0) {
                return;                 /* sort it out in the main loop :) */
        }
        fprintf(stderr, "%s: message received: '%s'\n",
                mod_name, spt->message
        );
        free(spt);
}                 


And there it is - a fully functional, albeit functionally trivial, FVWM module. Next post I'll use this framework to write the FvwmScheduler module I described at the start.

[edit] I missed out the error checking on the read in the main loop. Fixed now, but should still print errno for debugging.

Edited by theBlackDragon:
--> Split the Perl part out and moved from General Fvwm discussion

User avatar
Nick Fortune
CatCoder
CatCoder
Posts: 315
Joined: Thu Nov 04, 2004 2:11 am
Location: Stockton, England

Modules From Scratch - FvwmSchedule - Part One

Postby Nick Fortune » Sun Apr 03, 2005 2:06 pm

Having got our null module working, it's time to turn our attention to FvwmSchedule.

In principle all FvwmSchedule needs to do is to hang sleep like so:

Code: Select all

main()
{
        for(;;) {
                sleep(1);
                while(cmd = commands_for_this_second()) {
                        process_command(cmd);
                }
        }
}


The problem is we can't wait on sleep, because we also need to wait on read. The way to solve this one is using the select function call.

Select(2) allows you to monitor a number of different file descriptors for to see if they're read for reading or writing. More importantly, it allows us to set a timeout down to the microsecond level. So we don't need to wait for FVWM to send us data - we can operate asynchronously.

All this gets a little complicated though - so let's replace the function that reads the header with a function called read_header(). We can give it the same args:

Code: Select all

                rc = read_header(fd_read, &header, sizeof(header));
                if(rc <= 0) {
                        break;
                }


I've added in a test for negative returns - one glaring omission from the original post. I also lost the error messages - we'll handle them in the function.

Take a quick look at the select man page. we need to include a header file

Code: Select all

#include <sys/select.h>
#include <errno.h>              /* useful for errors */


and we need to cope with a couple of structures. The timeout is the easiest, using a struct called timeval which has two fields specifying seconds and microseconds to wait before timing out. So we can do

Code: Select all

struct timeval tval;

...

tval.tv_sec = 0;
tval.tv_usec = 200;


and specify a timeout period of a fifth of a second.

To watch the file descriptors, select uses things called fdsets. these are essentially lists of integers. You initialise them with FD_ZERO and add descriptors to them with FD_SET. Like this:

Code: Select all

fd_set rfds;

FD_ZERO(&rfds);
FD_SET(r_fd, &rfds);


select takes three fdset arguments for read, write and exception conditions, but you can pass NULL if you're not interested in a category. So our call to select looks like this:

Code: Select all

rc = select(r_fd + 1, &rfds, NULL, NULL, &tval);


The first argument has to be highest descriptor being watched, plus one. Since we're only interested in one of them, that's easy.

The return value is -1 for an error, 0 for a timeout, or N where N is the number of file descriptors that require attention.

That's almost enough information to write our function.

Code: Select all

int read_header(int fd, FVWM_HEAD *hpt)
{
        int rc;
        fd_set rfds;
        struct timeval tval;
/*
 *      set up structs for select()
 */
        FD_ZERO(&rfds);
        FD_SET(fd, &rfds);
        tval.tv_sec = 1;
        tval.tv_usec = 0;
/*
 *      wait for data with a 1 second timeout
 */
        rc = select(fd + 1, &rfds, NULL, NULL, &tval);
/*
 *      check for errors
 */
        if(rc == -1) {
                fprintf(stderr, "%s: select failed, errno = %d\n",
                        mod_name, errno
                );
                return -1;
        }
/*
 *      check for a timeout
 *
 *      we'll return zero here to indicate a timeout, rather than
 *      end of file as it would from read()
 */
        if(rc == 0) {
                return 0;
        }
/*
 *      if we get this far, we have data waiting to be read
 *
 *      I wonder what select does on end-of-file? I guess I'm going to find out
 */
        switch(rc = read(fd, hpt, sizeof(*hpt))) {
        case -1:
                fprintf(stderr, "%s: read() failed, errno = %d\n",
                        mod_name, errno
                );
                return -1;
        case 0:
/*
 *              we return -1 as though it was an error condition
 *              we do this because we're using zero to indicate timeouts
 */
                fprintf(stderr, "%s: pipe closed - exiting\n", mod_name);
                return -1;
/*
 *      Did You Know: you can use any compile time constant expression
 *      as a case label? For instance:
 */
        case sizeof(*hpt):
                return rc;              /* we got what we expected! */
        default:
                fprintf(stderr, "%s: read_header: expected %d bytes, got %d\n",
                        mod_name, sizeof(*hpt), rc
                );
                fprintf(stderr, "%s: this is weird and scary and I'm going home\
n",
                        mod_name
                );
                return -1;
        }
/*
 *      if we get here I've made a mistake. Let's make sure I find out :)
 */
        fprintf(stderr, "%s: logic error: this should be impossible!\n",
                mod_name
        );
        exit(-1);

}


Phew! You see what I mean about select getting complicated? Still that's the last of the heavy coding. Most of what remains is simple list handling. But first we need to tweak our main loop a little:

Code: Select all


        for(;;) {       
                int rc, len;
                FVWM_HEAD header;
/*
 *              read the packet header from FVWM
 */
                rc = read_header(fd_read, &header);
/*
 *              -1 could be a closed pipe or a read error
 *              error reporting is handled internally to read_header()
 *              all we have to do is break out of the loop
 */
                if(rc < 0) {
                        break;                  /* the way out */
                }
/*
 *              now check to see if a second has elapsed on the clock
 *              since last we came this way. If it has we need to check for
 *              commands come due
 *
 *              we do this for commands received from FVWM as well as for
 *              timeouts
 */
                time(&now);
                if(now > last) {
                        last = now;
                        check_commands(now);
                }
/*
 *              now - if it was a timeout, we can skip the rest of this loop
 */
                if(rc == 0) {
                        continue;               /* nothing to read */
                }


And thereafter it's unchanged. The check_commands() func I've just sketched in for now:

Code: Select all

void check_commands(time_t now)
{
        fprintf(stderr, "pretending to check for commands due on %03d\n", now);
}


Which is enough to compile the module. Run it now and you should see it trying to check for commands every second, as well as echoing back any SendToModule strings you may send.

And that makes a good place to leave it for now. We still need to parse the SendToModule strings we're going to receive from FVWM and as well as storing them and sending the commands back to FVWM at the proper time.

User avatar
Nick Fortune
CatCoder
CatCoder
Posts: 315
Joined: Thu Nov 04, 2004 2:11 am
Location: Stockton, England

Modules From Scratch - Part 3 - Configs and Parsing

Postby Nick Fortune » Tue Apr 05, 2005 1:15 pm

Well, I got FvwmSchedule working, but it took a bit more work than I'd anticipated. A lot that was fairly unremarkable C code with little direct relevance to FVWM, so I'm going to try and restrict myself to FVWM protocol details, and a quick outline of parsing and callbacks, and the interface to the scheduler table code. The full source for all of this is linked at the end of the post.

The biggest problem I had finshing FvwmSchedule, much to my surprise, lay in getting the configuration details from FVWM. Most of the modules in the FVWM source tree read their config data like this:

Code: Select all

InitGetConfigLine(fd, MyName);
for(;;) {
        GetConfigLine(fd, &buff)
        if(buff == NULL) {
                break;
        }
        ParseConfigLine(buff);
} /* end config lines */


This turns out to be a little misleading. Looking at it, and looking at the source for the functions too, you'd expect the program to loop, reading lines until it gets an end-of-config packet, and then drop out of the loop, declare itself open for business, and start performing module-like activities.

What happens is that it sends to FVWM saying "gimme the config" and FVWM thinks "just a minute, I'm busy". So select() times out, GetConfigLine sets buff to NULL because it can't think what else to do, and the execution drops out of the loop. After the program announces that it's finished initialising and enters its main loop, it gets a load of M_CONFIG_INFO packets and presumably processes them there and then.

Oh, and MyName has an asterix prepended to it already as well.

Once I figured all that lot out, the rest was easy. InitGetConfigLine just sends a Send_ConfigInfo message to FVWM. So I added this to my initialisation section:

Code: Select all

sprintf(buff, "Send_ConfigInfo *%s", mod_name);
send_to_fvwm(fd_write, buff);


And a couple more cases to the switch in the main loop:

Code: Select all

switch(header.pkt_type) {
case M_STRING:
        read_m_string(fd_read, len);
        break;
case M_CONFIG_INFO:
        read_m_config_info(fd_read, len);
        break;
case M_END_CONFIG_INFO:
        break;                  /* nothing to do */


Now we want a struct to decode M_CONFIG_INFO packets. They have three leading long ints (all set to zero) and then the configuration string.

Code: Select all

typedef struct fvwm_m_config_info {
        long    junk[3];
        char    message[1];             /* overallocate to message length */   
} FVWM_M_CONFIG_INFO;


The read function for that folows the same pattern as for M_STRING. There are a couple more gotchas here however. First of all, FVWM doesn't just send the configuration for your module as named to it in the Send_ConfigInfo command. Nope, it sends the module config lines plus colorset definitions, ImagePath, DesktopSize, and a bunch of other stuff described on the module developers page. Disregard anything that doesn't begin with a "*" and we're ok.

The other gotcha is that the format the config data arrives in. If you specify

Code: Select all

*FvwmSchedule: After 10 Do Echo BOO!
*FvwmSchedule: After 12 Do Echo Eeeeek!!


Then what you'll get is

Code: Select all

*FvwmScheduleAfter 10 Do Echo BOO!
*FvwmScheduleAfter 12 Do Echo Eeeeek!!


See how the ": " has been stripped out? So once we have the message, we need to skip over the module name and the leading asterix to get to the message. Once we get that, I need to parse it, which is where I left off last time.


The rule of parsing is to be clear on your syntax. The syntax for FvwmSchedule commands is pretty simple.

Code: Select all

After N [Thereafter M] [Name name] Do fvwm-commands
Cancel pattern


Where N and M are the number of seconds to wait before the first and subsequent executions of fvwm-commands. name is a string that may be matched by pattern to remove a command from the scheduling table.

There are lots of ways to parse a string, but for a simple syntax like this, sscanf() works as well as anything. Something broadly like this:

Code: Select all

/*
 * scan msg_pt for the first two space delimited tokens which go in
 * keyword and argument. bytes_consmed will be set to the number of bytes
 * covered by the scan.
 */
rc = sscanf(msg_pt, "%s %s %n", keyword, argument, &bytes_consumed)

/*
 * scanf will return the number of targets matched. Whether %n counts or not
 * is implementation dependant, but we expect rc to be at least 2 in this case
 */
if(rc < 2) {
        fprintf(stderr, "%s: ran out of tokens parsing '%s'\n",
                mod_name, msg_pt
        );
        return ERROR;
}

/*
 * there are only two keywords in our syntax: Cancel and After
 */
if(strcmp(keyword, "After") == 0) {
/*
 *      by adding bytes_consumed to the pointer we jump over
 *      the part of the string we just parsed
 */
        msg_pt += bytes_consumed;
        /* do stuff to parse rest of "after" directive */
}
else if(strcmp(keyword, "Cancel") == 0) {
        msg_pt += bytes_consumed;
        /* process the cancellation request */
}
else {
/*
 *      don't add bytes_consumed if no match
 *      this'd let us try another approach on the same string
 *      if one were available
 */
        fprintf(stderr, "%s: unrecognised keyword '%s'\n",
                mod_name, keyword
        );
        return ERROR;
}


That's the bones of the parse_command string. The extra stuff to parse the rest of the "After" directive works pretty much the same way.

Once we parse an After we need to store it until its due to execute. Then we send the fvwm command string back to FVWM. I stuck the list handling in a sepate .c file to try and keep the design clean. This is the header:

Code: Select all

#ifndef fvwm_schedule_table_h
#define fvwm_schedule_table_h
#include <time.h>

/*
 * struct to hold scheduled events
 */
typedef struct sched_cmd {
        int     id;             /* unique id */
        time_t  after;          /* delay before first execution */
        time_t  thereafter;     /* truct to hold scheduled events */
        time_t  next;           /* time value of next execution */
        char    *name;          /* used for cancelation/alteration */
        char    *fvwm_cmd;      /* what to send back to FVWM */
} SCHED_CMD;

extern void add_command(SCHED_CMD *source);
extern void cancel_command(char *pattern);

/*
 * scans the list for due commands. for each one it finds, callback gets called
 * with a pointer to the command struct and context as arguments.
 *
 * additionally clears out any commands flagged for deletion by setting id = 0
 */
extern void check_commands(time_t now, void (*callback)(), void *context);

#endif


Everything you need to know is there. We store commands in SCHED_CMD structs, add them to the table with add_command(), cancel them with cancel_command, and check_command() lets us scan the table. Internally, the list module is a 1024 slot array, but it could just as easily use linked lists or dynamic allocation. Again, the source is in the tarball.

The only major piece of FVWM related code to look at is how to execute the commands. The check_commands function I blocked out last time has mutated a bit and been pushed into the fvwm_schedule_table.c file. The invocation has changed a bit too...

Code: Select all

if(now > last) {
        last = now;
        check_commands(now, execute_command, &fd_write);
}


Where execute_command is the name of the function that we're going to use as a callback, and fd_write is going to be the context block. The callback function is defined like this:

Code: Select all

static void execute_command(SCHED_CMD *spt, void *context)
{
        int fd = (*((int *) context)); /* turn void * back into int */
/*
 *      send the command string to FVWM
 */
        fprintf(stderr, "%s: execute_command: thereafter = %d\n",
                mod_name, spt->thereafter
        );
/*
 *      send to command to be executed
 */
        send_to_fvwm(fd, spt->fvwm_cmd);
/*
 *      if it has a thereafter clause, bump the next value
 *      otherwise mark it for removal by unsetting id
 */
        if(spt->thereafter) {
                spt->next += spt->thereafter;
        }
        else {
                spt->id = 0;    /* flag for clearup */
        }
}


And that's it! The source for FvwmSchedule and FvwmNull can be found here. I hope this has been useful and even if you don't write any modules, that some folks will find FvwmSchedule useful. If I get really bored, I may take a shot at writing the same stuff in Perl purely for the comparison.

If anyone has any questions or comments, feel free to chip in.


Return to “Other languages”

Who is online

Users browsing this forum: No registered users and 1 guest