This document will describe how functions get written and dispatched. It should be a useful reference when you intend to add a new function, or understand how the code works.

1. Document Conventions

Unless otherwise stated, most mentions of "f.function" (in singular or plural) are meant to be generic references to any f.anything that ctwm implements, rather than specifically the f.function function. This is done because just calling them "functions" can be ambiguous, especially when talking about the implementation, because the implementation of a ctwm function is done in terms of a C function, so there’s often opportunity for terminological confusion.

2. Functional Considerations

There are a few choices in the way functions work to consider in any given case.

2.1. Arguments

Some functions take an argument, while others don’t. For example, the case of [example-gotoworkspace] as described below takes an argument, so you’d have something like f.gotoworkspace "one" in a key binding or menu. Contrarily, [example-identify] doesn’t, so you’d merely have f.identify in the config.

This is controlled by a column in the functions_defs.list file; see below where the [impl-functions-defs-sections] are discussed.

2.2. Deferral

There is also a concept of function deferral. This happens in the case of f.functions that in some way target a window (f.move and friends, f.resize, f.occupy, and a great many others). When you activate them from a mouse/key binding or titlebar icon or the like, ctwm can see which window you’re pointing at, and targets it from there. However, when run from a menu, you can’t be pointing at a window; you’re pointing at the menu.

As a result, ctwm defers the execution of the f.function. It changes the mouse cursor to something to prod the user, and waits for you to click on a window. Then it runs back into the function execution to actually to the work.

So any f.function that has to do something related to a window has to be setup to defer, or it won’t work from a menu. This is also controlled in functions_defs.list; x-ref the description of the [impl-functions-defs-sections]. The right cursor for any given case is a matter of judgement, but generally move/resize actions have one cursor (the DC_MOVE choice), and other functions use the other (DC_SELECT).

2.3. Magic and Internal

There are a few "synthetic" or "internal" f.functions, which exist only to link up some magic like the TwmWindows auto-generated menu. Unless you’re working with magic menus, you never need to go near or know anything about them.

There are also two somewhat magical f.functions. One is f.function which runs a user-defined function, which is a sequence of other existing functions. This is commonly used in conjunction with the other magical function, f.deltastop, to let you do stuff to a window that varies depending on whether you move the mouse or not. See the user manual for details of them. They get executed slightly differently than other functions; see the [impl-dispatch] section below for details.

3. Implementation Overview

Much over the overall control for dispatching and finding f.functions is done via generated code, from the definitions in functions_def.list. f.function execution begins by calling into the ExecuteFunction() function from various places (usually event handlers for menu selections or mouse/key bindings, but there are a few other ways). There it uses various of the autogenerated bits to look up what sort of deferral or other magic it might do, and then falls down into individual C functions for implementing each ctwm f.function.

3.1. functions_defs.list and autogenerated controls.

As part of the build process, tools/mk_function_bits.sh builds various generated header files (i.e., build/functions_*.h) from the functions_defs.list file. Comments in that file give a good reference to the details of the syntax. We’ll skim the higher-level overview here.

3.1.1. Sections

There are 3 sections in the file, delineated by comments like #START(section) and #END(section); these are used as markers by the mk_function_bits.sh script to find the bits it needs at any given time.

The aliases and synthetic section are almost certainly not anything you need to touch. aliases are alternate names for f.functions. Those that exist are historical, and we should probably avoid adding any new ones; just name a function what it should be named, and don’t add confusion by having multiple names. synthetic are f.functions not exposed to the user (i.e., not available in config files) but get called from things like the magic TwmWindow menu. Both are very special cases, so unless you’re doing something very unusual, you’ll never go near them.

The main section is where you’ll be playing. It contains space delimited columns (mostly visually lined up in the file for convenience; the script only cares about whitespace). First is the name; obvious. Second determines whether it’s a f.function that takes an argument (like [example-gotoworkspace] below) or one that doesn’t.

The third column defines the deferral cursor; this has the side effect of determining whether it’s a deferred f.function or not; see discussion of [cons-deferral] above. And the fourth allows hiding info about the function behind an #ifdef. The only current use of that is for the rplay-based sound support, and it should probably be avoided for new functions. Generally, the function should be available all the time, and just do nothing (or beep, or something appropriate) when the conditional code isn’t available. This saves users from some complication in writing their config files.

3.1.2. Generated Files

From that, mk_function_bits.sh generates header files that contain the various info about the f.functions.

  • One file contains the ``#define``'s for all the F_WHATEVER contants used in the code to refer to the f.functions internally. This only really needs the names.

  • It also generates the funckeytable lookup table the config file parser (in parse_keyword()) uses to look up the functions referred to in the config table. This needs the second column to distinguish functions taking argument from those that don’t. It also uses bits from the aliases section, since we need to parse those names when give (and treat them the same as the real f.function names).

  • It generates the fdef_table lookup table which is used in the f.function execution (in EF_main()) to determine whether to defer calling the function, and what X cursor to set when it defers. This uses the third column (and only includes f.functions that have something there). See earlier discussion of [cons-deferral].

  • And finally, it generates the func_dispatch table used in EF_main() to dispatch the actual execution of the f.function to the underlying C function that implements it. This is just built off the names.

