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 (inparse_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 thealiases
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 (inEF_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 inEF_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:
-
Add it to the
main
section of thefunctions_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 -
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.