{ $Id$ This file is part of the Free Pascal run time library. Copyright (c) 1999-2000 by Florian Klaempfl member of the Free Pascal development team Keyboard unit for linux See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit Keyboard; interface {$i keybrdh.inc} Const AltPrefix : byte = 0; ShiftPrefix : byte = 0; CtrlPrefix : byte = 0; Function RawReadKey:char; Function RawReadString : String; Function KeyPressed : Boolean; {$ifndef NotUseTree} Procedure AddSequence(Const St : String; Char,Scan :byte); Function FindSequence(Const St : String;var AChar, Ascan : byte) : boolean; {$endif NotUseTree} implementation uses Mouse, {$ifndef NotUseTree} Strings, TermInfo, {$endif NotUseTree} Linux; {$i keyboard.inc} var OldIO : TermIos; {$ifdef logging} f : text; {$endif logging} { list of all dos scancode for key giving 0 as char } Const kbNoKey = $00; kbAltEsc = $01; kbAltSpace = $02; kbCtrlIns = $04; kbShiftIns = $05; kbCtrlDel = $06; kbShiftDel = $07; kbAltBack = $08; kbAltShiftBack= $09; kbShiftTab = $0F; kbAltQ = $10; kbAltW = $11; kbAltE = $12; kbAltR = $13; kbAltT = $14; kbAltY = $15; kbAltU = $16; kbAltI = $17; kbAltO = $18; kbAltP = $19; kbAltLftBrack = $1A; kbAltRgtBrack = $1B; kbAltA = $1E; kbAltS = $1F; kbAltD = $20; kbAltF = $21; kbAltG = $22; kbAltH = $23; kbAltJ = $24; kbAltK = $25; kbAltL = $26; kbAltSemiCol = $27; kbAltQuote = $28; kbAltOpQuote = $29; kbAltBkSlash = $2B; kbAltZ = $2C; kbAltX = $2D; kbAltC = $2E; kbAltV = $2F; kbAltB = $30; kbAltN = $31; kbAltM = $32; kbAltComma = $33; kbAltPeriod = $34; kbAltSlash = $35; kbAltGreyAst = $37; kbF1 = $3B; kbF2 = $3C; kbF3 = $3D; kbF4 = $3E; kbF5 = $3F; kbF6 = $40; kbF7 = $41; kbF8 = $42; kbF9 = $43; kbF10 = $44; kbHome = $47; kbUp = $48; kbPgUp = $49; kbLeft = $4B; kbCenter = $4C; kbRight = $4D; kbAltGrayPlus = $4E; kbend = $4F; kbDown = $50; kbPgDn = $51; kbIns = $52; kbDel = $53; kbShiftF1 = $54; kbShiftF2 = $55; kbShiftF3 = $56; kbShiftF4 = $57; kbShiftF5 = $58; kbShiftF6 = $59; kbShiftF7 = $5A; kbShiftF8 = $5B; kbShiftF9 = $5C; kbShiftF10 = $5D; kbCtrlF1 = $5E; kbCtrlF2 = $5F; kbCtrlF3 = $60; kbCtrlF4 = $61; kbCtrlF5 = $62; kbCtrlF6 = $63; kbCtrlF7 = $64; kbCtrlF8 = $65; kbCtrlF9 = $66; kbCtrlF10 = $67; kbAltF1 = $68; kbAltF2 = $69; kbAltF3 = $6A; kbAltF4 = $6B; kbAltF5 = $6C; kbAltF6 = $6D; kbAltF7 = $6E; kbAltF8 = $6F; kbAltF9 = $70; kbAltF10 = $71; kbCtrlPrtSc = $72; kbCtrlLeft = $73; kbCtrlRight = $74; kbCtrlend = $75; kbCtrlPgDn = $76; kbCtrlHome = $77; kbAlt1 = $78; kbAlt2 = $79; kbAlt3 = $7A; kbAlt4 = $7B; kbAlt5 = $7C; kbAlt6 = $7D; kbAlt7 = $7E; kbAlt8 = $7F; kbAlt9 = $80; kbAlt0 = $81; kbAltMinus = $82; kbAltEqual = $83; kbCtrlPgUp = $84; kbF11 = $85; kbF12 = $86; kbShiftF11 = $87; kbShiftF12 = $88; kbCtrlF11 = $89; kbCtrlF12 = $8A; kbAltF11 = $8B; kbAltF12 = $8C; kbCtrlUp = $8D; kbCtrlMinus = $8E; kbCtrlCenter = $8F; kbCtrlGreyPlus= $90; kbCtrlDown = $91; kbCtrlTab = $94; kbAltHome = $97; kbAltUp = $98; kbAltPgUp = $99; kbAltLeft = $9B; kbAltRight = $9D; kbAltend = $9F; kbAltDown = $A0; kbAltPgDn = $A1; kbAltIns = $A2; kbAltDel = $A3; kbAltTab = $A5; {$ifdef Unused} type TKeyState = Record Normal, Shift, Ctrl, Alt : word; end; Const KeyStates : Array[0..255] of TKeyState ( ); {$endif Unused} Procedure SetRawMode(b:boolean); Var Tio : Termios; Begin TCGetAttr(1,Tio); if b then begin OldIO:=Tio; Tio.c_iflag:=Tio.c_iflag and (not (IGNBRK or BRKINT or PARMRK or ISTRIP or INLCR or IGNCR or ICRNL or IXON)); Tio.c_lflag:=Tio.c_lflag and (not (ECHO or ECHONL or ICANON or ISIG or IEXTEN)); end else Tio := OldIO; TCSetAttr(1,TCSANOW,Tio); End; type chgentry=packed record tab, idx, oldtab, oldidx : byte; oldval, newval : word; end; kbentry=packed record kb_table, kb_index : byte; kb_value : word; end; const kbdchanges=10; kbdchange:array[1..kbdchanges] of chgentry=( (tab:8; idx:$3b; oldtab:0; oldidx:$3b; oldval:0; newval:0), (tab:8; idx:$3c; oldtab:0; oldidx:$3c; oldval:0; newval:0), (tab:8; idx:$3d; oldtab:0; oldidx:$3d; oldval:0; newval:0), (tab:8; idx:$3e; oldtab:0; oldidx:$3e; oldval:0; newval:0), (tab:8; idx:$3f; oldtab:0; oldidx:$3f; oldval:0; newval:0), (tab:8; idx:$40; oldtab:0; oldidx:$40; oldval:0; newval:0), (tab:8; idx:$41; oldtab:0; oldidx:$41; oldval:0; newval:0), (tab:8; idx:$42; oldtab:0; oldidx:$42; oldval:0; newval:0), (tab:8; idx:$43; oldtab:0; oldidx:$43; oldval:0; newval:0), (tab:8; idx:$44; oldtab:0; oldidx:$44; oldval:0; newval:0) ); KDGKBENT=$4B46; KDSKBENT=$4B47; procedure PatchKeyboard; var e : ^chgentry; entry : kbentry; i : longint; begin for i:=1 to kbdchanges do begin e:=@kbdchange[i]; entry.kb_table:=e^.tab; entry.kb_index:=e^.idx; Ioctl(stdinputhandle,KDGKBENT,@entry); e^.oldval:=entry.kb_value; entry.kb_table:=e^.oldtab; entry.kb_index:=e^.oldidx; ioctl(stdinputhandle,KDGKBENT,@entry); e^.newval:=entry.kb_value; end; for i:=1to kbdchanges do begin e:=@kbdchange[i]; entry.kb_table:=e^.tab; entry.kb_index:=e^.idx; entry.kb_value:=e^.newval; Ioctl(stdinputhandle,KDSKBENT,@entry); end; end; procedure UnpatchKeyboard; var e : ^chgentry; entry : kbentry; i : longint; begin for i:=1 to kbdchanges do begin e:=@kbdchange[i]; entry.kb_table:=e^.tab; entry.kb_index:=e^.idx; entry.kb_value:=e^.oldval; Ioctl(stdinputhandle,KDSKBENT,@entry); end; end; { Buffered Input routines } const InSize=256; var InBuf : array [0..InSize-1] of char; InCnt, InHead, InTail : longint; function ttyRecvChar:char; var Readed,i : longint; begin {Buffer Empty? Yes, Input from StdIn} if (InHead=InTail) then begin {Calc Amount of Chars to Read} i:=InSize-InHead; if InTail>InHead then i:=InTail-InHead; {Read} Readed:=fdRead(StdInputHandle,InBuf[InHead],i); {Increase Counters} inc(InCnt,Readed); inc(InHead,Readed); {Wrap if End has Reached} if InHead>=InSize then InHead:=0; end; {Check Buffer} if (InCnt=0) then ttyRecvChar:=#0 else begin ttyRecvChar:=InBuf[InTail]; dec(InCnt); inc(InTail); if InTail>=InSize then InTail:=0; end; end; Const KeyBufferSize = 20; var KeyBuffer : Array[0..KeyBufferSize-1] of Char; KeyPut, KeySend : longint; Procedure PushKey(Ch:char); Var Tmp : Longint; Begin Tmp:=KeyPut; Inc(KeyPut); If KeyPut>=KeyBufferSize Then KeyPut:=0; If KeyPut<>KeySend Then KeyBuffer[Tmp]:=Ch Else KeyPut:=Tmp; End; Function PopKey:char; Begin If KeyPut<>KeySend Then Begin PopKey:=KeyBuffer[KeySend]; Inc(KeySend); If KeySend>=KeyBufferSize Then KeySend:=0; End Else PopKey:=#0; End; Procedure PushExt(b:byte); begin PushKey(#0); PushKey(chr(b)); end; const AltKeyStr : string[38]='qwertyuiopasdfghjklzxcvbnm1234567890-='; AltCodeStr : string[38]=#016#017#018#019#020#021#022#023#024#025#030#031#032#033#034#035#036#037#038+ #044#045#046#047#048#049#050#120#121#122#123#124#125#126#127#128#129#130#131; Function FAltKey(ch:char):byte; var Idx : longint; Begin Idx:=Pos(ch,AltKeyStr); if Idx>0 then FAltKey:=byte(AltCodeStr[Idx]) else FAltKey:=0; End; { This one doesn't care about keypresses already processed by readkey } { and waiting in the KeyBuffer, only about waiting keypresses at the } { TTYLevel (including ones that are waiting in the TTYRecvChar buffer) } function sysKeyPressed: boolean; var fdsin : fdSet; begin if (InCnt>0) then sysKeyPressed:=true else begin FD_Zero(fdsin); fd_Set(StdInputHandle,fdsin); sysKeypressed:=(Select(StdInputHandle+1,@fdsin,nil,nil,0)>0); end; end; Function KeyPressed:Boolean; Begin Keypressed := (KeySend<>KeyPut) or sysKeyPressed; End; Function IsConsole : Boolean; var ThisTTY: String[30]; FName : String; TTYfd: longint; begin IsConsole:=false; { check for tty } ThisTTY:=TTYName(stdinputhandle); if IsATTY(stdinputhandle) then begin { running on a tty, find out whether locally or remotely } if (Copy(ThisTTY, 1, 8) = '/dev/tty') and (ThisTTY[9] >= '0') and (ThisTTY[9] <= '9') then begin { running on the console } FName:='/dev/vcsa' + ThisTTY[9]; { check with read only as it might already be open in ReadWrite by video unit } TTYFd:=fdOpen(FName, 0, Open_RdOnly); { open console } end else TTYFd:=-1; if TTYFd<>-1 then begin IsConsole:=true; fdClose(TTYFd); end; end; end; Const LastMouseEvent : TMouseEvent = ( Buttons : 0; X : 0; Y : 0; Action : 0; ); {$ifndef NotUseTree} procedure GenMouseEvent; var MouseEvent: TMouseEvent; ch : char; fdsin : fdSet; begin FD_Zero(fdsin); fd_Set(StdInputHandle,fdsin); Fillchar(MouseEvent,SizeOf(TMouseEvent),#0); if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); ch:=ttyRecvChar; { Other bits are used for Shift, Meta and Ctrl modifiers PM } case (ord(ch)-ord(' ')) and 3 of 0 : {left button press} MouseEvent.buttons:=1; 1 : {middle button pressed } MouseEvent.buttons:=2; 2 : { right button pressed } MouseEvent.buttons:=4; 3 : { no button pressed }; end; if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); ch:=ttyRecvChar; MouseEvent.x:=Ord(ch)-ord(' ')-1; if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); ch:=ttyRecvChar; MouseEvent.y:=Ord(ch)-ord(' ')-1; if (MouseEvent.buttons<>0) then MouseEvent.action:=MouseActionDown else begin if (LastMouseEvent.Buttons<>0) and ((LastMouseEvent.X<>MouseEvent.X) or (LastMouseEvent.Y<>MouseEvent.Y)) then begin MouseEvent.Action:=MouseActionMove; MouseEvent.Buttons:=LastMouseEvent.Buttons; {$ifdef DebugMouse} Writeln(system.stderr,' Mouse Move (',MouseEvent.X,',',MouseEvent.Y,')'); {$endif DebugMouse} PutMouseEvent(MouseEvent); MouseEvent.Buttons:=0; end; MouseEvent.Action:=MouseActionUp; end; PutMouseEvent(MouseEvent); {$ifdef DebugMouse} if MouseEvent.Action=MouseActionDown then Write(system.stderr,'Button down : ') else Write(system.stderr,'Button up : '); Writeln(system.stderr,'buttons = ',MouseEvent.Buttons,' (',MouseEvent.X,',',MouseEvent.Y,')'); {$endif DebugMouse} LastMouseEvent:=MouseEvent; end; type TProcedure = procedure; PTreeElement = ^TTreeElement; TTreeElement = record Next,Parent,Child : PTreeElement; CanBeTerminal : boolean; char : byte; ScanValue : byte; CharValue : byte; SpecialHandler : TProcedure; end; var RootTree : Array[0..255] of PTreeElement; function NewPTree(ch : byte;Pa : PTreeElement) : PTreeElement; var PT : PTreeElement; begin New(PT); FillChar(PT^,SizeOf(TTreeElement),#0); PT^.char:=ch; PT^.Parent:=Pa; if Assigned(Pa) and (Pa^.Child=nil) then Pa^.Child:=PT; NewPTree:=PT; end; function DoAddSequence(Const St : String; AChar,AScan :byte) : PTreeElement; var CurPTree,NPT : PTreeElement; c : byte; i : longint; begin if St='' then begin DoAddSequence:=nil; exit; end; CurPTree:=RootTree[ord(st[1])]; if CurPTree=nil then begin CurPTree:=NewPTree(ord(st[1]),nil); RootTree[ord(st[1])]:=CurPTree; end; for i:=2 to Length(St) do begin NPT:=CurPTree^.Child; c:=ord(St[i]); if NPT=nil then NPT:=NewPTree(c,CurPTree); CurPTree:=nil; while assigned(NPT) and (NPT^.charAScan) or (CharValue<>AChar) then Writeln(system.stderr,'key "',st,'" changed value'); if (ScanValue<>AScan) then Writeln(system.stderr,'Scan was ',ScanValue,' now ',AScan); if (CharValue<>AChar) then Writeln(system.stderr,'Char was ',chr(CharValue),' now ',chr(AChar)); {$endif DEBUG} ScanValue:=AScan; CharValue:=AChar; end; end else with CurPTree^ do begin CanBeTerminal:=True; ScanValue:=AScan; CharValue:=AChar; end; DoAddSequence:=CurPTree; end; procedure AddSequence(Const St : String; AChar,AScan :byte); begin DoAddSequence(St,AChar,AScan); end; { Returns the Child that as c as char if it exists } Function FindChild(c : byte;Root : PTreeElement) : PTreeElement; var NPT : PTreeElement; begin if not assigned(Root) then begin FindChild:=nil; exit; end; NPT:=Root^.Child; while assigned(NPT) and (NPT^.charKeyPut then begin RawReadKey:=PopKey; exit; end; {Wait for Key} if not sysKeyPressed then begin FD_Zero (fdsin); FD_Set (StdInputHandle,fdsin); Select (StdInputHandle+1,@fdsin,nil,nil,nil); end; RawReadKey:=ttyRecvChar; end; Function RawReadString : String; Var ch : char; fdsin : fdSet; St : String; Begin St:=RawReadKey; FD_Zero (fdsin); FD_Set (StdInputHandle,fdsin); Repeat if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); if SysKeyPressed then ch:=ttyRecvChar else ch:=#0; if ch<>#0 then St:=St+ch; Until ch=#0; RawReadString:=St; end; Function ReadKey(var IsAlt : boolean):char; Var ch : char; {$ifdef NotUseTree} OldState : longint; State : longint; {$endif NotUseTree} is_delay : boolean; fdsin : fdSet; store : array [0..8] of char; arrayind : byte; {$ifndef NotUseTree} NPT,NNPT : PTreeElement; {$else NotUseTree} procedure GenMouseEvent; var MouseEvent: TMouseEvent; begin Fillchar(MouseEvent,SizeOf(TMouseEvent),#0); case ch of #32 : {left button pressed } MouseEvent.buttons:=1; #33 : {middle button pressed } MouseEvent.buttons:=2; #34 : { right button pressed } MouseEvent.buttons:=4; #35 : { no button pressed }; end; if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); ch:=ttyRecvChar; MouseEvent.x:=Ord(ch)-ord(' ')-1; if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); ch:=ttyRecvChar; MouseEvent.y:=Ord(ch)-ord(' ')-1; if (MouseEvent.buttons<>0) then MouseEvent.action:=MouseActionDown else begin if (LastMouseEvent.Buttons<>0) and ((LastMouseEvent.X<>MouseEvent.X) or (LastMouseEvent.Y<>MouseEvent.Y)) then begin MouseEvent.Action:=MouseActionMove; MouseEvent.Buttons:=LastMouseEvent.Buttons; PutMouseEvent(MouseEvent); MouseEvent.Buttons:=0; end; MouseEvent.Action:=MouseActionUp; end; PutMouseEvent(MouseEvent); LastMouseEvent:=MouseEvent; end; {$endif NotUseTree} procedure RestoreArray; var i : byte; begin for i:=0 to arrayind-1 do PushKey(store[i]); end; Begin IsAlt:=false; {Check Buffer first} if KeySend<>KeyPut then begin ReadKey:=PopKey; exit; end; {Wait for Key} if not sysKeyPressed then begin FD_Zero (fdsin); FD_Set (StdInputHandle,fdsin); Select (StdInputHandle+1,@fdsin,nil,nil,nil); end; ch:=ttyRecvChar; {$ifndef NotUseTree} NPT:=RootTree[ord(ch)]; if not assigned(NPT) then PushKey(ch) else begin FD_Zero(fdsin); fd_Set(StdInputHandle,fdsin); store[0]:=ch; arrayind:=1; while assigned(NPT) and syskeypressed do begin if (InCnt=0) then Select(StdInputHandle+1,@fdsin,nil,nil,10); ch:=ttyRecvChar; NNPT:=FindChild(ord(ch),NPT); if assigned(NNPT) then Begin NPT:=NNPT; if NPT^.CanBeTerminal and assigned(NPT^.SpecialHandler) then break; End; if ch<>#0 then begin store[arrayind]:=ch; inc(arrayind); end; end; if assigned(NPT) and NPT^.CanBeTerminal then begin if assigned(NPT^.SpecialHandler) then begin NPT^.SpecialHandler; ch:=#0; end else if NPT^.CharValue<>0 then PushKey(chr(NPT^.CharValue)) else if NPT^.ScanValue<>0 then PushExt(NPT^.ScanValue); end else RestoreArray; {$else NotUseTree} {Esc Found ?} If (ch=#27) then begin FD_Zero(fdsin); fd_Set(StdInputHandle,fdsin); State:=1; store[0]:=#27; arrayind:=1; {$ifdef logging} write(f,'Esc'); {$endif logging} if InCnt=0 then Select(StdInputHandle+1,@fdsin,nil,nil,10); while (State<>0) and (sysKeyPressed) do begin ch:=ttyRecvChar; store[arrayind]:=ch; inc(arrayind); {$ifdef logging} if ord(ch)>31 then write(f,ch) else write(f,'#',ord(ch):2); {$endif logging} OldState:=State; State:=0; case OldState of 1 : begin {Esc} case ch of 'a'..'z', '0'..'9', '-','=' : PushExt(FAltKey(ch)); 'A'..'N', 'P'..'Z' : PushExt(FAltKey(chr(ord(ch)+ord('a')-ord('A')))); #10 : PushKey(#10); #13 : PushKey(#10); #27 : begin IsAlt:=True; State:=1; end; #127 : PushExt(kbAltDel); '[' : State:=2; 'O' : State:=6; else RestoreArray; end; end; 2 : begin {Esc[} case ch of '[' : State:=3; 'A' : PushExt(kbUp); 'B' : PushExt(kbDown); 'C' : PushExt(kbRight); 'D' : PushExt(kbLeft); 'F' : PushExt(kbEnd); 'G' : PushKey('5'); 'H' : PushExt(kbHome); 'K' : PushExt(kbEnd); 'M' : State:=13; '1' : State:=4; '2' : State:=5; '3' : State:=12;{PushExt(kbDel)} '4' : PushExt(kbEnd); '5' : PushExt(73); '6' : PushExt(kbPgDn); '?' : State:=7; else RestoreArray; end; if ch in ['4'..'6'] then State:=255; end; 3 : begin {Esc[[} case ch of 'A' : PushExt(kbF1); 'B' : PushExt(kbF2); 'C' : PushExt(kbF3); 'D' : PushExt(kbF4); 'E' : PushExt(kbF5); else RestoreArray; end; end; 4 : begin {Esc[1} case ch of '~' : PushExt(kbHome); '7' : PushExt(kbF6); '8' : PushExt(kbF7); '9' : PushExt(kbF8); else RestoreArray; end; if (Ch<>'~') then State:=255; end; 5 : begin {Esc[2} case ch of '~' : PushExt(kbIns); '0' : pushExt(kbF9); '1' : PushExt(kbF10); '3' : PushExt($85){F11, but ShiftF1 also !!}; '4' : PushExt($86){F12, but Shift F2 also !!}; '5' : PushExt($56){ShiftF3}; '6' : PushExt($57){ShiftF4}; '8' : PushExt($58){ShiftF5}; '9' : PushExt($59){ShiftF6}; else RestoreArray; end; if (Ch<>'~') then State:=255; end; 12 : begin {Esc[3} case ch of '~' : PushExt(kbDel); '1' : PushExt($5A){ShiftF7}; '2' : PushExt($5B){ShiftF8}; '3' : PushExt($5C){ShiftF9}; '4' : PushExt($5D){ShiftF10}; else RestoreArray; end; if (Ch<>'~') then State:=255; end; 6 : begin {EscO Function keys in vt100 mode PM } case ch of 'P' : {F1}PushExt(kbF1); 'Q' : {F2}PushExt(kbF2); 'R' : {F3}PushExt(kbF3); 'S' : {F4}PushExt(kbF4); 't' : {F5}PushExt(kbF5); 'u' : {F6}PushExt(kbF6); 'v' : {F7}PushExt(kbF7); 'l' : {F8}PushExt(kbF8); 'w' : {F9}PushExt(kbF9); 'x' : {F10}PushExt(kbF10); 'D' : {keyLeft}PushExt($4B); 'C' : {keyRight}PushExt($4D); 'A' : {keyUp}PushExt($48); 'B' : {keyDown}PushExt($50); else RestoreArray; end; end; 7 : begin {Esc[? keys in vt100 mode PM } case ch of '0' : State:=11; '1' : State:=8; '7' : State:=9; else RestoreArray; end; end; 8 : begin {Esc[?1 keys in vt100 mode PM } case ch of 'l' : {local mode}; 'h' : {transmit mode}; ';' : { 'Esc[1;0c seems to be sent by M$ telnet app for no hangup purposes } state:=10; else RestoreArray; end; end; 9 : begin {Esc[?7 keys in vt100 mode PM } case ch of 'l' : {exit_am_mode}; 'h' : {enter_am_mode}; else RestoreArray; end; end; 10 : begin {Esc[?1; keys in vt100 mode PM } case ch of '0' : state:=11; else RestoreArray; end; end; 11 : begin {Esc[?1;0 keys in vt100 mode PM } case ch of 'c' : ; else RestoreArray; end; end; 13 : begin {Esc[M mouse prefix for xterm } GenMouseEvent; end; 255 : { just forget this trailing char }; end; if (State<>0) and (InCnt=0) then Select(StdInputHandle+1,@fdsin,nil,nil,10); end; if State=1 then PushKey(ch); {$endif NotUseTree} if ch='$' then begin { '$' means a delay of XX millisecs } is_delay :=false; Select(StdInputHandle+1,@fdsin,nil,nil,10); if (sysKeyPressed) then begin ch:=ttyRecvChar; is_delay:=(ch='<'); if not is_delay then begin PushKey('$'); PushKey(ch); end else begin {$ifdef logging} write(f,'$<'); {$endif logging} Select(StdInputHandle+1,@fdsin,nil,nil,10); while (sysKeyPressed) and (ch<>'>') do begin { Should we really repect this delay ?? } ch:=ttyRecvChar; {$ifdef logging} write(f,ch); {$endif logging} Select(StdInputHandle+1,@fdsin,nil,nil,10); end; end; end else PushKey('$'); end; end {$ifdef logging} writeln(f); {$endif logging} {$ifndef NotUseTree} ; ReadKey:=PopKey; {$else NotUseTree} else Begin case ch of #127 : PushKey(#8); else PushKey(ch); end; End; ReadKey:=PopKey; {$endif NotUseTree} End; function ShiftState:byte; var arg,shift : longint; begin arg:=6; shift:=0; {$Ifndef BSD} if IOCtl(StdInputHandle,TIOCLINUX,@arg) then begin if (arg and (2 or 8))<>0 then inc(shift,8); if (arg and 4)<>0 then inc(shift,4); if (arg and 1)<>0 then inc(shift,3); end; {$endif} ShiftState:=shift; end; { Exported functions } procedure InitKeyboard; begin SetRawMode(true); patchkeyboard; {$ifdef logging} assign(f,'keyboard.log'); rewrite(f); {$endif logging} if not IsConsole then begin { default for Shift prefix is ^ A} if ShiftPrefix = 0 then ShiftPrefix:=1; {default for Alt prefix is ^Z } if AltPrefix=0 then AltPrefix:=26; { default for Ctrl Prefix is ^W } if CtrlPrefix=0 then CtrlPrefix:=23; end; {$ifndef NotUseTree} LoadDefaultSequences; LoadTerminfoSequences; {$endif not NotUseTree} end; procedure DoneKeyboard; begin unpatchkeyboard; SetRawMode(false); {$ifdef logging} close(f); {$endif logging} end; function GetKeyEvent: TKeyEvent; function EvalScan(b:byte):byte; const DScan:array[0..31] of byte = ( $39, $02, $28, $04, $05, $06, $08, $28, $0A, $0B, $09, $0D, $33, $0C, $34, $35, $0B, $02, $03, $04, $05, $06, $07, $08, $09, $0A, $27, $27, $33, $0D, $34, $35); LScan:array[0..31] of byte = ( $29, $1E, $30, $2E, $20, $12, $21, $22, $23, $17, $24, $25, $26, $32, $31, $18, $19, $10, $13, $1F, $14, $16, $2F, $11, $2D, $15, $2C, $1A, $2B, $1B, $29, $0C); begin if (b and $E0)=$20 { digits / leters } then EvalScan:=DScan[b and $1F] else case b of $08:EvalScan:=$0E; { backspace } $09:EvalScan:=$0F; { TAB } $0D:EvalScan:=$1C; { CR } $1B:EvalScan:=$01; { esc } $40:EvalScan:=$03; { @ } $5E:EvalScan:=$07; { ^ } $60:EvalScan:=$29; { ` } else EvalScan:=LScan[b and $1F]; end; end; function EvalScanZ(b:byte):byte; begin EvalScanZ:=b; if b in [$3B..$44] { F1..F10 -> Alt-F1..Alt-F10} then EvalScanZ:=b+$2D; end; const {kbHome, kbUp, kbPgUp,Missing, kbLeft, kbCenter, kbRight, kbAltGrayPlus, kbend, kbDown, kbPgDn, kbIns, kbDel } CtrlArrow : array [kbHome..kbDel] of byte = {($77,$8d,$84,$8e,$73,$8f,$74,$90,$75,$91,$76);} (kbCtrlHome,kbCtrlUp,kbCtrlPgUp,kbNoKey,kbCtrlLeft, kbCtrlCenter,kbCtrlRight,kbAltGrayPlus,kbCtrlEnd, kbCtrlDown,kbCtrlPgDn,kbCtrlIns,kbCtrlDel); AltArrow : array [kbHome..kbDel] of byte = (kbAltHome,kbAltUp,kbAltPgUp,kbNoKey,kbAltLeft, kbCenter,kbAltRight,kbAltGrayPlus,kbAltEnd, kbAltDown,kbAltPgDn,kbAltIns,kbAltDel); var MyScan, SState : byte; MyChar : char; EscUsed,AltPrefixUsed,CtrlPrefixUsed,ShiftPrefixUsed,IsAlt,Again : boolean; begin {main} if PendingKeyEvent<>0 then begin GetKeyEvent:=PendingKeyEvent; PendingKeyEvent:=0; exit; end; MyChar:=Readkey(IsAlt); MyScan:=ord(MyChar); SState:=ShiftState; CtrlPrefixUsed:=false; AltPrefixUsed:=false; ShiftPrefixUsed:=false; EscUsed:=false; if IsAlt then SState:=SState or kbAlt; repeat again:=false; if Mychar=#0 then begin MyScan:=ord(ReadKey(IsAlt)); { Handle Ctrl- } if (SState and kbCtrl)<>0 then begin case MyScan of kbHome..kbDel : { cArrow } MyScan:=CtrlArrow[MyScan]; kbF1..KbF10 : { cF1-cF10 } MyScan:=MyScan+kbCtrlF1-kbF1; kbF11..KbF12 : { cF11-cF12 } MyScan:=MyScan+kbCtrlF11-kbF11; end; end { Handle Alt- } else if (SState and kbAlt)<>0 then begin case MyScan of kbHome..kbDel : { AltArrow } MyScan:=AltArrow[MyScan]; kbF1..KbF10 : { aF1-aF10 } MyScan:=MyScan+kbAltF1-kbF1; kbF11..KbF12 : { aF11-aF12 } MyScan:=MyScan+kbAltF11-kbF11; end; end else if (SState and kbShift)<>0 then begin case MyScan of kbIns: MyScan:=kbShiftIns; kbDel: MyScan:=kbShiftDel; kbF1..KbF10 : { sF1-sF10 } MyScan:=MyScan+kbShiftF1-kbF1; kbF11..KbF12 : { sF11-sF12 } MyScan:=MyScan+kbShiftF11-kbF11; end; end; GetKeyEvent:=$3000000 or ord(MyChar) or (MyScan shl 8) or (SState shl 16); exit; end else if MyChar=#27 then begin if EscUsed then SState:=SState and not kbAlt else begin SState:=SState or kbAlt; Again:=true; EscUsed:=true; end; end else if (AltPrefix<>0) and (MyChar=chr(AltPrefix)) then begin { ^Z - replace Alt for Linux OS } if AltPrefixUsed then begin SState:=SState and not kbAlt; end else begin AltPrefixUsed:=true; SState:=SState or kbAlt; Again:=true; end; end else if (CtrlPrefix<>0) and (MyChar=chr(CtrlPrefix)) then begin if CtrlPrefixUsed then SState:=SState and not kbCtrl else begin CtrlPrefixUsed:=true; SState:=SState or kbCtrl; Again:=true; end; end else if (ShiftPrefix<>0) and (MyChar=chr(ShiftPrefix)) then begin if ShiftPrefixUsed then SState:=SState and not kbShift else begin ShiftPrefixUsed:=true; SState:=SState or kbShift; Again:=true; end; end; if not again then begin MyScan:=EvalScan(ord(MyChar)); if (SState and kbAlt)<>0 then begin if MyScan in [$02..$0D] then inc(MyScan,$76); MyChar:=chr(0); end else if (SState and kbShift)<>0 then if MyChar=#9 then begin MyChar:=#0; MyScan:=kbShiftTab; end; end else begin MyChar:=Readkey(IsAlt); MyScan:=ord(MyChar); if IsAlt then SState:=SState or kbAlt; end; until not Again; GetKeyEvent:=$3000000 or ord(MyChar) or (MyScan shl 8) or (SState shl 16); end; function PollKeyEvent: TKeyEvent; begin if PendingKeyEvent<>0 then exit(PendingKeyEvent); if keypressed then begin { just get the key and place it in the pendingkeyevent } PendingKeyEvent:=GetKeyEvent; PollKeyEvent:=PendingKeyEvent; end else PollKeyEvent:=0; end; function PollShiftStateEvent: TKeyEvent; begin PollShiftStateEvent:=ShiftState shl 16; end; { Function key translation } type TTranslationEntry = packed record Min, Max: Byte; Offset: Word; end; const TranslationTableEntries = 12; TranslationTable: array [1..TranslationTableEntries] of TTranslationEntry = ((Min: $3B; Max: $44; Offset: kbdF1), { function keys F1-F10 } (Min: $54; Max: $5D; Offset: kbdF1), { Shift fn keys F1-F10 } (Min: $5E; Max: $67; Offset: kbdF1), { Ctrl fn keys F1-F10 } (Min: $68; Max: $71; Offset: kbdF1), { Alt fn keys F1-F10 } (Min: $85; Max: $86; Offset: kbdF11), { function keys F11-F12 } (Min: $87; Max: $88; Offset: kbdF11), { Shift+function keys F11-F12 } (Min: $89; Max: $8A; Offset: kbdF11), { Ctrl+function keys F11-F12 } (Min: $8B; Max: $8C; Offset: kbdF11), { Alt+function keys F11-F12 } (Min: $47; Max: $49; Offset: kbdHome), { Keypad keys kbdHome-kbdPgUp } (Min: $4B; Max: $4D; Offset: kbdLeft), { Keypad keys kbdLeft-kbdRight } (Min: $4F; Max: $51; Offset: kbdEnd), { Keypad keys kbdEnd-kbdPgDn } (Min: $52; Max: $53; Offset: kbdInsert)); function TranslateKeyEvent(KeyEvent: TKeyEvent): TKeyEvent; var I: Integer; ScanCode: Byte; begin if KeyEvent and $03000000 = $03000000 then begin if KeyEvent and $000000FF <> 0 then begin TranslateKeyEvent := KeyEvent and $00FFFFFF; exit; end else begin { This is a function key } ScanCode := (KeyEvent and $0000FF00) shr 8; for I := 1 to TranslationTableEntries do begin if (TranslationTable[I].Min <= ScanCode) and (ScanCode <= TranslationTable[I].Max) then begin TranslateKeyEvent := $02000000 + (KeyEvent and $00FF0000) + (ScanCode - TranslationTable[I].Min) + TranslationTable[I].Offset; exit; end; end; end; end; TranslateKeyEvent := KeyEvent; end; function TranslateKeyEventUniCode(KeyEvent: TKeyEvent): TKeyEvent; begin TranslateKeyEventUniCode := KeyEvent; ErrorCode:=errKbdNotImplemented; end; end. { $Log$ Revision 1.1 2001-01-13 11:03:58 peter * API 2 RTL commit }