jGNEFilter has been "under documented," with only vague
mentions appearing in Technote
TB 11: GetNextEvent; Blinking Apple Menu.
is the name of a mechanism by which programs can obtain
access to each
EventRecord just before the event is sent to
the caller of
jGNEFilter, your programs can
customize most event-driven
interaction with the user, including but not limited to
as monitoring keystrokes, and programmatically simulating
of user activity. Also, without being an application or
program can arrange to be called periodically at a time
safe to call Memory Manager (and the high-level managers
on Memory Manager).
Developers who would like to make use of
jGNEFilter - or
developers who are already bravely making use of it even in
of inadequate documentation - should read this Technote.
Updated: [Jan 18 2000]
The interface to
jGNEFilter is, unfortunately, rather
The key to the whole thing is a single long word in low memory (at
0x029A, to be precise). This long word, if non-zero
almost always is non-zero; more on that later), is the address of a
GetNextEvent calls to filter events. It's that
The process of installing a
jGNEFilter routine amounts to
off the old filter routine address and installing a new one. There is
no arbitration for access to this memory location; programs must be
very careful to access it according to the calling conventions, as
explained in the next section of this Technote. Otherwise, the system
may begin to misbehave in mysterious ways and other
routines may not get the access to events they need to function
As is always the case with low memory, you should access
jGNEFilter only through the low memory accessor
functions declared in the Universal Headers' "LowMem.h", which in this
jGNEFilter routines should call any previous routine. This
policy alone is responsible for the formation of a "chain" of
jGNEFilter routines. This is roughly the same idea as a trap
Calling the previous
jGNEFilter is essential to the proper
of the Mac and cannot be omitted. Exactly when in your routine to
call the next routine in the chain is up to you.
Back to top
jGNEFilter Calling Conventions
jGNEFilter calling conventions are inherently 68K-oriented and
don't conform to the calling conventions of any high-level language.
For PowerPC filter routines, use
CallGetNextEventFilterProc, both of which make use of a
routine descriptor that makes writing the routine's interface in a
high-level language simple.
For 68K filter routines, it's probably not impossible to write a
jGNEFilter routine entirely in a high-level language (assuming
using a reasonably modern compiler), but it's probably more effort
than it's worth. Instead, you will probably want to use a few lines of
assembly glue to call a routine written in a higher-level language.
On entry to your
jGNEFilter glue, register
will contain the
address of the event record to be filtered. Register
a word which is the proposed return value for
at offset 4 from register
A7 (just above the return address) will
also be the proposed return value for
difference is that
D0 is an input value and the stack word is an
output value. The stack word will be returned to the caller of
GetNextEvent. Initially, the word in
D0 and the
word on the stack are
(or should be, assuming there are no buggy
jGNEFilter functions in
the chain) the same.
jGNE code looking for
checking the stack-based
Boolean or the contents
D0 may not always work correctly in
Mac OS 8.5
or later. To find
nullEvents, such code should
examine the "
what" field in the event record itself
rather than looking at the stack-based
the value stored in register
Since some third-party extensions may exist that do not
set up these values correctly when handling
this warning may also apply to
in the context of Mac OS system software prior to Mac OS 8.5.
It's important to note that the values of both registers
must be set before calling the next routine in the chain or
returning. This isn't anything particularly special to
routines (as opposed to trap patches and other such things), but it
warrants emphasis because it's an easy thing to forget.
By far, the trickiest part of calling the previous
routine is knowing when and how to set the value of the word on the
stack. If your routine is going to jump to the next routine in
the chain (using a 68K
JMP instruction or its equivalent), you
to make sure the stack value is what you would have returned in case
the next routine in the chain chooses not to change it. If your
routine is going to call the next routine in the chain (using
JSR instruction or its equivalent), you should push a
the stack before calling the next routine in the chain and pop the
word after the routine returns. You can then use the return value
from the next routine in the chain routine to help you decide what
return value to put on the stack when your routine returns.
In any case, you should set register
D0 to the value you
next routine in the chain to use as input.
MOVE.W D0,-(A7) ;; push pre-result for C
MOVE.L A1,-(A7) ;; push event record pointer for C
JSR myGNE ;; do the real work (in C)
MOVE.L (A7)+,A1 ;; restore event record pointer
ADDQ.L #2,A7 ;; pop pre-result
;; the post-result (from C) is in D0
ASL.W #8,D0 ;; pretend to have same bug as GetNextEvent
MOVE.W D0,4(A7) ;; stash result where caller expects it
Listing 1. 68K assembly sequence for dispatching jGNE calls to
Listing 1, slightly modified from the "jGNE Helper" sample on
the Developer CD Series Tool Chest Edition, illustrates the
correct sequence of 68K assembly instructions for dispatching
jGNE calls to a routine written in the C language.
Back to top
The following section discusses the two important edge cases that
you need to take into consideration when installing a
The Low Memory Context Switching Gotcha
Most low-memory global variables are swapped in and out of low
memory on a process-by-process basis. (Their values live in the
Process Manager's storage while they're swapped out.) This was done
in the early days of MultiFinder to appease applications that assumed
they owned the whole machine.
jGNEFilter is not one of the low-memory globals which is
Consequently, it's possible for an application to install a filter
routine and get access to events which are about to be passed to
other applications. (Mostly, such filters get access to all relevant
events destined for the foreground application but only null and
update events destined for background applications, because these
events are generally the only events which background applications
An application installing a
jGNEFilter function does not pose a
problem until it's time to quit the application and/or uninstall the
filter. Since the
jGNEFilter routine address is just a long
word in low memory, there's no way to prevent a second application from
reading it and installing a new filter routine address. This second
application would expect to call what it perceives to be the next
filter routine in the chain. When the time came for the first
application to quit, it would restore the "next" routine address low
memory, over-writing the filter routine address of the second
application. Suddenly, the second application would be excluded from
the filter chain and would stop functioning properly. The situation
would get even worse if the second application were to call its
"next" filter routine address, because that code would have
disappeared when the first application quit.
The solution for applications that want to install a
to install it indirectly via a "jump island" in a 6-byte pointer
block in the system heap. Disassembled, the absolute jump instruction
is shown in Listing 2.
JMP XXXXXXXX ;; where XXXXXXXX is the address of your filter
;; machine language = 0x4EF9 0x00000000
;; where 0x00000000 is the address
Listing 2. An absolute jump instruction.
You declare a struct for storing an absolute jump instruction as shown
in listing 3.
# pragma options align=mac68k
unsigned short jmp;
# pragma options align=reset
Listing 3. Type declaration suitable for creating a "jump
NewSysPtr to allocate the block, you set
0x4EF9 and addr to the address of your filter routine (or
GetNextEventFilterUPP). Remember to flush the instruction
performing this magic. (See Technote HW 06 for details on flushing
the instruction cache.) When you want to uninstall your filter
routine, simply set addr to the previous filter routine address.
Do not dispose the pointer block and do not call
LMSetGNEFilter, so that other programs continue to function. Do
remember to flush the instruction cache again.
The Unexpectedly NIL Filter Routine Address Gotcha
Since the system uses
jGNEFilter to do housekeeping such as
servicing the Notification Manager queue, one might expect
to always be non-
NIL. However, this is not the case. Some
programs have taken it upon themselves to set the
NIL temporarily for their own nefarious purposes.
be ready to compensate for this. Compensating might be as simple as
testing the address before calling it.
The Re-entrancy Gotcha
jGNEFilter is not a loop but a filter. Consequently, a
routine does not have the freedom to define the way in which it
handles events. It handles them when the system dictates. If your
routine makes an Event Manager call which results in another call to
jGNEFilter, your routine needs to set and/or test a
to avoid infinite recursion.
Back to top
Compared to other mechanisms,
jGNEFilters have remarkably few
jGNEFilter routines can allocate or move memory
(directly or indirectly), call the Toolbox, perform file I/O, launch
applications, etc. The limitations are:
Not a Process
Any system call that relies on the current process to establish
some sort of unique identity is not going to work very well. The
reason is that a
jGNEFilter routine can be called while any
For example, the AppleEvent Manager uses the current process to
identify the sender of an Apple event. A
jGNEFilter routine can
send Apple events, as long as the current process has its
modeHighLevelEventAware bit set, but it can't receive them,
includes queued reply events. It might be tempting to set up
Apple event handler routines while a given process is current, but
that's likely to cause you big compatibility problems in the long
term, if not right away - just don't do it.
If you need a process, consider starting up a background-only
application, either via
LaunchApplication or by changing your
file to an
'appe'. You can find more information on
applications in Technote
PS 02 - Background-Only Applications.
keyDown events are not sent through the
This was a known bug, but it was fixed, and should only manifest
itself when multi-byte script packages, such as the Japanese Language
Kit, are installed.
If your program needs access to key strokes and will be sold into a
market where WorldScript is in heavy use, you may be better off
WaitNextEvent than writing a
routine. (Yes, it's
shocking to see DTS speaking in a favorable light about a trap patch,
and it pains us to write it, but it's the plain truth.)
Unfortunately, this kind of patch is difficult to write.
Text Services Manager Bugs
In the presence of a Text Services Manager input-method window
(usually called, simply, a TSM window), some mouseDown events may not
be sent to the
jGNEFilter and in fact may appear to "pass
TSM window into whatever is behind it. Unfortunately, there is no
official work around for this known bug.
Back to top
jGNEFilter is a powerful mechanism that adds new
to your code, enabling you to watch the system as whole. If you're
trying to monitor or modify the user's interaction with the system,
jGNEFilter is the place to start. If you're simply trying to
a more flexible environment for your periodic tasks - i.e., allowing
them to allocate memory or do file I/O,
jGNEFilter may also be for
you. There are some limitations, as explained in this Technote, but
the unique advantages of
jGNEFilter outweigh its
Back to top
TB 11: GetNextEvent; Blinking Apple
HW 06: Cache As Cache Can
PS 02: Background-Only Applications
Back to top
Corrected assembly snippet.
Removed obsolete reference to Mac OS 8.
Added compatiblity warning for nullEvents,
Back to top
Acrobat version of this Note (84K).
"jGNE Helper" Source Code Sample.
Back to top