program KeyboardSleuth;
{ Keyboard Sleuth: analyze key mappings
Stand-alone version written in Rascal
By Joel West, August 1986, for MacTutor
** Converted to TML Pascal by David E. Smith **
Tries to figure out what keyboard is installed
Uses several approaches:
-Dump and analyze keyboard #
-Check keypad for Mac 512 vs. Mac Plus
-Look at INTL resources to find for country code
-Check for mapping of space key (US vs. Foreign)
Then allows user to type keys and shows their keycodes
and ASCII values. Dumps this to screen and to a logfile }
{ Include files and constants }
{$I MemTypes.ipas }
{$I QuickDraw.ipas }
{$I OSIntf.ipas }
{$I ToolIntf.ipas }
{$I PackIntf.ipas }
{$I HFS.ipas }
{ ---------------- GLOBAL CONSTANTS ------------ }
CONST
Key1Trans = $29E; { Low Memory Globals }
Key2Trans = $2A2;
EOL = 13; { end of line file delimiter (RETURN)
}
{menu res id's }
AppleMenu = 256;
FileMenu = 257;
EditMenu = 258;
{ ---------------- ASCII values ------------ }
Space = $20; { }
{ Key #10, where US,UK "/" is (key # differs in US) }
Slash = $2F; { / UK }
Minus = $2D; { - German, Spanish, Swedish }
Equals = $3D; { = French }
Ograve = $98; { ò Italian }
Eaigu = $8E; { é French Canadian }
{ Key # 36, where UK "`" (accent grave) is
Used only to distinguish Spanish from German and Swedish }
Degree = $A1; { ° Spanish/Latin American }
Hash = $8A; { # German }
Apos = $27; { ' Swedish }
{ ---------------- Keycap Numbers ------------ }
USspKey = 49; { space bar in US }
UKspKey = 52; { space bar in UK, Euro-Classics}
UKslKey = 10; { / key in UK }
UKgrKey = 36; { ` (dead) key in UK }
{ ---------------- GLOBAL VARIABLES ------------ }
VAR
{my stuff}
my window: WindowPtr; { our window pointer }
finished: Boolean; {program terminator}
ClockCursor:CursHandle; {handle to waiting watch }
{STDFile stuff}
logfile: INTEGER; { file status }
logname: STR255; { file name }
volNumber: INTEGER; { vRefNum }
fileNumber: INTEGER; { file number }
{Screen stuff}
DragArea: Rect;
GrowArea: Rect;
Screen: Rect; {holds the screen dimensions }
{TextEdit stuff}
DestRect: Rect;
ViewRect: Rect;
theText: TEHandle;
scrollflg: Boolean;
{ ---------------- BEGIN CODE ------------ }
Function KeyTrans(keyno,modifies: Integer) : Integer; EXTERNAL;
{$U keytrans }
{ Translate key number and modifiers to
their corresponding ASCII value }
Function CR:str255;
begin
CR:= chr(EOL)
end;
PROCEDURE Openlog;
{ open keyboard logfile to save all messages for later review }
label 1;
Var
where: Point;
Prompt: STR255;
origName: STR255;
reply: SFReply; { standard file reply record }
Info: FInfo; { Finder file info reply record }
vol: INTEGER; { vRefNum }
fileno: INTEGER; { file number }
resultCode: OSErr;
Begin
where.v := 50;
where.h := 50;
Prompt := 'Save your log file as:';
origName := 'KeyBoard Log';
DILoad; {in case disks are switched}
SFPutFile(Where, Prompt, origName, Nil, reply);
logname := reply.fName;
vol := reply.vRefNum;
IF reply.good = FALSE THEN
logfile := 0 {bad file}
ELSE
logfile:= 1; {good file}
IF logfile = 0 THEN goto 1;
resultCode:=GetFInfo (logname, vol, Info);
case resultCode of
NoErr: { file exists..delete it }
Begin
if Info.fdType <>'TEXT' then
begin
logfile:=0;
goto 1;
end;
resultCode:=RstFlock(logname,vol);
if resultCode <> NoErr then
begin
logfile:=0;
goto 1;
end;
resultCode:=FSDelete(logname,vol);
if resultCode <> NoErr then
begin
logfile:=0;
goto 1;
end;
resultCode:= Create (logname, vol, 'MACA', 'TEXT');
if resultCode <> NoErr then
begin
logfile:=0;
goto 1;
end;
end;
FNFErr: { file not found so create one }
begin
resultCode:= Create (logname, vol, 'MACA', 'TEXT');
if resultCode <> NoErr then
begin
logfile:=0;
goto 1;
end;
end;
OTHERWISE logfile:=0;
End; { case }
if logfile = 0 then goto 1;
resultCode:= FSOpen (logname, vol,fileno); { open log file }
if resultCode <> NoErr then
begin
logfile:=0;
goto 1;
end;
resultCode:= SetFPos (fileno, FSFromStart, 0);
if resultCode <> NoErr then
begin
logfile:=0;
goto 1
end;
volNumber:=vol;
fileNumber:=fileno;
1:
if logfile = 1 then
SetWTitle(my window, logname)
else
SetWTitle(my window, 'No Log File!');
End;
Procedure PutString(str: Str255);
{ Write a string to the log file and to the screen }
Var
resultCode: OSErr;
strlen: LONGINT;
scrollup: integer;
curlines: integer;
linepos: integer;
newpos: integer;
endpos: integer;
BEGIN
strlen:=length(str);
TEInsert(POINTER(ORD(@str)+1),strlen,theText);
TEIdle(theText);
HLock( handle(theText));
IF (not scrollflg) then
begin
scrollup:=theText^^.lineHeight;
curlines:=theText^^.nLines;
linepos:=curlines*scrollup;
endpos:=theText^^.ViewRect.bottom;
if (linepos>=endpos) then
scrollflg:=true;
end;
if scrollflg then TEScroll(0,-theText^^.lineHeight,theText)H
HUnlock( handle(theText));
IF logfile = 1 THEN
Begin
resultCode:= FSWrite (fileNumber, strlen, POINTER(ORD(@str)+1));
if resultCode <> NoErr then logfile:=0;
End;
END;
Function IntToString(num: Integer):str255;
{integer to string}
VAR
s: Str255;
longnum: LongInt;
BEGIN
longnum:=num;
NumToString(longnum, s);
IntToString:=s;
END;
Function KbdType: Integer;
{ Get low memory value at $21E, a byte, the keyboard no. }
magicHandle=^magicptr;
magicptr = ^magic;
magic = packed record
case boolean of
true: (l: longint);
false: (byte3,byte2,byte1,byte0: Byte)
end;
Var
tempHandle: Handle; {handle to signed byte}
magicman: magicHandle; {handle to magic}
addr: INTEGER;
mysize: INTEGER;
BEGIN
addr:= $021E;
mysize:=SIZEOF(magicman);
tempHandle:=NewHandle(mysize);
magicman:=magicHandle(tempHandle);
magicman^:=pointer(addr);
KbdType:=magicman^^.byte3;
disposHandle(tempHandle);
END;
Procedure ShowIntlNation;
{ Show }
VAR
country: integer;
ih: intl0Hndl;
s:str255;
known: Boolean;
BEGIN
ih := intl0Hndl(IUGetIntl(0)); { get INTL 0 resource }
country := (ih^^.intl0Vers) div 16; { country is upper byte
}
s:='This Mac is configured for ';
known:=true; {be optomistic}
{ There are symbolic constants for these (verUS, verFrance, etc.),
but unless even if you have the latest update to your development
system, you probably won't have all 26. I've hard-coded them for
clarity. }
CASE country OF
0: s:=concat(s,'the US or Canada');
1: s:=concat(s,'France');
2: s:=concat(s,'U.K. or Ireland');
3: s:=concat(s,'Deutschland'); { Germany }
4: s:=concat(s,'Italia');
5: s:=concat(s,'Nederland'); { Netherlands }
6: s:=concat(s,'Belgique ou Luxembourg');
7: s:=concat(s,'Sverige'); { Sweden }
8: s:=concat(s,'Españá'); { Spain }
9: s:=concat(s,'Danmark');
10: s:=concat(s,'Portugal');
11: s:=concat(s,'Quebec'); { French Canada }
12: s:=concat(s,'Norge'); { Norway }
13: s:=concat(s,'Yisra'el');
14: s:=concat(s,'Nippon'); { Japan }
15: s:=concat(s,'Australia or New Zealand');
16: s:=concat(s,'Arabiyah');
17: s:=concat(s,'Suomi'); { Finland }
18: s:=concat(s,'Suisse'); { French Swiss }
19: s:=concat(s,'Schweiz'); { German Swiss }
20: s:=concat(s,'Ellas'); { Greece }
21: s:=concat(s,'Island'); { Iceland }
22: s:=concat(s,'Malta');
23: s:=concat(s,'Kypros'); { Cyprus }
24: s:=concat(s,'Türkiye');
25: s:=concat(s,'Jugoslavija');
OTHERWISE
Begin
known:=false;
s:=concat(s,'an unknown country, #',IntToString(country),'. ');
End;
END; {case}
if known then s:=concat(s,'. ');
s:=concat(s,CR,CR);
PutString(s);
END;
Procedure ShowModel;
{ Guess which type of Macintosh keyboard }
Var
s,ss:str255;
Kbd:INTEGER;
BEGIN
{ Use derived keyboard numbers }
Kbd:=KbdType;
ss:=IntToString(Kbd);
s:=concat('The keyboard type is ',ss);
CASE Kbd OF
11: s:=concat(s,', which is a Mac Plus keyboard.');
3: s:=concat(s,', which is the Classic Mac
keyboard.');
OTHERWISE s:=concat(s,', which is unknown.');
END; {case}
s:=concat(s,CR);
PutString(s);
END;
Procedure GuessKeyNation;
{ Guess which country keyboard mappings are set for }
Var
s: str255;
BEGIN
{ Try mapping of certain keys to figure US vs. non-US board }
IF (KeyTrans(USspKey,0) = Space) THEN
begin
s:='This is US, Canadian or down under.';
end {IF..THEN}
ELSE
BEGIN
IF (KeyTrans(UKspKey,0) = Space) THEN
BEGIN
{ Use UK "/" key to guess at nationality }
CASE KeyTrans(UKslKey,0) OF
Slash: { / UK }
s:=concat(s,'I am British or Dutch.');
Ograve: { ò Italian }
s:=concat(s,'Sono Italiano.');
Equals: { = French }
s:=concat(s,'Je suis français, suisse ou belge.');
Eaigu: { é French Canadian }
s:=concat(s,'Je suis canadien.');
Minus: { - German, Spanish, Swedish }
{ Use UK accent grave (dead `) to tell
German, Spanish, and Swedish }
CASE KeyTrans(UKgrKey,0) OF
Hash: { # German }
s:=concat(s,'Ich bin ein Deutscher.');
Degree: { ç Spanish }
s:=concat(s,'Habla Español.');
Apos: { ' Swedish }
s:=concat(s,'This is Swedish.');
OTHERWISE { I have no country! }
s:=concat(s,'No tengo un país!');
END; {case UKgrKey}
OTHERWISE
begin
s:=concat(s,'I am a Mac without a country!');
end; {otherwise}
END; {CASE}
END {IF...THEN}
ELSE
begin
s:=concat(s,'Neither US nor European, what is it?');
end; {else}
END; {IF..THEN..ELSE}
s:=concat(s,CR,CR,'Type keys, or click mouse to quit.',CR);
PutString(s);
END; {proc}
Procedure DoMyStuff;
Var
s: str255;
BEGIN
OpenLog; { log file }
ShowIntlNation; { Find country code }
ShowModel; { Examine keyboard type }
GuessKeyNation; { Look at key mappings }
showWindow(my window);
END;
{ Following is standard Mac Shell stolen from TML Examples}
PROCEDURE DoMenu(select:longint);
Var
Menu_No: integer;
Item_No: integer;
NameHolder: Str255; {DA or Font name holder }
DNA: integer; {OpenDA result
Begin
If select <> 0 then
begin
Menu_No := HiWord(select); {get the Hi word of...}
Item_no := LoWord(select); {get the Lo word of...}
Case Menu_No of
AppleMenu:
Begin
GetItem(GetMHandle(AppleMenu), Item_No, NameHolder);
DNA := OpenDeskAcc(NameHolder);
End; { applemenu}
FileMenu: Finished:=true; {quit}
EditMenu:
Begin
If Not SystemEdit(Item_no - 1) then
Case Item_No of
1: begin end; {undo}
{ 2: line divider}
3: TECut(theText); {cut}
4: TECopy(theText ); {copy}
5: TEPaste(theText ); {paste}
6: TEDelete(theText ); {clear}
End; {case}
End; {editmenu}
end; {case menu_no}
HiliteMenu(0); {unhilite after processing menu}
end; {If select <> 0}
End; {of DoMenu procedure}
PROCEDURE doMouseDowns(Event:EventRecord);
Var
Location :integer;
WindowPointedTo :WindowPtr;
MouseLoc :Point;
WindoLoc :integer;
Begin
MouseLoc := Event.Where;
WindoLoc := FindWindow(MouseLoc, WindowPointedTo);
Case WindoLoc of
inMenuBar: DoMenu(MenuSelect(MouseLoc));
inSysWindow: SystemClick(Event,WindowPointedTo);
inContent:
if WindowPointedto <> FrontWindow then
SelectWindow(WindowPointedTo);
inGrow: Begin End; {no grow} inDrag: D
DragWindow(WindowPointedTo,MouseLoc,DragArea); inGoAway:
Begin
If TrackGoAway(WindowPointedTo,MouseLoc) then
Begin
DisposeWindow(WindowPointedTo);
finished:=true;
End;
End; {inGoAway}
End{ of case};
End;
PROCEDURE doKeyDowns(Event:EventRecord);
magicHandle=^magicptr;
magicptr = ^magic;
magic = packed record
case boolean of
true: (l: longint);
false: (byte3,byte2,byte1:Byte;chr0: Char)
end;
Var
CharCode: char;
keycode: Byte;
mods: INTEGER;
s: str255;
keyc: INTEGER;
asc: INTEGER;
tempHandle: Handle; {handle to signed byte}
magicman: magicHandle; {handle to magic}
mysize: INTEGER;
Begin
mysize :=SIZEOF(magicman);
tempHandle :=NewHandle(mysize);
magicman :=magicHandle(tempHandle);
magicman^^.l :=Event.message;
CharCode :=magicman^^.chr0;
keycode :=magicman^^.byte1;
keyc := keycode;
mods := Event.modifiers;
s:=concat('Key #',IntToString(keyc));
IF BitAnd(mods,optionKey) = optionKey THEN
s:=concat(s,' with Option');
IF BitAnd(mods,shiftKey) = shiftKey THEN
s:=concat(s,', shifted');
IF BitAnd(mods,alphaLock) = alphaLock THEN
s:=concat(s,', Caps Locked');
asc := KeyTrans(keyc,mods); { try translate to ASCII }
{ Don't want to print control characters }
IF asc >= 32 THEN
BEGIN
s:=concat(s,' is ',chr(asc),' (ascii ',IntToString(asc),').');
END;
s:=concat(s,CR);
PutString(s)
END;
PROCEDURE doActivates(Event: EventRecord);
Var TargetWindow:WindowPtr;
Begin
TargetWindow := pointer(ord4(Event.message));
If Odd(Event.modifiers) then
Begin {activate}
SetPort(TargetWindow);
End
else {deactivate}
Begin End;
End;
PROCEDURE doUpdates(Event:EventRecord);
Var
UpDateWindow,TempPort: WindowPtr;
Begin
UpDateWindow := pointer(ord4(Event.message));
if UpDateWindow = mywindow then
Begin
GetPort(TempPort); {Save the current port}
SetPort(my window); {set the port to one in Evt.msg}
BeginUpDate(my window);
EraseRect(my window^.visRgn^^.rgnBBox);
TEUpdate(my window^.visRgn^^.rgnBBox,theText);
EndUpDate(my window);
SetPort(TempPort); {restore to the previous port}
End;
End;
PROCEDURE EndProgram;
Var
resultcode: OSErr;
Begin
IF logfile =1 THEN
begin
resultCode:= FSClose(fileNumber);
end;
ExitToShell;
End;
PROCEDURE MainEventLoop;
Var Event:EventRecord;
DoIt: Boolean;
Begin
InitCursor;
Repeat
SystemTask; {support DAs}
DoIt := GetNextEvent(EveryEvent,Event);
If DoIt{is true} then {we'll DoIt}
Case Event.what of
mouseDown : doMouseDowns(Event); {1}
mouseUp : begin end; {2}
KeyDown : doKeyDowns (Event); {3}
keyUp : begin end; {4}
autoKey : begin end; {5}
updateEvt : doUpdates (Event); {6}
diskEvt : begin end; {7}
activateEvt : doActivates (Event); {8}
{abort evt now reserved for future} {9}
networkEvt : begin end; {A}
driverEvt : begin end; {B}
app1Evt : begin end; {C}
app2Evt : begin end; {D}
app3Evt : begin end; {E}
app4Evt : begin end; {F}
End;{of Case}
Until Finished; {end program}
EndProgram; {call our finish up stuff}
End;
PROCEDURE InitThings;
Begin
InitGraf(@thePort);
ClockCursor := GetCursor(watchCursor);
HLock(Handle(ClockCursor));
SetCursor(ClockCursor^^);
InitFonts;
InitWindows;
InitMenus;
TEInit;
InitDialogs(Nil);
FlushEvents(everyEvent,0);
scrollflg:=false; {too early to scroll!}
finished:=false; {clear program terminator}
End;
PROCEDURE SetupLimits;
Begin
Screen := ScreenBits.Bounds; {screen 512 by 342 pixels}
SetRect(DragArea,Screen.left+4,Screen.top+24,
Screen.right-4,Screen.bottom-4);
SetRect(GrowArea,Screen.left,Screen.top+24,
Screen.right,Screen.bottom);
End;
Procedure SetupWindows;
Const
sbarwidth=16; {width of scroll bars}
Var
myrect: Rect;
windtype: integer;
Visible: boolean;
GoAway: boolean;
RefVal: LongInt;
Begin
SetRect(myrect,10,40,500,330); { window size -global cord}
windtype := 4; {set window type - nogrowdocproc
}
Visible := false; {set the window to invisible }
GoAway := true; {give the window a GoAway box }
my window:= NewWindow(Nil, { allocate space in Heap}
myrect,
'Keyboard Sleuth',
Visible,
windtype,
POINTER(-1), {front}
GoAway, { goaway region in title area }
RefVal); { 32-bit value used by App}
SetPort(my window);
TextFont(Geneva);
{ Set Up Text Edit Record for this Window }
with myWindow^.portRect do
SetRect(ViewRect,4,4,right-(sbarwidth-1),
bottom-(sbarwidth-1));
DestRect:=ViewRect;
theText:= TENew(DestRect,ViewRect);
{ NOTE: NewWindow initiated an ActivatEvt and an }
{ UpDateEvt event. Being queued up by event manager.}
End;
PROCEDURE SetupMenus;
Var myMenu :MenuHandle;
NameHolder :STR255;
Begin
myMenu := GetMenu(AppleMenu);{from resource file}
AddResMenu(myMenu,'DRVR'); {adds DAs}
InsertMenu(myMenu,0);
myMenu := GetMenu(FileMenu); {Quiting...}
InsertMenu(myMenu,0);
myMenu := GetMenu(EditMenu); {DA support...}
InsertMenu(myMenu,0);
DrawMenuBar; {show the menu bar}
End;
{ ---------------- MAIN PROGRAM ------------ }
BEGIN
InitThings;
SetupLimits;
SetupWindows; {do first so its low in heap}
SetupMenus;
DoMyStuff;
MainEventLoop;
END.
; EXAMPLE ASSEMBLY SUBROUTINE
; key test thing
; VERSION 11 July 1986
; (C) Copyright 1986 MacTutor
INCLUDE MACTRAPS.D
XDEF keyTrans ; required for linker
; =========== system globals =============
Key1Trans equ $29E; { Low Memory Globals }
Key2Trans equ $2A2;
; =========== key translation routine ======
KeyTrans:
; key code (2 bytes) and modifiers (2 bytes) passed
; ascii char code returned (2 bytes)
link a6, #-4
movem.l A0-A1/D0-D2, -(SP)
; get key code from stack into D2
; get modifiers from stack into D1
move.w 8(A6), D1 ;second parameter (modifiers)
move.w 10(A6), D2 ;first parameter (key code)
move.w #9, D0 ;shift count for flags
lsr D0,D1 ;move bits to lower byte
andi #7, D1 ;mask 3 bits to get modify in D1
cmpi #64, D2 ;keycode <64 then key1
BGE key2 ;=>64 then key2
key1:
clr.l D0
LEA showit, A0 ;get return address
move.l A0, -(SP) ;return address to stack
move.l #key1trans, A0 ;global for ptr to key1trans
move.l (A0), A0 ;get address of key1trans
jmp (A0) ;call subroutine
key2:
subi #64, D2 ;adjust key code
clr.l D0
LEA showit, A0 ;get return address
move.l A0, -(SP) ;return address to stack
move.l #key2trans, A0 ;global for ptr to key2trans
move.l (A0), A0 ;get address of key1trans
jmp (A0) ;call subroutine
showit:
; return function result from D0
move.w D0, 12(A6) ;pass back function result
movem.l (SP)+, A0-A1/D0-D2
unlk a6
move.l (SP)+, A0 ;get return address
addq.l #4, SP ;remove passed parameters
JMP (A0)
; ------------ END OF PROGRAM ----------------
Link file
!PAS$Xfer
/Globals -4
PAS$Library
OSTraps
ToolTraps
PackTraps
keytrans
/TYPE 'APPL' 'DAV1'
/BUNDLE
/RESOURCES
Sleuth/Rsrc
$
* Sleuth.R
*
Sleuth/Rsrc.Rel
Type DAV1 = STR
,0
D. Smith & J. West
Type FREF
,128
APPL 0
Type BNDL
,128
DAV1 0
ICN#
0 128
FREF
0 128
Type MENU
* the desk acc menu
,256
\14
* the file menu
,257
Quit /Q
* the edit menu
,258
Undo /Z
(-
Cut /X
Copy /C
Paste /V
Clear
Type ICN# = GNRL
,128 (32)
.H
001F C000
0060 2000
0080 1000
011F 8800
0120 4400
0240 2200
0240 1200
0280 1200
0380 1200
0000 1200
0000 2200
0000 2400
0000 4400
0000 8800
0001 1000
0002 2000
0004 4000
0004 8000
0004 8000
0004 8000
0004 8000
0004 8000
3FFF FFFC
76DB 6DBA
B6DB 6DB9
8000 0001
BB6D B6DF
BB6D B6DF
8000 0001
B6FF FEED
76FF FEEE
3FFF FFFC
*
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF
FFFFFFFF