fpc/rtl/unix/video.pp
2003-11-19 17:11:39 +00:00

934 lines
21 KiB
ObjectPascal
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
$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
Video 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 Video;
interface
{$i videoh.inc}
implementation
uses
BaseUnix, Strings, TermInfo, termio;
{$i video.inc}
Type TConsoleType = (ttyNetwork,ttyLinux,ttyFreeBSD,ttyNetBSD);
var
LastCursorType : byte;
TtyFd: Longint;
Console: TConsoleType;
{$ifdef logging}
f: file;
const
logstart: string = '';
nl: char = #10;
logend: string = #10#10;
{$endif logging}
{$ifdef cpui386}
{$ASMMODE ATT}
{$endif cpui386}
const
can_delete_term : boolean = false;
ACSIn : string = '';
ACSOut : string = '';
InACS : boolean =false;
function IsACS(var ch,ACSchar : char): boolean;
begin
IsACS:=false;
case ch of
#24, #30: {}
ch:='^';
#25, #31: {}
ch:='v';
#26, #16: {Never introduce a ctrl-Z ... }
ch:='>';
{#27,needed in Escape sequences} #17: {}
ch:='<';
#176, #177, #178: {°±²}
begin
IsACS:=true;
ACSChar:='a';
end;
#180, #181, #182, #185: {´µ¶¹}
begin
IsACS:=true;
ACSChar:='u';
end;
#183, #184, #187, #191: {·¸»¿}
begin
IsACS:=true;
ACSChar:='k';
end;
#188, #189, #190, #217: {¼½¾Ù}
begin
IsACS:=true;
ACSChar:='j';
end;
#192, #200, #211, #212: {ÀÈÓÔ}
begin
IsACS:=true;
ACSChar:='m';
end;
#193, #202, #207, #208: {ÁÊÏÐ}
begin
IsACS:=true;
ACSChar:='v';
end;
#194, #203, #209, #210: {ÂËÑÒ}
begin
IsACS:=true;
ACSChar:='w';
end;
#195, #198, #199, #204: {ÃÆÇÌ}
begin
IsACS:=true;
ACSChar:='t';
end;
#196, #205: {ÄÍ}
begin
IsACS:=true;
ACSChar:='q';
end;
#179, #186: {³º}
begin
IsACS:=true;
ACSChar:='x';
end;
#197, #206, #215, #216: {ÅÎ×Ø}
begin
IsACS:=true;
ACSChar:='n';
end;
#201, #213, #214, #218: {ÉÕÖÚ}
begin
IsACS:=true;
ACSChar:='l';
end;
#254: { þ }
begin
ch:='*';
end;
{ Shadows for Buttons }
#220: { Ü }
begin
IsACS:=true;
ACSChar:='a';
end;
#223: { ß }
begin
IsACS:=true;
ACSChar:='a';
end;
end;
end;
function SendEscapeSeqNdx(Ndx: Word) : boolean;
var
P,pdelay: PChar;
begin
SendEscapeSeqNdx:=false;
if not assigned(cur_term_Strings) then
exit{RunError(219)};
P:=cur_term_Strings^[Ndx];
if assigned(p) then
begin { Do not transmit the delays }
pdelay:=strpos(p,'$<');
if assigned(pdelay) then
pdelay^:=#0;
fpWrite(stdoutputhandle, P^, StrLen(P));
SendEscapeSeqNdx:=true;
if assigned(pdelay) then
pdelay^:='$';
end;
end;
procedure SendEscapeSeq(const S: String);
begin
fpWrite(stdoutputhandle, S[1], Length(S));
end;
Function IntStr(l:longint):string;
var
s : string;
begin
Str(l,s);
IntStr:=s;
end;
Function XY2Ansi(x,y,ox,oy:longint):String;
{
Returns a string with the escape sequences to go to X,Y on the screen
}
Begin
if y=oy then
begin
if x=ox then
begin
XY2Ansi:='';
exit;
end;
if x=1 then
begin
XY2Ansi:=#13;
exit;
end;
if x>ox then
begin
XY2Ansi:=#27'['+IntStr(x-ox)+'C';
exit;
end
else
begin
XY2Ansi:=#27'['+IntStr(ox-x)+'D';
exit;
end;
end;
if x=ox then
begin
if y>oy then
begin
XY2Ansi:=#27'['+IntStr(y-oy)+'B';
exit;
end
else
begin
XY2Ansi:=#27'['+IntStr(oy-y)+'A';
exit;
end;
end;
if ((x=1) and (oy+1=y)) and (console<>ttyfreebsd) then
XY2Ansi:=#13#10
else
XY2Ansi:=#27'['+IntStr(y)+';'+IntStr(x)+'H';
End;
const
AnsiTbl : string[8]='04261537';
Function Attr2Ansi(Attr,OAttr:longint):string;
{
Convert Attr to an Ansi String, the Optimal code is calculate
with use of the old OAttr
}
var
hstr : string[16];
OFg,OBg,Fg,Bg : longint;
procedure AddSep(ch:char);
begin
if length(hstr)>0 then
hstr:=hstr+';';
hstr:=hstr+ch;
end;
begin
if Attr=OAttr then
begin
Attr2Ansi:='';
exit;
end;
Hstr:='';
Fg:=Attr and $f;
Bg:=Attr shr 4;
OFg:=OAttr and $f;
OBg:=OAttr shr 4;
if (OFg<>7) or (Fg=7) or ((OFg>7) and (Fg<8)) or ((OBg>7) and (Bg<8)) then
begin
hstr:='0';
OFg:=7;
OBg:=0;
end;
if (Fg>7) and (OFg<8) then
begin
AddSep('1');
OFg:=OFg or 8;
end;
if (Bg and 8)<>(OBg and 8) then
begin
AddSep('5');
OBg:=OBg or 8;
end;
if (Fg<>OFg) then
begin
AddSep('3');
hstr:=hstr+AnsiTbl[(Fg and 7)+1];
end;
if (Bg<>OBg) then
begin
AddSep('4');
hstr:=hstr+AnsiTbl[(Bg and 7)+1];
end;
if hstr='0' then
hstr:='';
Attr2Ansi:=#27'['+hstr+'m';
end;
procedure UpdateTTY(Force:boolean);
type
tchattr=packed record
{$ifdef ENDIAN_LITTLE}
ch : char;
attr : byte;
{$else}
attr : byte;
ch : char;
{$endif}
end;
var
outbuf : array[0..1023+255] of char;
chattr : tchattr;
skipped : boolean;
outptr,
spaces,
eol,
x,y,
LastX,LastY,
SpaceAttr,
LastAttr : longint;
p,pold : pvideocell;
LastLineWidth : Longint;
procedure TransformUsingACS(var st : string);
var
res : string;
i : longint;
ch,ACSch : char;
begin
res:='';
for i:=1 to length(st) do
begin
ch:=st[i];
if IsACS(ch,ACSch) then
begin
if not InACS then
begin
res:=res+ACSIn;
InACS:=true;
end;
res:=res+ACSch;
end
else
begin
if InACS then
begin
res:=res+ACSOut+Attr2Ansi(LastAttr,0);
InACS:=false;
end;
res:=res+ch;
end;
end;
st:=res;
end;
procedure outdata(hstr:string);
begin
If Length(HStr)>0 Then
Begin
while (eol>0) do
begin
hstr:=#13#10+hstr;
dec(eol);
end;
if NoExtendedFrame and (ACSIn<>'') and (ACSOut<>'') then
TransformUsingACS(Hstr);
move(hstr[1],outbuf[outptr],length(hstr));
inc(outptr,length(hstr));
if outptr>=1024 then
begin
{$ifdef logging}
blockwrite(f,logstart[1],length(logstart));
blockwrite(f,nl,1);
blockwrite(f,outptr,sizeof(outptr));
blockwrite(f,nl,1);
blockwrite(f,outbuf,outptr);
blockwrite(f,nl,1);
{$endif logging}
fpWrite(stdoutputhandle,outbuf,outptr);
outptr:=0;
end;
end;
end;
procedure OutClr(c:byte);
begin
if c=LastAttr then
exit;
OutData(Attr2Ansi(c,LastAttr));
LastAttr:=c;
end;
procedure OutSpaces;
begin
if (Spaces=0) then
exit;
OutClr(SpaceAttr);
OutData(Space(Spaces));
LastX:=x;
LastY:=y;
Spaces:=0;
end;
function GetTermString(ndx:word):String;
var
P,pdelay: PChar;
begin
GetTermString:='';
if not assigned(cur_term_Strings) then
exit{RunError(219)};
P:=cur_term_Strings^[Ndx];
if assigned(p) then
begin { Do not transmit the delays }
pdelay:=strpos(p,'$<');
if assigned(pdelay) then
pdelay^:=#0;
GetTermString:=StrPas(p);
if assigned(pdelay) then
pdelay^:='$';
end;
end;
begin
OutPtr:=0;
Eol:=0;
skipped:=true;
p:=PVideoCell(VideoBuf);
pold:=PVideoCell(OldVideoBuf);
{ init Attr, X,Y and set autowrap off }
SendEscapeSeq(#27'[m'#27'[?7l'{#27'[H'} );
// 1.0.x: SendEscapeSeq(#27'[m'{#27'[H'});
LastAttr:=7;
LastX:=-1;
LastY:=-1;
for y:=1 to ScreenHeight do
begin
SpaceAttr:=0;
Spaces:=0;
LastLineWidth:=ScreenWidth;
If (y=ScreenHeight) And (Console=ttyFreeBSD) {And :am: is on} Then
LastLineWidth:=ScreenWidth-2;
for x:=1 to LastLineWidth do
begin
if (not force) and (p^=pold^) then
begin
if (Spaces>0) then
OutSpaces;
skipped:=true;
end
else
begin
if skipped then
begin
OutData(XY2Ansi(x,y,LastX,LastY));
LastX:=x;
LastY:=y;
skipped:=false;
end;
chattr:=tchattr(p^);
if chattr.ch in [#0,#255] then
chattr.ch:=' ';
if chattr.ch=' ' then
begin
if Spaces=0 then
SpaceAttr:=chattr.Attr;
if (chattr.attr and $f0)=(spaceattr and $f0) then
chattr.Attr:=SpaceAttr
else
begin
OutSpaces;
SpaceAttr:=chattr.Attr;
end;
inc(Spaces);
end
else
begin
if (Spaces>0) then
OutSpaces;
if ord(chattr.ch)<32 then
begin
Chattr.Attr:= $ff xor Chattr.Attr;
ChAttr.ch:= chr(ord(chattr.ch)+ord('A')-1);
end;
if LastAttr<>chattr.Attr then
OutClr(chattr.Attr);
OutData(chattr.ch);
LastX:=x+1;
LastY:=y;
end;
p^:=tvideocell(chattr);
end;
inc(p);
inc(pold);
end;
if (Spaces>0) then
OutSpaces;
if force then
inc(eol)
else
skipped:=true;
end;
eol:=0;
{if am in capabilities? Then}
If (Console=ttyFreeBSD) and (Plongint(p)^<>plongint(pold)^) Then
Begin
OutData(XY2Ansi(ScreenWidth,ScreenHeight,LastX,LastY));
OutData(#8);
{Output last char}
chattr:=tchattr(p[1]);
if LastAttr<>chattr.Attr then
OutClr(chattr.Attr);
OutData(chattr.ch);
inc(LastX);
// OutData(XY2Ansi(ScreenWidth-1,ScreenHeight,LastX,LastY));
// OutData(GetTermString(Insert_character));
OutData(#8+#27+'[1@');
chattr:=tchattr(p^);
if LastAttr<>chattr.Attr then
OutClr(chattr.Attr);
OutData(chattr.ch);
inc(LastX);
end;
OutData(XY2Ansi(CursorX,CursorY,LastX,LastY));
{$ifdef logging}
blockwrite(f,logstart[1],length(logstart));
blockwrite(f,nl,1);
blockwrite(f,outptr,sizeof(outptr));
blockwrite(f,nl,1);
blockwrite(f,outbuf,outptr);
blockwrite(f,nl,1);
{$endif logging}
fpWrite(stdoutputhandle,outbuf,outptr);
if InACS then
SendEscapeSeqNdx(exit_alt_charset_mode);
{turn autowrap on}
// SendEscapeSeq(#27'[?7h');
end;
var
InitialVideoTio, preInitVideoTio, postInitVideoTio: termio.termios;
inputRaw, outputRaw: boolean;
procedure saveRawSettings(const tio: termio.termios);
Begin
with tio do
begin
inputRaw :=
((c_iflag and (IGNBRK or BRKINT or PARMRK or ISTRIP or
INLCR or IGNCR or ICRNL or IXON)) = 0) and
((c_lflag and (ECHO or ECHONL or ICANON or ISIG or IEXTEN)) = 0);
outPutRaw :=
((c_oflag and OPOST) = 0) and
((c_cflag and (CSIZE or PARENB)) = 0) and
((c_cflag and CS8) <> 0);
end;
end;
procedure restoreRawSettings(tio: termio.termios);
begin
with tio do
begin
if inputRaw then
begin
c_iflag := c_iflag and (not (IGNBRK or BRKINT or PARMRK or ISTRIP or
INLCR or IGNCR or ICRNL or IXON));
c_lflag := c_lflag and
(not (ECHO or ECHONL or ICANON or ISIG or IEXTEN));
end;
if outPutRaw then
begin
c_oflag := c_oflag and not(OPOST);
c_cflag := c_cflag and not(CSIZE or PARENB) or CS8;
end;
end;
TCSetAttr(1,TCSANOW,tio);
end;
procedure TargetEntry;
begin
TCGetAttr(1,InitialVideoTio);
end;
procedure TargetExit;
begin
TCSetAttr(1,TCSANOW,InitialVideoTio);
end;
procedure prepareInitVideo;
begin
TCGetAttr(1,preInitVideoTio);
saveRawSettings(preInitVideoTio);
end;
procedure videoInitDone;
begin
TCGetAttr(1,postInitVideoTio);
restoreRawSettings(postInitVideoTio);
end;
procedure prepareDoneVideo;
var
tio: termio.termios;
begin
TCGetAttr(1,tio);
saveRawSettings(tio);
TCSetAttr(1,TCSANOW,postInitVideoTio);
end;
procedure doneVideoDone;
begin
restoreRawSettings(preInitVideoTio);
end;
procedure SysInitVideo;
const
fontstr : string[3]=#27'(K';
var
ThisTTY: String[30];
FName: String;
WS: packed record
ws_row, ws_col, ws_xpixel, ws_ypixel: Word;
end;
Err: Longint;
prev_term : TerminalCommon_ptr1;
begin
{$ifndef CPUI386}
LowAscii:=false;
{$endif CPUI386}
{ check for tty }
ThisTTY:=TTYName(stdinputhandle);
if (IsATTY(stdinputhandle)<>-1) then
begin
{ save current terminal characteristics and remove rawness }
prepareInitVideo;
{ write code to set a correct font }
fpWrite(stdoutputhandle,fontstr[1],length(fontstr));
{ running on a tty, find out whether locally or remotely }
TTyfd:=-1;
Console:=TTyNetwork; {Default: Network or other vtxxx tty}
if (Copy(ThisTTY, 1, 8) = '/dev/tty') and
not (ThisTTY[9] IN ['p'..'u','P']) then // FreeBSD has these
begin
{ running on the console }
Case ThisTTY[9] of
'0'..'9' : begin { running Linux on native console or native-emulation }
FName:='/dev/vcsa' + ThisTTY[9];
TTYFd:=fpOpen(FName, &666, O_RdWr); { open console }
IF TTYFd <>-1 Then
Console:=ttyLinux;
end;
'v' : { check for (Free?)BSD native}
If (ThisTTY[10]>='0') and (ThisTTY[10]<='9') Then
Console:=ttyFreeBSD; {TTYFd ?}
end;
end;
If (Copy(fpGetEnv('TERM'),1,4)='cons') Then // cons<lines>
Console:=ttyFreeBSD;
If Console<>ttylinux Then
begin
{ running on a remote terminal, no error with /dev/vcsa }
LowAscii:=false;
//TTYFd:=stdoutputhandle;
end;
fpioctl(stdinputhandle, TIOCGWINSZ, @WS);
if WS.ws_Col=0 then
WS.ws_Col:=80;
if WS.ws_Row=0 then
WS.ws_Row:=25;
ScreenWidth:=WS.ws_Col;
{ TDrawBuffer only has FVMaxWidth elements
larger values lead to crashes }
if ScreenWidth> FVMaxWidth then
ScreenWidth:=FVMaxWidth;
ScreenHeight:=WS.ws_Row;
CursorX:=1;
CursorY:=1;
LastCursorType:=$ff;
ScreenColor:=True;
{ Start with a clear screen }
if Console<>ttylinux then
begin
prev_term:=cur_term;
setupterm(nil, stdoutputhandle, err);
can_delete_term:=assigned(prev_term) and (prev_term<>cur_term);
SendEscapeSeqNdx(cursor_home);
SendEscapeSeqNdx(cursor_normal);
SendEscapeSeqNdx(cursor_visible);
SendEscapeSeqNdx(enter_ca_mode);
SetCursorType(crUnderLine);
If Console=ttyFreeBSD Then
SendEscapeSeqNdx(exit_am_mode);
end
else if not assigned(cur_term) then
begin
setupterm(nil, stdoutputhandle, err);
can_delete_term:=false;
end;
if assigned(cur_term_Strings) then
begin
ACSIn:=StrPas(cur_term_Strings^[enter_alt_charset_mode]);
ACSOut:=StrPas(cur_term_Strings^[exit_alt_charset_mode]);
if (ACSIn<>'') and (ACSOut<>'') then
SendEscapeSeqNdx(ena_acs);
if pos('$<',ACSIn)>0 then
ACSIn:=Copy(ACSIn,1,Pos('$<',ACSIn)-1);
if pos('$<',ACSOut)>0 then
ACSOut:=Copy(ACSOut,1,Pos('$<',ACSOut)-1);
end
else
begin
ACSIn:='';
ACSOut:='';
end;
{$ifdef logging}
assign(f,'video.log');
rewrite(f,1);
{$endif logging}
{ save new terminal characteristics and possible restore rawness }
videoInitDone;
end
else
ErrorCode:=errVioInit; { not a TTY }
end;
procedure SysDoneVideo;
begin
prepareDoneVideo;
if Console=ttylinux then
SetCursorPos(1,1)
else
begin
SendEscapeSeqNdx(exit_ca_mode);
SendEscapeSeqNdx(cursor_home);
SendEscapeSeqNdx(cursor_normal);
SendEscapeSeqNdx(cursor_visible);
SetCursorType(crUnderLine);
SendEscapeSeq(#27'[H');
end;
ACSIn:='';
ACSOut:='';
doneVideoDone;
{ FreeBSD gives an error here.
According to Pierre this could be more a NCurses version thing that
a FreeBSD one. FreeBSD 4.4 has ncurses 5.
MvdV102003: Since I ran 1.1 with newer FreeBSD without problem, I let it be for now}
if can_delete_term then
begin
del_curterm(cur_term);
can_delete_term:=false;
end;
{$ifdef logging}
close(f);
{$endif logging}
end;
procedure SysClearScreen;
begin
if Console=ttylinux then
UpdateScreen(true)
else
begin
SendEscapeSeq(#27'[0m');
SendEscapeSeqNdx(clear_screen);
end;
end;
procedure SysUpdateScreen(Force: Boolean);
var
DoUpdate : boolean;
i : longint;
p1,p2 : plongint;
begin
if not force then
begin
{$ifdef cpui386}
asm
movl VideoBuf,%esi
movl OldVideoBuf,%edi
movl VideoBufSize,%ecx
shrl $2,%ecx
repe
cmpsl
setne DoUpdate
end;
{$else not cpui386}
p1:=plongint(VideoBuf);
p2:=plongint(OldVideoBuf);
for i:=0 to VideoBufSize div 2 do
if (p1^<>p2^) then
begin
DoUpdate:=true;
break;
end
else
begin
{ Inc does add sizeof(longint) to both pointer values }
inc(p1);
inc(p2);
end;
{$endif not cpui386}
end
else
DoUpdate:=true;
if not DoUpdate then
exit;
if Console=ttylinux then
begin
fplSeek(TTYFd, 4, Seek_Set);
fpWrite(TTYFd, VideoBuf^,VideoBufSize);
end
else
begin
UpdateTTY(force);
end;
Move(VideoBuf^, OldVideoBuf^, VideoBufSize);
end;
function SysGetCapabilities: Word;
begin
{ about cpColor... we should check the terminfo database... }
SysGetCapabilities:=cpUnderLine + cpBlink + cpColor;
end;
procedure SysSetCursorPos(NewCursorX, NewCursorY: Word);
var
Pos : array [1..2] of Byte;
begin
if (CursorX=NewCursorX+1) and (CursorY=NewCursorY+1) then
exit;
if Console=ttylinux then
begin
fplSeek(TTYFd, 2, Seek_Set);
Pos[1]:=NewCursorX;
Pos[2]:=NewCursorY;
fpWrite(TTYFd, Pos, 2);
end
else
begin
{ newcursorx,y is 0 based ! }
SendEscapeSeq(XY2Ansi(NewCursorX+1,NewCursorY+1,CursorX,CursorY));
end;
CursorX:=NewCursorX+1;
CursorY:=NewCursorY+1;
end;
function SysGetCursorType: Word;
begin
SysGetCursorType:=LastCursorType;
end;
procedure SysSetCursorType(NewType: Word);
begin
If LastCursorType=NewType then
exit;
LastCursorType:=NewType;
case NewType of
crBlock :
Begin
If not SendEscapeSeqNdx(cursor_visible) then
If Console<>ttyFreeBSD Then // should be done only for linux?
SendEscapeSeq(#27'[?17;0;64c');
End;
crHidden :
Begin
If not SendEscapeSeqNdx(cursor_invisible) then
If Console<>ttyFreeBSD Then
SendEscapeSeq(#27'[?1c');
End;
else
begin
If not SendEscapeSeqNdx(cursor_normal) then
If Console<>ttyFreeBSD Then
SendEscapeSeq(#27'[?2c');
end;
end;
end;
Const
SysVideoDriver : TVideoDriver = (
InitDriver : @SysInitVideo;
DoneDriver : @SysDoneVideo;
UpdateScreen : @SysUpdateScreen;
ClearScreen : @SysClearScreen;
SetVideoMode : Nil;
GetVideoModeCount : Nil;
GetVideoModeData : Nil;
SetCursorPos : @SysSetCursorPos;
GetCursorType : @SysGetCursorType;
SetCursorType : @SysSetCursorType;
GetCapabilities : @SysGetCapabilities;
);
initialization
SetVideoDriver(SysVideoDriver);
end.
{
$Log$
Revision 1.20 2003-11-19 17:11:40 marco
* termio unit
Revision 1.19 2003/11/17 10:05:51 marco
* threads for FreeBSD. Not working tho
Revision 1.18 2003/10/26 15:32:25 marco
* partial fix for bug 2212.
Revision 1.17 2003/10/25 22:48:52 marco
* small after merge fixes
Revision 1.16 2003/10/24 17:51:39 marco
* merged some fixes from 1.0.x
Revision 1.15 2003/10/17 22:13:30 olle
* changed i386 to cpui386
Revision 1.14 2003/09/14 20:15:01 marco
* Unix reform stage two. Remove all calls from Unix that exist in Baseunix.
Revision 1.13 2003/03/26 12:45:21 armin
* added wrapoff to avoid problems in the ide with some terminal emulators
Revision 1.12 2002/09/07 16:01:28 peter
* old logs removed and tabs fixed
Revision 1.11 2002/07/06 16:50:17 marco
* Fix for corrupt color-attr after some ACS-mode changes. (Pierre, Strassbourg
meeting)
}