~ctwm/ctwm/trunk

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.