Fiddling with VESA cursor, cleaning up VESA and VTerm
[tpg/acess2.git] / Kernel / drv / vterm.c
1 /*
2  * Acess2 Virtual Terminal Driver
3  */
4 #define DEBUG   0
5 #include <acess.h>
6 #include <fs_devfs.h>
7 #include <modules.h>
8 #include <tpl_drv_video.h>
9 #include <tpl_drv_keyboard.h>
10 #include <tpl_drv_terminal.h>
11 #include <errno.h>
12
13 #define USE_CTRL_ALT    0
14
15 // === CONSTANTS ===
16 #define VERSION ((0<<8)|(50))
17
18 #define NUM_VTS 8
19 #define MAX_INPUT_CHARS32       64
20 #define MAX_INPUT_CHARS8        (MAX_INPUT_CHARS32*4)
21 //#define DEFAULT_OUTPUT        "BochsGA"
22 #define DEFAULT_OUTPUT  "Vesa"
23 #define DEFAULT_INPUT   "PS2Keyboard"
24 #define DEFAULT_WIDTH   640
25 #define DEFAULT_HEIGHT  480
26 #define DEFAULT_SCROLLBACK      2       // 2 Screens of text + current screen
27 #define DEFAULT_COLOUR  (VT_COL_BLACK|(0xAAA<<16))
28
29 #define VT_FLAG_HIDECSR 0x01
30 #define VT_FLAG_HASFB   0x10    //!< Set if the VTerm has requested the Framebuffer
31
32 enum eVT_InModes {
33         VT_INMODE_TEXT8,        // UTF-8 Text Mode (VT100 Emulation)
34         VT_INMODE_TEXT32,       // UTF-32 Text Mode (Acess Native)
35         NUM_VT_INMODES
36 };
37
38 // === TYPES ===
39 typedef struct {
40          int    Mode;   //!< Current Mode (see ::eTplTerminal_Modes)
41          int    Flags;  //!< Flags (see VT_FLAG_*)
42         short   Width;  //!< Virtual Width
43         short   Height; //!< Virtual Height
44         
45          int    ViewPos;        //!< View Buffer Offset (Text Only)
46          int    WritePos;       //!< Write Buffer Offset (Text Only)
47         Uint32  CurColour;      //!< Current Text Colour
48         
49          int    InputRead;      //!< Input buffer read position
50          int    InputWrite;     //!< Input buffer write position
51         char    InputBuffer[MAX_INPUT_CHARS8];
52         union {
53                 tVT_Char        *Text;
54                 Uint32          *Buffer;
55         };
56         char    Name[2];        //!< Name of the terminal
57         tVFS_Node       Node;
58 } tVTerm;
59
60 // === IMPORTS ===
61 extern void     Debug_SetKTerminal(char *File);
62
63 // === PROTOTYPES ===
64  int    VT_Install(char **Arguments);
65 void    VT_InitOutput(void);
66 void    VT_InitInput(void);
67 char    *VT_ReadDir(tVFS_Node *Node, int Pos);
68 tVFS_Node       *VT_FindDir(tVFS_Node *Node, char *Name);
69  int    VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data);
70 Uint64  VT_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
71 Uint64  VT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer);
72  int    VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data);
73 void    VT_SetResolution(int Width, int Height);
74 void    VT_SetMode(int Mode);
75 void    VT_SetTerminal(int ID);
76 void    VT_KBCallBack(Uint32 Codepoint);
77 void    VT_int_PutString(tVTerm *Term, Uint8 *Buffer, Uint Count);
78  int    VT_int_ParseEscape(tVTerm *Term, char *Buffer);
79 void    VT_int_PutChar(tVTerm *Term, Uint32 Ch);
80 void    VT_int_ScrollFramebuffer( tVTerm *Term );
81 void    VT_int_UpdateScreen( tVTerm *Term, int UpdateAll );
82 void    VT_int_ChangeMode(tVTerm *Term, int NewMode);
83
84 // === CONSTANTS ===
85 const Uint16    caVT100Colours[] = {
86                 // Black, Red, Green, Yellow, Blue, Purple, Cyan, Gray
87                 // Same again, but bright
88                 VT_COL_BLACK, 0x700, 0x070, 0x770, 0x007, 0x707, 0x077, 0xAAA,
89                 VT_COL_GREY, 0xF00, 0x0F0, 0xFF0, 0x00F, 0xF0F, 0x0FF, VT_COL_WHITE
90         };
91
92 // === GLOBALS ===
93 MODULE_DEFINE(0, VERSION, VTerm, VT_Install, NULL, DEFAULT_OUTPUT, DEFAULT_INPUT, NULL);
94 tDevFS_Driver   gVT_DrvInfo = {
95         NULL, "VTerm",
96         {
97         .Flags = VFS_FFLAG_DIRECTORY,
98         .Size = NUM_VTS,
99         .Inode = -1,
100         .NumACLs = 0,
101         .ReadDir = VT_ReadDir,
102         .FindDir = VT_FindDir,
103         .IOCtl = VT_Root_IOCtl
104         }
105 };
106 // --- Terminals ---
107 tVTerm  gVT_Terminals[NUM_VTS];
108  int    giVT_CurrentTerminal = 0;
109 tVTerm  *gpVT_CurTerm = &gVT_Terminals[0];
110 // --- Video State ---
111 short   giVT_RealWidth  = DEFAULT_WIDTH;        //!< Screen Width
112 short   giVT_RealHeight = DEFAULT_HEIGHT;       //!< Screen Height
113  int    giVT_Scrollback = DEFAULT_SCROLLBACK;
114 // --- Driver Handles ---
115 char    *gsVT_OutputDevice = NULL;
116 char    *gsVT_InputDevice = NULL;
117  int    giVT_OutputDevHandle = -2;
118  int    giVT_InputDevHandle = -2;
119 // --- Key States --- (Used for VT Switching/Magic Combos)
120  int    gbVT_CtrlDown = 0;
121  int    gbVT_AltDown = 0;
122  int    gbVT_SysrqDown = 0;
123
124 // === CODE ===
125 /**
126  * \fn int VT_Install(char **Arguments)
127  * \brief Installs the Virtual Terminal Driver
128  */
129 int VT_Install(char **Arguments)
130 {
131          int    i;
132         
133         // Scan Arguments
134         if(Arguments)
135         {
136                 char    **args = Arguments;
137                 char    *arg, *opt, *val;
138                 for( ; (arg = *args); args++ )
139                 {
140                         Log_Debug("VTerm", "Argument '%s'", arg);
141                         opt = arg;
142                         val = arg + strpos(arg, '=');   *val++ = '\0';
143                         
144                         if( strcmp(opt, "Video") == 0 ) {
145                                 if(gsVT_OutputDevice)   free(gsVT_OutputDevice);
146                                 gsVT_OutputDevice = strdup(val);
147                         }
148                         else if( strcmp(opt, "Input") == 0 ) {
149                                 if(gsVT_InputDevice)    free(gsVT_InputDevice);
150                                 gsVT_InputDevice = strdup(val);
151                         }
152                         else if( strcmp(opt, "Width") == 0 ) {
153                                 giVT_RealWidth = atoi( val );
154                         }
155                         else if( strcmp(opt, "Height") == 0 ) {
156                                 giVT_RealHeight = atoi( val );
157                         }
158                         else if( strcmp(opt, "Scrollback") == 0 ) {
159                                 giVT_Scrollback = atoi( val );
160                         }
161                 }
162         }
163         
164         // Apply Defaults
165         if(!gsVT_OutputDevice)  gsVT_OutputDevice = "/Devices/"DEFAULT_OUTPUT;
166         if(!gsVT_InputDevice)   gsVT_InputDevice = "/Devices/"DEFAULT_INPUT;
167         
168         Log_Log("VTerm", "Using '%s' as output", gsVT_OutputDevice);
169         Log_Log("VTerm", "Using '%s' as input", gsVT_InputDevice);
170         
171         // Create Nodes
172         for( i = 0; i < NUM_VTS; i++ )
173         {
174                 gVT_Terminals[i].Mode = TERM_MODE_TEXT;
175                 gVT_Terminals[i].Flags = 0;
176                 gVT_Terminals[i].Width = giVT_RealWidth/giVT_CharWidth;
177                 gVT_Terminals[i].Height = giVT_RealHeight/giVT_CharHeight;
178                 gVT_Terminals[i].CurColour = DEFAULT_COLOUR;
179                 gVT_Terminals[i].WritePos = 0;
180                 gVT_Terminals[i].ViewPos = 0;
181                 
182                 gVT_Terminals[i].Buffer = calloc(
183                         gVT_Terminals[i].Width*gVT_Terminals[i].Height*(giVT_Scrollback+1),
184                         sizeof(tVT_Char)
185                         );
186                 
187                 gVT_Terminals[i].Name[0] = '0'+i;
188                 gVT_Terminals[i].Name[1] = '\0';
189                 gVT_Terminals[i].Node.Inode = i;
190                 gVT_Terminals[i].Node.ImplPtr = &gVT_Terminals[i];
191                 gVT_Terminals[i].Node.NumACLs = 0;      // Only root can open virtual terminals
192                 
193                 gVT_Terminals[i].Node.Read = VT_Read;
194                 gVT_Terminals[i].Node.Write = VT_Write;
195                 gVT_Terminals[i].Node.IOCtl = VT_Terminal_IOCtl;
196         }
197         
198         // Add to DevFS
199         DevFS_AddDevice( &gVT_DrvInfo );
200         
201         VT_InitOutput();
202         VT_InitInput();
203         
204         // Set kernel output to VT0
205         Debug_SetKTerminal("/Devices/VTerm/0");
206         
207         Log_Log("VTerm", "Returning %i", MODULE_ERR_OK);
208         return MODULE_ERR_OK;
209 }
210
211 /**
212  * \fn void VT_InitOutput()
213  * \brief Initialise Video Output
214  */
215 void VT_InitOutput()
216 {
217         giVT_OutputDevHandle = VFS_Open(gsVT_OutputDevice, VFS_OPENFLAG_WRITE);
218         if(giVT_InputDevHandle == -1) {
219                 Log_Warning("VTerm", "Oh F**k, I can't open the video device '%s'", gsVT_OutputDevice);
220                 return ;
221         }
222         VT_SetResolution(giVT_RealWidth, giVT_RealHeight);
223         VT_SetTerminal( 0 );
224         VT_SetMode( VIDEO_BUFFMT_TEXT );
225 }
226
227 /**
228  * \fn void VT_InitInput()
229  * \brief Initialises the input
230  */
231 void VT_InitInput()
232 {
233         giVT_InputDevHandle = VFS_Open(gsVT_InputDevice, VFS_OPENFLAG_READ);
234         if(giVT_InputDevHandle == -1)   return ;
235         VFS_IOCtl(giVT_InputDevHandle, KB_IOCTL_SETCALLBACK, VT_KBCallBack);
236 }
237
238 /**
239  * \fn char *VT_ReadDir(tVFS_Node *Node, int Pos)
240  * \brief Read from the VTerm Directory
241  */
242 char *VT_ReadDir(tVFS_Node *Node, int Pos)
243 {
244         if(Pos < 0)     return NULL;
245         if(Pos >= NUM_VTS)      return NULL;
246         return strdup( gVT_Terminals[Pos].Name );
247 }
248
249 /**
250  * \fn tVFS_Node *VT_FindDir(tVFS_Node *Node, char *Name)
251  * \brief Find an item in the VTerm directory
252  */
253 tVFS_Node *VT_FindDir(tVFS_Node *Node, char *Name)
254 {
255          int    num;
256         
257         ENTER("pNode sName", Node, Name);
258         
259         // Open the input and output files if needed
260         if(giVT_OutputDevHandle == -2)  VT_InitOutput();
261         if(giVT_InputDevHandle == -2)   VT_InitInput();
262         
263         // Sanity check name
264         if(Name[0] < '0' || Name[0] > '9' || Name[1] != '\0') {
265                 LEAVE('n');
266                 return NULL;
267         }
268         // Get index
269         num = Name[0] - '0';
270         if(num >= NUM_VTS) {
271                 LEAVE('n');
272                 return NULL;
273         }
274         // Return node
275         LEAVE('p', &gVT_Terminals[num].Node);
276         return &gVT_Terminals[num].Node;
277 }
278
279 /**
280  * \fn int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
281  * \brief Control the VTerm Driver
282  */
283 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
284 {
285          int    len;
286         switch(Id)
287         {
288         case DRV_IOCTL_TYPE:    return DRV_TYPE_MISC;
289         case DRV_IOCTL_IDENT:   memcpy(Data, "VT\0\0", 4);      return 0;
290         case DRV_IOCTL_VERSION: return VERSION;
291         case DRV_IOCTL_LOOKUP:  return 0;
292         
293         case 4: // Get Video Driver
294                 if(Data)        strcpy(Data, gsVT_OutputDevice);
295                 return strlen(gsVT_OutputDevice);
296         
297         case 5: // Set Video Driver
298                 if(!Data)       return -EINVAL;
299                 if(Threads_GetUID() != 0)       return -EACCES;
300                 
301                 len = strlen(Data);
302                 
303                 free(gsVT_OutputDevice);
304                 
305                 gsVT_OutputDevice = malloc(len+1);
306                 strcpy(gsVT_OutputDevice, Data);
307                 
308                 VFS_Close(giVT_OutputDevHandle);
309                 giVT_OutputDevHandle = -1;
310                 
311                 VT_InitOutput();
312                 return 1;
313         }
314         return 0;
315 }
316
317 /**
318  * \fn Uint64 VT_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
319  * \brief Read from a virtual terminal
320  */
321 Uint64 VT_Read(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
322 {
323          int    pos = 0;
324         tVTerm  *term = &gVT_Terminals[ Node->Inode ];
325         
326         // Check current mode
327         switch(term->Mode)
328         {
329         case TERM_MODE_TEXT:
330                 while(pos < Length)
331                 {
332                         while(term->InputRead == term->InputWrite)      Threads_Yield();
333                         
334                         ((char*)Buffer)[pos] = term->InputBuffer[term->InputRead];
335                         pos ++;
336                         term->InputRead ++;
337                         term->InputRead %= MAX_INPUT_CHARS8;
338                 }
339                 break;
340         
341         //case TERM_MODE_FB:
342         default:
343                 while(pos < Length)
344                 {
345                         while(term->InputRead == term->InputWrite)      Threads_Yield();
346                         ((Uint32*)Buffer)[pos] = ((Uint32*)term->InputBuffer)[term->InputRead];
347                         pos ++;
348                         term->InputRead ++;
349                         term->InputRead %= MAX_INPUT_CHARS32;
350                 }
351                 break;
352         }
353         return 0;
354 }
355
356 /**
357  * \fn Uint64 VT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
358  * \brief Write to a virtual terminal
359  */
360 Uint64 VT_Write(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
361 {
362         tVTerm  *term = &gVT_Terminals[ Node->Inode ];
363          int    size;
364         
365         // Write
366         switch( term->Mode )
367         {
368         case TERM_MODE_TEXT:
369                 VT_int_PutString(term, Buffer, Length);
370                 break;
371         case TERM_MODE_FB:
372                 size = term->Width*term->Height*4;
373                 if( Offset > size ) {
374                         Log_Notice("VTerm", "VT_Write: Offset (0x%llx) > FBSize (0x%x)",
375                                 Offset, size);
376                         return 0;
377                 }
378                 
379                 if( Offset + Length > size ) {
380                         Log_Notice("VTerm", "VT_Write: Offset+Length (0x%llx) > FBSize (0x%x)",
381                                 Offset+Length, size);
382                         Length = size - Offset;
383                 }
384                 
385                 memcpy( (void*)((Uint)term->Buffer + (Uint)Offset), Buffer, Length );
386                 
387                 if( Node->Inode == giVT_CurrentTerminal )
388                 {
389                         if( giVT_RealWidth > term->Width || giVT_RealHeight > term->Height )
390                         {
391                                  int    x, y, w, h;
392                                 x = Offset/4;   y = x / term->Width;    x %= term->Width;
393                                 w = Length/4+x; h = w / term->Width;    w %= term->Width;
394                                 // Center
395                                 x += (giVT_RealWidth - term->Width) / 2;
396                                 y += (giVT_RealHeight - term->Height) / 2;
397                                 while(h--)
398                                 {
399                                         VFS_WriteAt( giVT_OutputDevHandle,
400                                                 (x + y * giVT_RealWidth)*4,
401                                                 term->Width * 4,
402                                                 Buffer
403                                                 );
404                                         Buffer = (void*)( (Uint)Buffer + term->Width*4 );
405                                         y ++;
406                                 }
407                                 return 0;
408                         }
409                         else {
410                                 return VFS_WriteAt( giVT_OutputDevHandle, Offset, Length, Buffer );
411                         }
412                 }
413         
414         case TERM_MODE_2DACCEL:
415         //case TERM_MODE_3DACCEL:
416                 if( Node->Inode == giVT_CurrentTerminal )
417                 {
418                         VFS_Write( giVT_OutputDevHandle, Length, Buffer );
419                 }
420                 break;
421         }
422         
423         return 0;
424 }
425
426 /**
427  * \fn int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
428  * \brief Call an IO Control on a virtual terminal
429  */
430 int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
431 {
432          int    *iData = Data;
433         tVTerm  *term = Node->ImplPtr;
434         ENTER("pNode iId pData", Node, Id, Data);
435         
436         if(Id >= DRV_IOCTL_LOOKUP) {
437                 // Only root can fiddle with graphics modes
438                 // TODO: Remove this and replace with user ownership
439                 if( Threads_GetUID() != 0 )     return -1;
440         }
441         
442         switch(Id)
443         {
444         // --- Core Defined
445         case DRV_IOCTL_TYPE:
446                 LEAVE('i', DRV_TYPE_TERMINAL);
447                 return DRV_TYPE_TERMINAL;
448         case DRV_IOCTL_IDENT:
449                 memcpy(Data, "VT\0\0", 4);
450                 LEAVE('i', 0);
451                 return 0;
452         case DRV_IOCTL_VERSION:
453                 LEAVE('x', VERSION);
454                 return VERSION;
455         case DRV_IOCTL_LOOKUP:
456                 LEAVE('i', 0);
457                 return 0;
458         
459         // Get/Set the mode (and apply any changes)
460         case TERM_IOCTL_MODETYPE:
461                 if(Data != NULL)
462                 {
463                         if( CheckMem(Data, sizeof(int)) == 0 ) {
464                                 LEAVE('i', -1);
465                                 return -1;
466                         }
467                         Log_Log("VTerm", "VTerm %i mode set to %i", (int)Node->Inode, *iData);
468                         
469                         // Update mode if needed
470                         if(term->Mode != *iData)
471                                 VT_int_ChangeMode(term, *iData);
472                         
473                         // Update the screen dimensions
474                         if(Node->Inode == giVT_CurrentTerminal)
475                                 VT_SetTerminal( giVT_CurrentTerminal );
476                 }
477                 LEAVE('i', term->Mode);
478                 return term->Mode;
479         
480         // Get/set the terminal width
481         case TERM_IOCTL_WIDTH:
482                 if(Data != NULL) {
483                         if( CheckMem(Data, sizeof(int)) == 0 ) {
484                                 LEAVE('i', -1);
485                                 return -1;
486                         }
487                         term->Width = *iData;
488                 }
489                 Log("VT_Terminal_IOCtl - RETURN term->Width = %i", term->Width);
490                 LEAVE('i', term->Width);
491                 return term->Width;
492         
493         // Get/set the terminal height
494         case TERM_IOCTL_HEIGHT:
495                 if(Data != NULL) {
496                         if( CheckMem(Data, sizeof(int)) == 0 ) {
497                                 LEAVE('i', -1);
498                                 return -1;
499                         }
500                         term->Height = *iData;
501                 }
502                 Log("VT_Terminal_IOCtl - RETURN term->Height = %i", term->Height);
503                 LEAVE('i', term->Height);
504                 return term->Height;
505         
506         case TERM_IOCTL_FORCESHOW:
507                 Log_Log("VTerm", "Thread %i forced VTerm %i to be shown",
508                         Threads_GetTID(), (int)Node->Inode);
509                 VT_SetTerminal( Node->Inode );
510                 LEAVE('i', 1);
511                 return 1;
512         }
513         LEAVE('i', -1);
514         return -1;
515 }
516
517 void VT_SetResolution(int Width, int Height)
518 {
519         tVideo_IOCtl_Mode       mode = {0};
520          int    tmp;
521          int    i;
522         
523         // Create the video mode
524         mode.width = Width;
525         mode.height = Height;
526         mode.bpp = 32;
527         mode.flags = 0;
528         
529         // Set video mode
530         VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_FINDMODE, &mode );
531         tmp = mode.id;
532         if( Width != mode.width || Height != mode.height )
533         {
534                 Log_Warning("VTerm",
535                         "Selected resolution (%ix%i is not supported) by the device, using (%ix%i)",
536                         giVT_RealWidth, giVT_RealHeight,
537                         mode.width, mode.height
538                         );
539         }
540         VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_GETSETMODE, &tmp );
541         
542         // Resize text terminals if needed
543         if( giVT_RealWidth != mode.width || giVT_RealHeight != mode.height )
544         {
545                  int    newBufSize = (giVT_RealWidth/giVT_CharWidth)
546                                         *(giVT_RealHeight/giVT_CharHeight)
547                                         *(giVT_Scrollback+1);
548                 //tVT_Char      *tmp;
549                 // Resize the text terminals
550                 giVT_RealWidth = mode.width;
551                 giVT_RealHeight = mode.height;
552                 for( i = 0; i < NUM_VTS; i ++ )
553                 {
554                         if( gVT_Terminals[i].Mode != TERM_MODE_TEXT )   continue;
555                         
556                         gVT_Terminals[i].Text = realloc(
557                                 gVT_Terminals[i].Text,
558                                 newBufSize*sizeof(tVT_Char)
559                                 );
560                 }
561         }
562 }
563
564 void VT_SetMode(int Mode)
565 {
566         VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_SETBUFFORMAT, &Mode );
567 }
568
569 /**
570  * \fn void VT_SetTerminal(int ID)
571  * \brief Set the current terminal
572  */
573 void VT_SetTerminal(int ID)
574 {       
575         // Update current terminal ID
576         Log_Log("VTerm", "Changed terminal from %i to %i", giVT_CurrentTerminal, ID);
577         giVT_CurrentTerminal = ID;
578         gpVT_CurTerm = &gVT_Terminals[ID];
579         
580         // Update cursor
581         if( gpVT_CurTerm->Mode == TERM_MODE_TEXT && !(gpVT_CurTerm->Flags & VT_FLAG_HIDECSR) )
582         {
583                 tVideo_IOCtl_Pos        pos;
584                 pos.x = gpVT_CurTerm->WritePos % gpVT_CurTerm->Width;
585                 pos.y = gpVT_CurTerm->WritePos / gpVT_CurTerm->Width;
586                 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSOR, &pos);
587         }
588         
589         if( gpVT_CurTerm->Mode == TERM_MODE_TEXT )
590                 VT_SetMode( VIDEO_BUFFMT_TEXT );
591         else
592                 VT_SetMode( VIDEO_BUFFMT_FRAMEBUFFER );
593         
594         // Update the screen
595         VT_int_UpdateScreen( &gVT_Terminals[ ID ], 1 );
596 }
597
598 /**
599  * \fn void VT_KBCallBack(Uint32 Codepoint)
600  * \brief Called on keyboard interrupt
601  * \param Codepoint     Pseudo-UTF32 character
602  * 
603  * Handles a key press and sends the key code to the user's buffer.
604  * If the code creates a kernel-magic sequence, it is not passed to the
605  * user and is handled in-kernel.
606  */
607 void VT_KBCallBack(Uint32 Codepoint)
608 {
609         tVTerm  *term = &gVT_Terminals[giVT_CurrentTerminal];
610         
611         // How the hell did we get a Codepoint of zero?
612         if(Codepoint == 0)      return;
613         
614         // Key Up
615         if( Codepoint & 0x80000000 )
616         {
617                 Codepoint &= 0x7FFFFFFF;
618                 switch(Codepoint)
619                 {
620                 #if !USE_CTRL_ALT
621                 case KEY_RSHIFT:        gbVT_CtrlDown = 0;      break;
622                 case KEY_LSHIFT:        gbVT_AltDown = 0;       break;
623                 #else
624                 case KEY_LALT:
625                 case KEY_RALT:
626                         gbVT_AltDown = 0;
627                         break;
628                 case KEY_LCTRL:
629                 case KEY_RCTRL:
630                         gbVT_CtrlDown = 0;
631                         break;
632                 #endif
633                 }
634                 return;
635         }
636         
637         switch(Codepoint)
638         {
639         #if !USE_CTRL_ALT
640         case KEY_RSHIFT:        gbVT_CtrlDown = 1;      break;
641         case KEY_LSHIFT:        gbVT_AltDown = 1;       break;
642         #else
643         case KEY_LALT:
644         case KEY_RALT:
645                 gbVT_AltDown = 1;
646                 break;
647         case KEY_LCTRL:
648         case KEY_RCTRL:
649                 gbVT_CtrlDown = 1;
650                 break;
651         #endif
652         
653         default:
654                 #if USE_CTRL_ALT
655                 if(!gbVT_AltDown || !gbVT_CtrlDown)
656                         break;
657                 #endif
658                 switch(Codepoint)
659                 {
660                 case KEY_F1:    VT_SetTerminal(0);      return;
661                 case KEY_F2:    VT_SetTerminal(1);      return;
662                 case KEY_F3:    VT_SetTerminal(2);      return;
663                 case KEY_F4:    VT_SetTerminal(3);      return;
664                 case KEY_F5:    VT_SetTerminal(4);      return;
665                 case KEY_F6:    VT_SetTerminal(5);      return;
666                 case KEY_F7:    VT_SetTerminal(6);      return;
667                 case KEY_F8:    VT_SetTerminal(7);      return;
668                 case KEY_F9:    VT_SetTerminal(8);      return;
669                 case KEY_F10:   VT_SetTerminal(9);      return;
670                 case KEY_F11:   VT_SetTerminal(10);     return;
671                 case KEY_F12:   VT_SetTerminal(11);     return;
672                 case KEY_PGUP:
673                         if( gpVT_CurTerm->ViewPos > gpVT_CurTerm->Width )
674                                 gpVT_CurTerm->ViewPos -= gpVT_CurTerm->Width;
675                         else
676                                 gpVT_CurTerm->ViewPos = 0;
677                         return;
678                 case KEY_PGDOWN:
679                         if( gpVT_CurTerm->ViewPos < gpVT_CurTerm->Width*gpVT_CurTerm->Height*(giVT_Scrollback-1) )
680                                 gpVT_CurTerm->ViewPos += gpVT_CurTerm->Width;
681                         else
682                                 gpVT_CurTerm->ViewPos = gpVT_CurTerm->Width*gpVT_CurTerm->Height*(giVT_Scrollback-1);
683                         return;
684                 }
685         }
686         
687         // Encode key
688         if(term->Mode == TERM_MODE_TEXT)
689         {
690                 Uint8   buf[6] = {0};
691                  int    len = 0;
692                 
693                 // Ignore Modifer Keys
694                 if(Codepoint > KEY_MODIFIERS)   return;
695                 
696                 // Get UTF-8/ANSI Encoding
697                 switch(Codepoint)
698                 {
699                 case KEY_LEFT:
700                         buf[0] = '\x1B';        buf[1] = '[';   buf[2] = 'D';
701                         len = 3;
702                         break;
703                 case KEY_RIGHT:
704                         buf[0] = '\x1B';        buf[1] = '[';   buf[2] = 'C';
705                         len = 3;
706                         break;
707                 case KEY_UP:
708                         buf[0] = '\x1B';        buf[1] = '[';   buf[2] = 'A';
709                         len = 3;
710                         break;
711                 case KEY_DOWN:
712                         buf[0] = '\x1B';        buf[1] = '[';   buf[2] = 'B';
713                         len = 3;
714                         break;
715                 
716                 case KEY_PGUP:
717                         buf[0] = '\x1B';        buf[1] = '[';   buf[2] = '5';   // Some overline also
718                         //len = 4;      // Commented out until I'm sure
719                         break;
720                 case KEY_PGDOWN:
721                         len = 0;
722                         break;
723                 
724                 // Attempt to encode in UTF-8
725                 default:
726                         len = WriteUTF8( buf, Codepoint );
727                         if(len == 0) {
728                                 Warning("Codepoint (%x) is unrepresentable in UTF-8", Codepoint);
729                         }
730                         break;
731                 }
732                 
733                 if(len == 0) {
734                         // Unprintable / Don't Pass
735                         return;
736                 }
737                 
738                 // Write
739                 if( MAX_INPUT_CHARS8 - term->InputWrite >= len )
740                         memcpy( &term->InputBuffer[term->InputWrite], buf, len );
741                 else {
742                         memcpy( &term->InputBuffer[term->InputWrite], buf, MAX_INPUT_CHARS8 - term->InputWrite );
743                         memcpy( &term->InputBuffer[0], buf, len - (MAX_INPUT_CHARS8 - term->InputWrite) );
744                 }
745                 // Roll the buffer over
746                 term->InputWrite += len;
747                 term->InputWrite %= MAX_INPUT_CHARS8;
748                 if( (term->InputWrite - term->InputRead + MAX_INPUT_CHARS8)%MAX_INPUT_CHARS8 < len ) {
749                         term->InputRead = term->InputWrite + 1;
750                         term->InputRead %= MAX_INPUT_CHARS8;
751                 }
752                 
753         }
754         else
755         {
756                 // Encode the raw UTF-32 Key
757                 ((Uint32*)term->InputBuffer)[ term->InputWrite ] = Codepoint;
758                 term->InputWrite ++;
759                 term->InputWrite %= MAX_INPUT_CHARS32;
760                 if(term->InputRead == term->InputWrite) {
761                         term->InputRead ++;
762                         term->InputRead %= MAX_INPUT_CHARS32;
763                 }
764         }
765 }
766
767 /**
768  * \fn void VT_int_ClearLine(tVTerm *Term, int Num)
769  * \brief Clears a line in a virtual terminal
770  */
771 void VT_int_ClearLine(tVTerm *Term, int Num)
772 {
773          int    i;
774         //ENTER("pTerm iNum", Term, Num);
775         for( i = Term->Width; i--; )
776         {
777                 Term->Text[ Num*Term->Width + i ].Ch = 0;
778                 Term->Text[ Num*Term->Width + i ].Colour = Term->CurColour;
779         }
780         //LEAVE('-');
781 }
782
783 /**
784  * \fn int VT_int_ParseEscape(tVTerm *Term, char *Buffer)
785  * \brief Parses a VT100 Escape code
786  */
787 int VT_int_ParseEscape(tVTerm *Term, char *Buffer)
788 {
789         char    c;
790          int    argc = 0, j = 1;
791          int    tmp;
792          int    args[6] = {0,0,0,0};
793         
794         switch(Buffer[0])
795         {
796         //Large Code
797         case '[':
798                 // Get Arguments
799                 c = Buffer[j++];
800                 if( '0' <= c && c <= '9' )
801                 {
802                         do {
803                                 while('0' <= c && c <= '9') {
804                                         args[argc] *= 10;
805                                         args[argc] += c-'0';
806                                         c = Buffer[j++];
807                                 }
808                                 argc ++;
809                         } while(c == ';');
810                 }
811                 
812                 /*
813                 // Get string (what does this do?)
814                 if(c == '"') {
815                         c = Buffer[j++];
816                         while(c != '"')
817                                 c = Buffer[j++];
818                 }
819                 */
820                 
821                 //Log_Debug("VTerm", "argc = %i", argc);
822                 
823                 // Get Command
824                 if(     ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))
825                 {
826                         switch(c)
827                         {
828                         // Left
829                         case 'D':
830                                 if(argc == 1)   tmp = args[0];
831                                 else    tmp = 1;
832                                 
833                                 if( Term->WritePos-(tmp-1) % Term->Width == 0 )
834                                         Term->WritePos -= Term->WritePos % Term->Width;
835                                 else
836                                         Term->WritePos -= tmp;
837                                 break;
838                         
839                         // Right
840                         case 'C':
841                                 if(argc == 1)   tmp = args[0];
842                                 else    tmp = 1;
843                                 if( (Term->WritePos + tmp) % Term->Width == 0 ) {
844                                         Term->WritePos -= Term->WritePos % Term->Width;
845                                         Term->WritePos += Term->Width - 1;
846                                 } else
847                                         Term->WritePos += tmp;
848                                 break;
849                         
850                         // Clear By Line
851                         case 'J':
852                                 // Clear Screen
853                                 switch(args[0])
854                                 {
855                                 case 2:
856                                         {
857                                          int    i = Term->Height * (giVT_Scrollback + 1);
858                                         while( i-- )    VT_int_ClearLine(Term, i);
859                                         Term->WritePos = 0;
860                                         Term->ViewPos = 0;
861                                         VT_int_UpdateScreen(Term, 1);
862                                         }
863                                         break;
864                                 }
865                                 break;
866                         // Set Font flags
867                         case 'm':
868                                 for( ; argc--; )
869                                 {
870                                         // Flags
871                                         if( 0 <= args[argc] && args[argc] <= 8)
872                                         {
873                                                 switch(args[argc])
874                                                 {
875                                                 case 0: Term->CurColour = DEFAULT_COLOUR;       break;  // Reset
876                                                 case 1: Term->CurColour |= 0x80000000;  break;  // Bright
877                                                 case 2: Term->CurColour &= ~0x80000000; break;  // Dim
878                                                 }
879                                         }
880                                         // Foreground Colour
881                                         else if(30 <= args[argc] && args[argc] <= 37) {
882                                                 Term->CurColour &= 0xF000FFFF;
883                                                 Term->CurColour |= (Uint32)caVT100Colours[ args[argc]-30+(Term->CurColour>>28) ] << 16;
884                                         }
885                                         // Background Colour
886                                         else if(40 <= args[argc] && args[argc] <= 47) {
887                                                 Term->CurColour &= 0xFFFF8000;
888                                                 Term->CurColour |= caVT100Colours[ args[argc]-40+((Term->CurColour>>12)&15) ];
889                                         }
890                                 }
891                                 break;
892                         default:
893                                 Log_Warning("VTerm", "Unknown control sequence");
894                                 break;
895                         }
896                 }
897                 break;
898                 
899         default:        break;
900         }
901         
902         //Log_Debug("VTerm", "j = %i, Buffer = '%s'", j, Buffer);
903         return j;
904 }
905
906 /**
907  * \fn void VT_int_PutString(tVTerm *Term, Uint8 *Buffer, Uint Count)
908  * \brief Print a string to the Virtual Terminal
909  */
910 void VT_int_PutString(tVTerm *Term, Uint8 *Buffer, Uint Count)
911 {
912         Uint32  val;
913          int    i;
914         
915         // Iterate
916         for( i = 0; i < Count; i++ )
917         {
918                 // Handle escape sequences
919                 if( Buffer[i] == 0x1B )
920                 {
921                         i ++;
922                         i += VT_int_ParseEscape(Term, (char*)&Buffer[i]) - 1;
923                         continue;
924                 }
925                 
926                 // Fast check for non UTF-8
927                 if( Buffer[i] < 128 )   // Plain ASCII
928                         VT_int_PutChar(Term, Buffer[i]);
929                 else {  // UTF-8
930                         i += ReadUTF8(&Buffer[i], &val) - 1;
931                         VT_int_PutChar(Term, val);
932                 }
933         }
934         // Update Screen
935         VT_int_UpdateScreen( Term, 0 );
936         
937         // Update cursor
938         if( Term == gpVT_CurTerm && !(Term->Flags & VT_FLAG_HIDECSR) )
939         {
940                 tVideo_IOCtl_Pos        pos;
941                 pos.x = Term->WritePos % Term->Width;
942                 pos.y = Term->WritePos / Term->Width;
943                 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSOR, &pos);
944         }
945 }
946
947 /**
948  * \fn void VT_int_PutChar(tVTerm *Term, Uint32 Ch)
949  * \brief Write a single character to a VTerm
950  */
951 void VT_int_PutChar(tVTerm *Term, Uint32 Ch)
952 {
953          int    i;
954                 
955         switch(Ch)
956         {
957         case '\0':      return; // Ignore NULL byte
958         case '\n':
959                 VT_int_UpdateScreen( Term, 0 ); // Update the line before newlining
960                 Term->WritePos += Term->Width;
961         case '\r':
962                 Term->WritePos -= Term->WritePos % Term->Width;
963                 break;
964         
965         case '\t':
966                 do {
967                         Term->Text[ Term->WritePos ].Ch = '\0';
968                         Term->Text[ Term->WritePos ].Colour = Term->CurColour;
969                         Term->WritePos ++;
970                 } while(Term->WritePos & 7);
971                 break;
972         
973         case '\b':
974                 // Backspace is invalid at Offset 0
975                 if(Term->WritePos == 0) break;
976                 
977                 Term->WritePos --;
978                 // Singe Character
979                 if(Term->Text[ Term->WritePos ].Ch != '\0') {
980                         Term->Text[ Term->WritePos ].Ch = 0;
981                         Term->Text[ Term->WritePos ].Colour = Term->CurColour;
982                         break;
983                 }
984                 // Tab
985                 i = 7;  // Limit it to 8
986                 do {
987                         Term->Text[ Term->WritePos ].Ch = 0;
988                         Term->Text[ Term->WritePos ].Colour = Term->CurColour;
989                         Term->WritePos --;
990                 } while(Term->WritePos && i-- && Term->Text[ Term->WritePos ].Ch == '\0');
991                 if(Term->Text[ Term->WritePos ].Ch != '\0')
992                         Term->WritePos ++;
993                 break;
994         
995         default:
996                 Term->Text[ Term->WritePos ].Ch = Ch;
997                 Term->Text[ Term->WritePos ].Colour = Term->CurColour;
998                 Term->WritePos ++;
999                 break;
1000         }
1001         
1002         // Move Screen
1003         // - Check if we need to scroll the entire scrollback buffer
1004         if(Term->WritePos >= Term->Width*Term->Height*(giVT_Scrollback+1))
1005         {
1006                  int    base, i;
1007                 
1008                 //Debug("Scrolling entire buffer");
1009                 
1010                 // Move back by one
1011                 Term->WritePos -= Term->Width;
1012                 // Update the scren
1013                 VT_int_UpdateScreen( Term, 0 );
1014                 
1015                 // Update view position
1016                 base = Term->Width*Term->Height*(giVT_Scrollback);
1017                 if(Term->ViewPos < base)
1018                         Term->ViewPos += Term->Width;
1019                 if(Term->ViewPos > base)
1020                         Term->ViewPos = base;
1021                 
1022                 // Scroll terminal cache
1023                 base = Term->Width*(Term->Height*(giVT_Scrollback+1)-1);
1024                 memcpy(
1025                         Term->Text,
1026                         &Term->Text[Term->Width],
1027                         (Term->Width*Term->Height*(giVT_Scrollback+1)-Term->Width)*sizeof(tVT_Char)
1028                         );
1029                 
1030                 // Clear last row
1031                 for( i = 0; i < Term->Width; i ++ )
1032                 {
1033                         Term->Text[ base + i ].Ch = 0;
1034                         Term->Text[ base + i ].Colour = Term->CurColour;
1035                 }
1036                 
1037                 VT_int_ScrollFramebuffer( Term );
1038                 VT_int_UpdateScreen( Term, 0 );
1039         }
1040         // Ok, so we only need to scroll the screen
1041         else if(Term->WritePos >= Term->ViewPos + Term->Width*Term->Height)
1042         {
1043                 //Debug("Term->WritePos (%i) >= %i",
1044                 //      Term->WritePos,
1045                 //      Term->ViewPos + Term->Width*Term->Height
1046                 //      );
1047                 //Debug("Scrolling screen only");
1048                 
1049                 // Update the last line
1050                 Term->WritePos -= Term->Width;
1051                 VT_int_UpdateScreen( Term, 0 );
1052                 Term->WritePos += Term->Width;
1053                 
1054                 // Scroll
1055                 Term->ViewPos += Term->Width;
1056                 //Debug("Term->ViewPos = %i", Term->ViewPos);
1057                 VT_int_ScrollFramebuffer( Term );
1058                 VT_int_UpdateScreen( Term, 0 );
1059         }
1060         
1061         //LEAVE('-');
1062 }
1063
1064 /**
1065  * \fn void VT_int_ScrollFramebuffer( tVTerm *Term )
1066  * \note Scrolls the framebuffer by 1 text line
1067  */
1068 void VT_int_ScrollFramebuffer( tVTerm *Term )
1069 {
1070          int    tmp;
1071         struct {
1072                 Uint8   Op;
1073                 Uint16  DstX, DstY;
1074                 Uint16  SrcX, SrcY;
1075                 Uint16  W, H;
1076         } PACKED        buf;
1077         
1078         // Only update if this is the current terminal
1079         if( Term != gpVT_CurTerm )      return;
1080         
1081         // Switch to 2D Command Stream
1082         tmp = VIDEO_BUFFMT_2DSTREAM;
1083         VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETBUFFORMAT, &tmp);
1084         
1085         // BLIT from 0,0 to 0,giVT_CharHeight
1086         buf.Op = VIDEO_2DOP_BLIT;
1087         buf.DstX = 0;   buf.DstY = 0;
1088         buf.SrcX = 0;   buf.SrcY = giVT_CharHeight;
1089         buf.W = Term->Width * giVT_CharWidth;
1090         buf.H = (Term->Height-1) * giVT_CharHeight;
1091         VFS_WriteAt(giVT_OutputDevHandle, 0, sizeof(buf), &buf);
1092         
1093         // Restore old mode (this function is only called during text mode)
1094         tmp = VIDEO_BUFFMT_TEXT;
1095         VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETBUFFORMAT, &tmp);
1096 }
1097
1098 /**
1099  * \fn void VT_int_UpdateScreen( tVTerm *Term, int UpdateAll )
1100  * \brief Updates the video framebuffer
1101  */
1102 void VT_int_UpdateScreen( tVTerm *Term, int UpdateAll )
1103 {
1104         // Only update if this is the current terminal
1105         if( Term != gpVT_CurTerm )      return;
1106         
1107         switch( Term->Mode )
1108         {
1109         case TERM_MODE_TEXT:
1110                 // Re copy the entire screen?
1111                 if(UpdateAll) {
1112                         VFS_WriteAt(
1113                                 giVT_OutputDevHandle,
1114                                 0,
1115                                 Term->Width*Term->Height*sizeof(tVT_Char),
1116                                 &Term->Text[Term->ViewPos]
1117                                 );
1118                 }
1119                 // Only copy the current line
1120                 else {
1121                          int    pos = Term->WritePos - Term->WritePos % Term->Width;
1122                         VFS_WriteAt(
1123                                 giVT_OutputDevHandle,
1124                                 (pos - Term->ViewPos)*sizeof(tVT_Char),
1125                                 Term->Width*sizeof(tVT_Char),
1126                                 &Term->Text[pos]
1127                                 );
1128                 }
1129                 break;
1130         case TERM_MODE_FB:
1131                 VFS_WriteAt(
1132                         giVT_OutputDevHandle,
1133                         0,
1134                         Term->Width*Term->Height*sizeof(Uint32),
1135                         Term->Buffer
1136                         );
1137                 break;
1138         }
1139 }
1140
1141 /**
1142  * \fn void VT_int_ChangeMode(tVTerm *Term, int NewMode)
1143  * \brief Change the mode of a VTerm
1144  */
1145 void VT_int_ChangeMode(tVTerm *Term, int NewMode)
1146 {       
1147         switch(NewMode)
1148         {
1149         case TERM_MODE_TEXT:
1150                 Log_Log("VTerm", "Set VT %p to text mode", Term);
1151                 free(Term->Buffer);
1152                 Term->Text = calloc( Term->Width*Term->Height*(giVT_Scrollback+1), sizeof(tVT_Char) );
1153                 break;
1154         case TERM_MODE_FB:
1155                 Log_Log("VTerm", "Set VT %p to framebuffer mode (%ix%i)", Term,
1156                         Term->Width, Term->Height);
1157                 free(Term->Text);
1158                 Term->Buffer = calloc( Term->Width*Term->Height, sizeof(Uint32) );
1159                 Log_Debug("VTerm", "Term->Buffer = %p", Term->Buffer);
1160                 break;
1161         //case TERM_MODE_2DACCEL:
1162         //case TERM_MODE_3DACCEL:
1163         //      return;
1164         }
1165         
1166         Term->Mode = NewMode;
1167 }
1168
1169 // ---
1170 // Font Render
1171 // ---
1172 #define MONOSPACE_FONT  10816
1173
1174 #if MONOSPACE_FONT == 10808     // 8x8
1175 # include "vterm_font_8x8.h"
1176 #elif MONOSPACE_FONT == 10816   // 8x16
1177 # include "vterm_font_8x16.h"
1178 #endif
1179
1180 // === PROTOTYPES ===
1181 Uint8   *VT_Font_GetChar(Uint32 Codepoint);
1182
1183 // === GLOBALS ===
1184 int     giVT_CharWidth = FONT_WIDTH;
1185 int     giVT_CharHeight = FONT_HEIGHT;
1186
1187 // === CODE ===
1188 /**
1189  * \fn void VT_Font_Render(Uint32 Codepoint, void *Buffer, int Pitch, Uint32 BGC, Uint32 FGC)
1190  * \brief Render a font character
1191  */
1192 void VT_Font_Render(Uint32 Codepoint, void *Buffer, int Pitch, Uint32 BGC, Uint32 FGC)
1193 {
1194         Uint8   *font;
1195         Uint32  *buf = Buffer;
1196          int    x, y;
1197         
1198         font = VT_Font_GetChar(Codepoint);
1199         
1200         for(y = 0; y < FONT_HEIGHT; y ++)
1201         {
1202                 for(x = 0; x < FONT_WIDTH; x ++)
1203                 {
1204                         if(*font & (1 << (FONT_WIDTH-x-1)))
1205                                 buf[x] = FGC;
1206                         else
1207                                 buf[x] = BGC;
1208                 }
1209                 buf += Pitch;
1210                 font ++;
1211         }
1212 }
1213
1214 /**
1215  * \fn Uint32 VT_Colour12to24(Uint16 Col12)
1216  * \brief Converts a 
1217  */
1218 Uint32 VT_Colour12to24(Uint16 Col12)
1219 {
1220         Uint32  ret;
1221          int    tmp;
1222         tmp = Col12 & 0xF;
1223         ret  = (tmp << 0) | (tmp << 4);
1224         tmp = (Col12 & 0xF0) >> 4;
1225         ret |= (tmp << 8) | (tmp << 12);
1226         tmp = (Col12 & 0xF00) >> 8;
1227         ret |= (tmp << 16) | (tmp << 20);
1228         return ret;
1229 }
1230
1231 /**
1232  * \fn Uint8 *VT_Font_GetChar(Uint32 Codepoint)
1233  * \brief Gets an index into the font array given a Unicode Codepoint
1234  * \note See http://en.wikipedia.org/wiki/CP437
1235  */
1236 Uint8 *VT_Font_GetChar(Uint32 Codepoint)
1237 {
1238          int    index = 0;
1239         if(Codepoint < 128)
1240                 return &VTermFont[Codepoint*FONT_HEIGHT];
1241         switch(Codepoint)
1242         {
1243         case 0xC7:      index = 128;    break;  // Ç
1244         case 0xFC:      index = 129;    break;  // ü
1245         case 0xE9:      index = 130;    break;  // é
1246         case 0xE2:      index = 131;    break;  // â
1247         case 0xE4:      index = 132;    break;  // ä
1248         case 0xE0:      index = 133;    break;  // à
1249         case 0xE5:      index = 134;    break;  // å
1250         case 0xE7:      index = 135;    break;  // ç
1251         case 0xEA:      index = 136;    break;  // ê
1252         case 0xEB:      index = 137;    break;  // ë
1253         case 0xE8:      index = 138;    break;  // è
1254         case 0xEF:      index = 139;    break;  // ï
1255         case 0xEE:      index = 140;    break;  // î
1256         case 0xEC:      index = 141;    break;  // ì
1257         case 0xC4:      index = 142;    break;  // Ä
1258         case 0xC5:      index = 143;    break;  // Å
1259         }
1260         
1261         return &VTermFont[index*FONT_HEIGHT];
1262 }
1263
1264 EXPORTAS(&giVT_CharWidth, giVT_CharWidth);
1265 EXPORTAS(&giVT_CharHeight, giVT_CharHeight);
1266 EXPORT(VT_Font_Render);
1267 EXPORT(VT_Colour12to24);

UCC git Repository :: git.ucc.asn.au