ShiftMod
Volume Number: 3
Issue Number: 9
Column Tag: Technical Note
ShiftMod Patch
By Mike Scanlin, San Diego, CA
Last week we were talking with an IBM freak about the differences between our
Macs and his footrest, that is, PC. One of his complaints about the Mac was that you get
upper case letters when both the shift key and caps lock key are down at the same time.
He wanted to be able to type a lower case letter if the caps lock key was down by
pressing the shift key. Then he could type sentences like “i WISH i HAD A mACINTOSH”
without having to hold down the shift key the whole time. Well, Mac fans, fear not. Now
the Mac can do this too. Say hello to Mr. ShiftMod.
All that is really needed is a tail patch to _GetNextEvent that checks if a keyDown
or autoKey event occured while the caps lock and shift keys were held down. The only
problem with implementing it is that because _GetNextEvent is a stack based trap (it
gets its parameters from the stack) the patch requires a bit of self modifying code to
get it to set things up correctly. While this example is trivial, the technique used here
can be used to patch any stack based trap routine.
A tail patch is something that first calls the original trap and then does some
post-processing before returning to the caller. If the original trap is not stack based,
we can do a JSR OriginalTrap at the beginning of our patch, do our post-processing,
and end with an RTS. However, if the original trap is stack based it will expect the
stack to look a certain way when it gets called. By doing a JSR to it, we put a return
address on top of the existing stack which, in effect, shifts all of the parameters by 4
bytes (relative to the top of the stack). The routine being called does not know about
the extra return address and will use the wrong bytes as parameters.
The code listed here is an application, but it can be easily (via ResEdit) be made
into an INIT resource and pasted into your system file. It installs itself in the system
heap and hangs around as long as you don’t turn your Mac off. This code is useful if you
want to install any event processing stuff that is global for all applications. For
instance, you could call _Eject every time you detect a diskEvt and count how many
times someone tries to insert a disk in a minute. Or you could do something
semi-useful like adding a keyclick after keyDowns (how about a mouseclick on
mouseDowns?) for debugging.
Now how about an IBM hacker writing a routine to make his machine return
upper case letters when both the shift key and caps lock key are down? Is it even
possible?
/* shiftMod.c 2 July 1987
*
* by Mike Scanlin and Andy Voelker
*
* This program installs a tail patch on _GetNextEvent so
* that the shift key toggles between upper and lower case
* letters if the caps lock key is down.
*
* No toggle is done if the option and/or command key was
* held down.
*/
#include “Asm.h”
#include “EventMgr.h”
#define what OFFSET(EventRecord,what)
#define message OFFSET(EventRecord,message)
#define modifiers OFFSET(EventRecord,modifiers)
#define GetNextEvent 0xA970
#define JMP 0x4EF9
#define memFullErr -108
main()
{
asm {
move.l D3,-(SP)
/* set up the JMP instruction at the end of the patch */
lea @patchExit,A0
move #JMP,(A0)
/* get the old trap address */
move #GetNextEvent,D0
_GetTrapAddress
/* set up the JMP instruction that calls the original trap */
lea @origTrap,A1
move #JMP,(A1)+
move.l A0,(A1)
/* get some space in the system heap */
lea @last,A0
lea @first,A1
suba.l A1,A0
move.l A0,D0
move.l D0,D3 /* save for _BlockMove */
_NewPtr SYS
cmpi #memFullErr,D0
beq.s @noPatch
move.l A0,-(SP) /* save for _BlockMove */
/* set the trap address to the space we just got in the
* system heap
*/
move #GetNextEvent,D0
_SetTrapAddress
/* now move it into place */
lea @first,A0
move.l (SP)+,A1
move.l D3,D0
_BlockMove
@noPatch move.l (SP)+,D3
rts
/* Here’s the new _GetNextEvent. It calls the existing
* _GetNextEvent and then checks if a keyDown or autoKey
* event is being reported.
*/
/* save original return address */
@first lea @exitAddress,A0
move.l (SP)+,(A0)
/* save ptr to event record */
lea @ eventPtr,A0
move.l (SP),(A0)
/* return to our routine */
pea @tailPatch
@origTrap nop /* JMP to original trap */
nop
nop
@tailPatch lea @ eventPtr,A0
move.l (A0),A0
/* Is it a keyDown or autoKey? */
move what(A0),D0
cmpi #keyDown,D0
beq.s @isKeyDown
cmpi #autoKey,D0
bne.s @patchExit
@isKeyDown move modifiers(A0),D0
andi #shiftKey+alphaLock+optionKey
+cmdKey,D0
eori #shiftKey+alphaLock,D0
bne.s @patchExit
move.l message(A0),D0
cmpi.b #’A’,D0
bmi.s @patchExit
cmpi.b #’Z’,D0
bgt.s @patchExit
addi.b #’a’-’A’,D0
move.l D0,message(A0)
/* return to original caller via long JMP */
@patchExit nop
@exitAddress nop
nop
@ eventPtr dc.l 0 /* storage for event record ptr */
@last
}
}