3.2. Dispatching and Executing

Some mechanism (usually invocation from menu or button/key binding) calls some f.function. This calls into ExecuteFunction() to do the dispatching, which is just an external thunk into EF_main(). This checks the environment and the fdef_table we generated to determine whether the function should be deferred; if so, it sets the deferral cursor and returns. Actual execution then happens via another fresh call into ExecuteFunction() via slightly creepy magic in the ButtonPress event handling code. You don’t want to know.

Then it falls into actually dispatching the f.function. There are two special cases described below. Most f.functions simply run through to an individual C function that implements them, via the func_dispatch table and specific naming; the implemetation of the ctwm function f.abcdef will be in the C function f_abcdef_impl().

The two special cases revolve around the f.function construction which allows user creation of ctwm functions that alias or chain multiple other f.functions (x-ref Function keyword in the user name). The first is f.function itself, which loops over the list of things the user told it to do and recurses back into EF_main() for them. The second is the magic f.deltastop (which is only meaningful as part of a ``f.function``'s chain), which checks its magic and returns a value from EF_main() to tell the calling f.function invocation to stop where it is instead of proceeding. This is the only use of ``EF_main()``'s return value.

4. Implementating A Function

Most of the work of implementing a new f.function should be whatever code you actually need to write to do what the function is supposed to do. We want to minimize the boilerplate you need to do to hook it up.

Generally, you only need to do two things:

  1. Add it to the main section of the functions_defs.list file, with whatever options are appropriate. The build system will notice the change and add it to the generated files next time you build. Then it’s ready to be parsed from a config file and executed at runtime. Note that this will cause a compile failure until you also

  2. Create the implementation in the appropriately named C function. The DFHANDLER() macro exists to set the right name and argument list; use it instead of trying to do it manually. Even an empty function will be enough to satify the compiler and get you running.

4.1. Internal Macros And Details

The functions_internal.h file contains a few macros used in defining and calling f.function implementations, the prototypes for all those implementations, and a few other bits that get shared among the function_*.c implementation files.

EF_FULLPROTO gives the full list of arguments that ExecuteFunction() and all the f.function handlers takes. It’s also used in some backend functions the handlers call. Commonly these are cases where several functions act almost identically, and so just thunk through to a shared backend function; e.g., how all of f.move, f.forcemove, f.movepack, and f.movepush merely call movewindow() in functions_win_moveresize.c. The EF_ARGS macro is the same set of arguments, just in the form of the names as you’d use in calling the function; you can see its usage in those same cases.

The DFHANDLER() macro is used in Defining a Function HANDLER. It’s used in both the prototypes in functions_internal.h and in all the implementations in the functions_*.c files. By just calling it with the function name, we can automate away making sure the implementation is named correctly so the generated func_dispatch table can find them in the dispatch (x-ref [impl-dispatch]), and that it takes the right args. Along with the mentioned EF_* macros, that will save us a lot of trouble visiting hundreds of places if/when we change the set of args we pass around function execution and handlers.

5. Implementation Examples

5.1. f.identify and f.version

f.version pops up a window with info about the ctwm build and version. f.identify pops up a window with information about a given window, which has also all that f.version information up top. So they can be considered variants of the same thing. And in fact, they both wind up implemented by the same code on the backend.

So, to trace from the top, we find the version and identify lines in the main section of functions_defs.list. The version line has nothing in the other 3 fields; it takes no argument, and since it doesn’t target a window it doesn’t need any deferral. identify also takes no argument, but does target a window, so it needs to be deferred; the CS entry means we’re using the "select" style cursor. From that file, the various lookup arrays for deferring and dispatching get autogenerated.

The implementations are in functions_identify.c. As with all functions, the DFHANDLER() macro is used to name the function and arguments. Each of those implementations just calls the Identify() backend function for the implementation; f.identify passes the targetted window (the tmp_win argument to the handler), while f.version passes NULL. Identify() then builds the window with the ctwm version/build info, and then the window info if it were given one.

5.2. f.gotoworkspace

f.gotoworkspace warps you to a named workspace, so it takes an argument. See discussion in [func-arguments] above. So we see in its line in functions_defs.list that it has an S in the first field, indicating it’s taking a string argument (the only choice other than the stand-in - for functions not taking args).

The implementation in functions_workspaces.c is then a fairly thin wrapper around the existing GotoWorkSpaceByName() function used elsewhere. The action argument to the handler contains the value of the argument given in the config file, which in the case is a string of the name of the workspace, and GotoWorkSpaceByName() does its thing.