3 * - By John Hodge (thePowersGang)
6 * - Virtual Terminal - Initialisation and VFS Interface
12 #include <api_drv_keyboard.h>
13 #include <api_drv_video.h>
15 #include <semaphore.h>
18 #define VERSION ((0<<8)|(50))
21 //#define DEFAULT_OUTPUT "BochsGA"
22 #define DEFAULT_OUTPUT "Vesa"
23 #define FALLBACK_OUTPUT "x86_VGAText"
24 #define DEFAULT_INPUT "Keyboard"
25 #define DEFAULT_WIDTH 640
26 #define DEFAULT_HEIGHT 480
27 #define DEFAULT_SCROLLBACK 4 // 2 Screens of text + current screen
28 //#define DEFAULT_SCROLLBACK 0
33 extern void Debug_SetKTerminal(const char *File);
36 int VT_Install(char **Arguments);
37 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data);
38 void VT_int_PutFBData(tVTerm *Term, size_t Offset, size_t Length, const void *Data);
39 void VT_PTYOutput(void *Handle, size_t Length, const void *Data);
40 int VT_PTYResize(void *Handle, const struct ptydims *Dims);
41 int VT_PTYModeset(void *Handle, const struct ptymode *Mode);
46 MODULE_DEFINE(0, VERSION, VTerm, VT_Install, NULL, "PTY", NULL);
47 tVFS_NodeType gVT_RootNodeType = {
48 .TypeName = "VTerm Root",
49 .IOCtl = VT_Root_IOCtl
51 tDevFS_Driver gVT_DrvInfo = {
58 .Type = &gVT_RootNodeType
62 tVTerm gVT_Terminals[NUM_VTS];
63 int giVT_CurrentTerminal = 0;
64 tVTerm *gpVT_CurTerm = &gVT_Terminals[0];
65 // --- Video State ---
66 short giVT_RealWidth = DEFAULT_WIDTH; //!< Screen Width
67 short giVT_RealHeight = DEFAULT_HEIGHT; //!< Screen Height
68 int giVT_Scrollback = DEFAULT_SCROLLBACK;
69 // --- Driver Handles ---
70 char *gsVT_OutputDevice = NULL;
71 char *gsVT_InputDevice = NULL;
72 int giVT_OutputDevHandle = -2;
73 int giVT_InputDevHandle = -2;
77 * \fn int VT_Install(char **Arguments)
78 * \brief Installs the Virtual Terminal Driver
80 int VT_Install(char **Arguments)
89 for(args = Arguments; (arg = *args); args++ )
91 char data[strlen(arg)+1];
95 val = strchr(arg, '=');
98 data[ val - arg ] = '\0';
101 Log_Debug("VTerm", "Argument '%s'='%s'", opt, val);
103 if( strcmp(opt, "Video") == 0 ) {
104 if( !gsVT_OutputDevice )
105 gsVT_OutputDevice = val;
107 else if( strcmp(opt, "Input") == 0 ) {
108 if( !gsVT_InputDevice )
109 gsVT_InputDevice = val;
111 else if( strcmp(opt, "Width") == 0 ) {
112 giVT_RealWidth = atoi( val );
114 else if( strcmp(opt, "Height") == 0 ) {
115 giVT_RealHeight = atoi( val );
117 else if( strcmp(opt, "Scrollback") == 0 ) {
118 giVT_Scrollback = atoi( val );
121 Log_Notice("VTerm", "Unknown option '%s'", opt);
127 if(!gsVT_OutputDevice) gsVT_OutputDevice = (char*)DEFAULT_OUTPUT;
128 else if( Module_EnsureLoaded( gsVT_OutputDevice ) ) gsVT_OutputDevice = (char*)DEFAULT_OUTPUT;
129 if( Module_EnsureLoaded( gsVT_OutputDevice ) ) gsVT_OutputDevice = (char*)FALLBACK_OUTPUT;
130 if( Module_EnsureLoaded( gsVT_OutputDevice ) ) {
131 Log_Error("VTerm", "Fallback video '%s' is not avaliable, giving up", FALLBACK_OUTPUT);
132 return MODULE_ERR_MISC;
135 if(!gsVT_InputDevice) gsVT_InputDevice = (char*)DEFAULT_INPUT;
136 else if( Module_EnsureLoaded( gsVT_InputDevice ) ) gsVT_InputDevice = (char*)DEFAULT_INPUT;
137 if( Module_EnsureLoaded( gsVT_InputDevice ) ) {
138 Log_Error("VTerm", "Fallback input '%s' is not avaliable, input will not be avaliable", DEFAULT_INPUT);
141 // Create device paths
144 tmp = malloc( 9 + strlen(gsVT_OutputDevice) + 1 );
145 strcpy(tmp, "/Devices/");
146 strcpy(&tmp[9], gsVT_OutputDevice);
147 gsVT_OutputDevice = tmp;
149 tmp = malloc( 9 + strlen(gsVT_InputDevice) + 1 );
150 strcpy(tmp, "/Devices/");
151 strcpy(&tmp[9], gsVT_InputDevice);
152 gsVT_InputDevice = tmp;
155 Log_Log("VTerm", "Using '%s' as output", gsVT_OutputDevice);
156 Log_Log("VTerm", "Using '%s' as input", gsVT_InputDevice);
163 Log_Debug("VTerm", "Initialising nodes (and creating buffers)");
164 for( i = 0; i < NUM_VTS; i++ )
166 gVT_Terminals[i].Mode = TERM_MODE_TEXT;
167 gVT_Terminals[i].Flags = 0;
168 // gVT_Terminals[i].Flags = VT_FLAG_HIDECSR; //HACK - Stop all those memcpy calls
169 gVT_Terminals[i].CurColour = DEFAULT_COLOUR;
170 gVT_Terminals[i].WritePos = 0;
171 gVT_Terminals[i].AltWritePos = 0;
172 gVT_Terminals[i].ViewPos = 0;
173 gVT_Terminals[i].ScrollHeight = 0;
176 VT_int_Resize( &gVT_Terminals[i], giVT_RealWidth, giVT_RealHeight );
177 gVT_Terminals[i].Mode = PTYBUFFMT_TEXT;
178 char name[] = {'v','t','0'+i,'\0'};
179 gVT_Terminals[i].PTY = PTY_Create(name, &gVT_Terminals[i],
180 VT_PTYOutput, VT_PTYResize, VT_PTYModeset);
181 struct ptymode mode = {
182 .OutputMode = PTYBUFFMT_TEXT,
183 .InputMode = PTYIMODE_CANON|PTYIMODE_ECHO
185 struct ptydims dims = {
186 .W = giVT_RealWidth / giVT_CharWidth,
187 .H = giVT_RealHeight / giVT_CharHeight,
188 .PW = giVT_RealWidth,
189 .PH = giVT_RealHeight
191 PTY_SetAttrib(gVT_Terminals[i].PTY, &dims, &mode, 0);
195 DevFS_AddDevice( &gVT_DrvInfo );
197 // Set kernel output to VT0
198 Log_Debug("VTerm", "Setting kernel output to VT#0");
199 Debug_SetKTerminal("/Devices/pts/vt0");
201 return MODULE_ERR_OK;
205 * \brief Set the video resolution
206 * \param Width New screen width
207 * \param Height New screen height
209 void VT_SetResolution(int Width, int Height)
211 tVideo_IOCtl_Mode mode = {0};
215 // Create the video mode
217 mode.height = Height;
222 VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_FINDMODE, &mode );
224 if( Width != mode.width || Height != mode.height )
227 "Selected resolution (%ix%i) is not supported by the device, using (%ix%i)",
228 giVT_RealWidth, giVT_RealHeight,
229 mode.width, mode.height
231 giVT_RealWidth = mode.width;
232 giVT_RealHeight = mode.height;
234 VFS_IOCtl( giVT_OutputDevHandle, VIDEO_IOCTL_GETSETMODE, &tmp );
236 // Resize text terminals if needed
237 // - VT0 check is for the first resolution set
238 if( gVT_Terminals[0].Text && (giVT_RealWidth != mode.width || giVT_RealHeight != mode.height) )
240 int newBufSize = (giVT_RealWidth/giVT_CharWidth)
241 *(giVT_RealHeight/giVT_CharHeight)
242 *(giVT_Scrollback+1);
244 // Resize the text terminals
245 Log_Debug("VTerm", "Resizing terminals to %ix%i",
246 giVT_RealWidth/giVT_CharWidth, giVT_RealHeight/giVT_CharHeight);
247 for( i = 0; i < NUM_VTS; i ++ )
249 if( gVT_Terminals[i].Mode != TERM_MODE_TEXT ) continue;
251 gVT_Terminals[i].TextWidth = giVT_RealWidth/giVT_CharWidth;
252 gVT_Terminals[i].TextHeight = giVT_RealHeight/giVT_CharHeight;
253 gVT_Terminals[i].ScrollHeight = gVT_Terminals[i].TextHeight;
255 gVT_Terminals[i].Text = realloc(
256 gVT_Terminals[i].Text,
257 newBufSize*sizeof(tVT_Char)
264 * \fn int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
265 * \brief Control the VTerm Driver
267 int VT_Root_IOCtl(tVFS_Node *Node, int Id, void *Data)
272 case DRV_IOCTL_TYPE: return DRV_TYPE_MISC;
273 case DRV_IOCTL_IDENT: memcpy(Data, "VT\0\0", 4); return 0;
274 case DRV_IOCTL_VERSION: return VERSION;
275 case DRV_IOCTL_LOOKUP: return 0;
277 case 4: // Get Video Driver
278 if(Data) strcpy(Data, gsVT_OutputDevice);
279 return strlen(gsVT_OutputDevice);
281 case 5: // Set Video Driver
282 if(!Data) return -EINVAL;
283 if(Threads_GetUID() != 0) return -EACCES;
287 // TODO: Check if the string used is a heap string
289 free(gsVT_OutputDevice);
291 gsVT_OutputDevice = malloc(len+1);
292 strcpy(gsVT_OutputDevice, Data);
294 VFS_Close(giVT_OutputDevHandle);
295 giVT_OutputDevHandle = -1;
303 void VT_int_PutFBData(tVTerm *Term, size_t Offset, size_t Length, const void *Buffer)
305 size_t maxlen = Term->Width * Term->Height * 4;
307 ENTER("pTerm xOffset xLength pBuffer", Term, Offset, Length, Buffer);
309 if( Offset >= maxlen ) {
314 LOG("maxlen = 0x%x", maxlen);
315 Length = MIN(Length, maxlen - Offset);
317 // If the terminal is currently shown, write directly to the screen
318 if( Term == gpVT_CurTerm )
320 // Center the terminal vertically
321 if( giVT_RealHeight > Term->Height ) {
322 Offset += (giVT_RealHeight - Term->Height) / 2 * Term->Width * 4;
323 LOG("Altered offset 0x%x", Offset);
326 // If the terminal is not native width, center it horizontally
327 if( giVT_RealWidth > Term->Width )
329 // No? :( Well, just center it
332 // TODO: Fix to handle the final line correctly?
333 x = Offset/4; y = x / Term->Width; x %= Term->Width;
334 w = Length/4+x; h = w / Term->Width; w %= Term->Width;
336 LOG("(%i,%i) %ix%i", x, y, w, h);
339 x += (giVT_RealWidth - Term->Width) / 2;
340 dst_ofs = (x + y * giVT_RealWidth) * 4;
343 VFS_WriteAt( giVT_OutputDevHandle,
348 Buffer = (const Uint32*)Buffer + Term->Width;
349 dst_ofs += giVT_RealWidth * 4;
352 // otherwise, just go directly to the screen
355 VFS_WriteAt( giVT_OutputDevHandle, Offset, Length, Buffer );
358 // If not active, write to the backbuffer (allocating if needed)
362 Term->Buffer = malloc( Term->Width * Term->Height * 4 );
363 LOG("Direct to cache");
364 // Copy to the local cache
365 memcpy( (char*)Term->Buffer + Offset, Buffer, Length );
370 void VT_PTYOutput(void *Handle, size_t Length, const void *Data)
372 tVTerm *term = Handle;
376 VT_int_PutString(term, Data, Length);
379 // TODO: How do offset?
380 VT_int_PutFBData(term, 0, Length, Data);
382 case PTYBUFFMT_2DCMD:
383 // TODO: Impliment 2D commands
384 VT_int_Handle2DCmd(term, Length, Data);
386 case PTYBUFFMT_3DCMD:
387 // TODO: Impliment 3D commands
392 int VT_PTYResize(void *Handle, const struct ptydims *Dims)
394 tVTerm *term = Handle;
395 int newW = Dims->W * (term->Mode == PTYBUFFMT_TEXT ? giVT_CharWidth : 1);
396 int newH = Dims->H * (term->Mode == PTYBUFFMT_TEXT ? giVT_CharHeight : 1);
397 if( newW > giVT_RealWidth || newH > giVT_RealHeight )
399 VT_int_Resize(term, newW, newH);
403 int VT_PTYModeset(void *Handle, const struct ptymode *Mode)
405 tVTerm *term = Handle;
406 term->Mode = (Mode->OutputMode & PTYOMODE_BUFFMT);
408 memset(&term->Cmd2D, 0, sizeof(term->Cmd2D));
410 if( term == gpVT_CurTerm ) {
414 VT_SetMode(VIDEO_BUFFMT_TEXT);
417 VT_SetMode(VIDEO_BUFFMT_FRAMEBUFFER);
427 * \fn int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
428 * \brief Call an IO Control on a virtual terminal
430 int VT_Terminal_IOCtl(tVFS_Node *Node, int Id, void *Data)
434 tVTerm *term = Node->ImplPtr;
435 ENTER("pNode iId pData", Node, Id, Data);
437 if(Id >= DRV_IOCTL_LOOKUP) {
438 // Only root can fiddle with graphics modes
439 // TODO: Remove this and replace with user ownership
440 if( Threads_GetUID() != 0 ) return -1;
447 LEAVE('i', DRV_TYPE_TERMINAL);
448 return DRV_TYPE_TERMINAL;
449 case DRV_IOCTL_IDENT:
450 memcpy(Data, "VT\0\0", 4);
453 case DRV_IOCTL_VERSION:
456 case DRV_IOCTL_LOOKUP:
460 // Get/Set the mode (and apply any changes)
461 case TERM_IOCTL_MODETYPE:
464 if( CheckMem(Data, sizeof(int)) == 0 ) {
468 Log_Log("VTerm", "VTerm %i mode set to %i", (int)Node->Inode, *iData);
470 // Update mode if needed
471 if( term->Mode != *iData || term->NewWidth || term->NewHeight)
473 // Adjust for text mode
474 if( *iData == TERM_MODE_TEXT ) {
475 term->NewHeight *= giVT_CharHeight;
476 term->NewWidth *= giVT_CharWidth;
478 // Fill unchanged dimensions
479 if(term->NewHeight == 0) term->NewHeight = term->Height;
480 if(term->NewWidth == 0) term->NewWidth = term->Width;
482 VT_int_ChangeMode(term, *iData, term->NewWidth, term->NewHeight);
483 // Clear unapplied dimensions
488 // Update the screen dimensions
489 if(Node->Inode == giVT_CurrentTerminal)
490 VT_SetTerminal( giVT_CurrentTerminal );
492 LEAVE('i', term->Mode);
495 // Get/set the terminal width
496 case TERM_IOCTL_WIDTH:
498 if( CheckMem(Data, sizeof(int)) == 0 ) {
502 term->NewWidth = *iData;
505 ret = term->NewWidth;
506 else if( term->Mode == TERM_MODE_TEXT )
507 ret = term->TextWidth;
513 // Get/set the terminal height
514 case TERM_IOCTL_HEIGHT:
516 if( CheckMem(Data, sizeof(int)) == 0 ) {
520 term->NewHeight = *iData;
522 if( term->NewHeight )
523 ret = term->NewHeight;
524 else if( term->Mode == TERM_MODE_TEXT )
525 ret = term->TextHeight;
531 case TERM_IOCTL_FORCESHOW:
532 Log_Log("VTerm", "Thread %i forced VTerm %i to be shown",
533 Threads_GetTID(), (int)Node->Inode);
534 VT_SetTerminal( Node->Inode );
538 case TERM_IOCTL_GETSETCURSOR:
541 tVideo_IOCtl_Pos *pos = Data;
542 if( !CheckMem(Data, sizeof(*pos)) ) {
548 if( term->Mode == TERM_MODE_TEXT )
550 if(term->Flags & VT_FLAG_ALTBUF)
551 term->AltWritePos = pos->x + pos->y * term->TextWidth;
553 term->WritePos = pos->x + pos->y * term->TextWidth + term->ViewPos;
554 VT_int_UpdateCursor(term, 0);
558 term->VideoCursorX = pos->x;
559 term->VideoCursorY = pos->y;
560 VT_int_UpdateCursor(term, 1);
563 ret = (term->Flags & VT_FLAG_ALTBUF) ? term->AltWritePos : term->WritePos-term->ViewPos;
567 case TERM_IOCTL_SETCURSORBITMAP: {
568 tVideo_IOCtl_Bitmap *bmp = Data;
571 free( term->VideoCursor );
572 term->VideoCursor = NULL;
577 // Sanity check bitmap
578 if( !CheckMem(bmp, sizeof(tVideo_IOCtl_Bitmap)) ) {
579 Log_Notice("VTerm", "%p in TERM_IOCTL_SETCURSORBITMAP invalid", bmp);
583 if( !CheckMem(bmp->Data, bmp->W*bmp->H*sizeof(Uint32)) ) {
584 Log_Notice("VTerm", "%p in TERM_IOCTL_SETCURSORBITMAP invalid", bmp);
589 // Reallocate if needed
590 if(term->VideoCursor)
592 if(bmp->W * bmp->H != term->VideoCursor->W * term->VideoCursor->H) {
593 free(term->VideoCursor);
594 term->VideoCursor = NULL;
597 if(!term->VideoCursor) {
598 term->VideoCursor = malloc(sizeof(tVideo_IOCtl_Pos) + bmp->W*bmp->H*sizeof(Uint32));
599 if(!term->VideoCursor) {
600 Log_Error("VTerm", "Unable to allocate memory for cursor");
606 memcpy(term->VideoCursor, bmp, sizeof(tVideo_IOCtl_Pos) + bmp->W*bmp->H*sizeof(Uint32));
608 Log_Debug("VTerm", "Set VT%i's cursor to %p %ix%i",
609 (int)term->Node.Inode, bmp, bmp->W, bmp->H);
611 if(gpVT_CurTerm == term)
612 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSORBITMAP, term->VideoCursor);
621 void VT_Terminal_Reference(tVFS_Node *Node)
623 // Append PID to list
626 void VT_Terminal_Close(tVFS_Node *Node)
628 // Remove PID from list
633 * \fn void VT_SetTerminal(int ID)
634 * \brief Set the current terminal
636 void VT_SetTerminal(int ID)
638 // Copy the screen state
639 if( ID != giVT_CurrentTerminal && gpVT_CurTerm->Mode != TERM_MODE_TEXT )
641 if( !gpVT_CurTerm->Buffer )
642 gpVT_CurTerm->Buffer = malloc( gpVT_CurTerm->Width*gpVT_CurTerm->Height*4 );
643 if( gpVT_CurTerm->Width < giVT_RealWidth )
646 Uint32 *dest = gpVT_CurTerm->Buffer;
647 // Slower scanline copy
648 for( int line = 0; line < gpVT_CurTerm->Height; line ++ )
650 VFS_ReadAt(giVT_OutputDevHandle, ofs, gpVT_CurTerm->Width*4, dest);
651 ofs += giVT_RealWidth * 4;
652 dest += gpVT_CurTerm->Width;
657 VFS_ReadAt(giVT_OutputDevHandle,
658 0, gpVT_CurTerm->Height*giVT_RealWidth*4,
662 LOG("Cached screen contents");
665 // Update current terminal ID
666 Log_Log("VTerm", "Changed terminal from %i to %i", giVT_CurrentTerminal, ID);
667 giVT_CurrentTerminal = ID;
668 gpVT_CurTerm = &gVT_Terminals[ID];
670 LOG("Attempting VT_SetMode");
672 if( gpVT_CurTerm->Mode == PTYBUFFMT_TEXT )
674 VT_SetMode( VIDEO_BUFFMT_TEXT );
678 // Update the cursor image
679 if(gpVT_CurTerm->VideoCursor)
680 VFS_IOCtl(giVT_OutputDevHandle, VIDEO_IOCTL_SETCURSORBITMAP, gpVT_CurTerm->VideoCursor);
681 VT_SetMode( VIDEO_BUFFMT_FRAMEBUFFER );
686 if(gpVT_CurTerm->Buffer)
688 // TODO: Handle non equal sized
690 giVT_OutputDevHandle,
692 gpVT_CurTerm->Width*gpVT_CurTerm->Height*sizeof(Uint32),
695 LOG("Updated screen contents");
698 VT_int_UpdateCursor(gpVT_CurTerm, 1);
700 VT_int_UpdateScreen(gpVT_CurTerm, 1);