1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
|
= f.functions
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.
== 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.
== Functional Considerations
There are a few choices in the way functions work to consider in any
given case.
[[func-arguments,Arguments]]
=== 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.
[[cons-deferral,Deferral]]
=== 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`).
=== 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.
== 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.
=== `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.
[[impl-functions-defs-sections,functions_defs.list sections]]
==== 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.
==== 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.
[[impl-dispatch,Function Dispatching]]
=== 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_.
== 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 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
. 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.
=== 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 **D**efining a **F**unction
**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.
== Implementation Examples
[[example-identify,f.identify]]
=== `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.
[[example-gotoworkspace,f.gotoworkspace]]
=== `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.
|