1 Introduction
2 Ground Rules

Building a File System
3 File Systems
4 File Content Data Structure
5 Allocation Cluster Manager
6 Exceptions and Emancipation
7 Base Classes, Testing, and More
8 File Meta Data
9 Native File Class
10 Our File System
11 Allocation Table
12 File System Support Code
13 Initializing the File System
14 Contiguous Files
15 Rebuilding the File System
16 Native File System Support Methods
17 Lookups, Wildcards, and Unicode, Oh My
18 Finishing the File System Class

The Init Program
19 Hardware Abstraction and UOS Architecture
20 Init Command Mode
21 Using Our File System
22 Hardware and Device Lists
23 Fun with Stores: Partitions
24 Fun with Stores: RAID
25 Fun with Stores: RAM Disks
26 Init wrap-up

The Executive
27 Overview of The Executive
28 Starting the Kernel
29 The Kernel
30 Making a Store Bootable
31 The MMC
32 The HMC
33 Loading the components
34 Using the File Processor
35 Symbols and the SSC
36 The File Processor and Device Management
37 The File Processor and File System Management
38 Finishing Executive Startup

Users and Security
39 Introduction to Users and Security
40 More Fun With Stores: File Heaps
41 File Heaps, part 2
42 SysUAF
43 TUser
44 SysUAF API

Terminal I/O
45 Shells and UCL
46 UOS API, the Application Side
47 UOS API, the Executive Side
48 I/O Devices
49 Streams
50 Terminal Output Filters
51 The TTerminal Class
52 Handles
53 Putting it All Together
54 Getting Terminal Input
55 QIO
56 Cooking Terminal Input
57 Putting it all together, part 2
58 Quotas and I/O

UCL
59 UCL Basics
60 Symbol Substitution
61 UCL Command execution
62 UCL Command execution, part 2
63 UCL Command Abbreviation
64 ASTs
65 UCL Expressions, Part 1

Glossary/Index


Download sources
Download binaries

The TTerminal Class

The TTerminal class handles I/O for terminals, printers, and conceptually similar devices. Because it handles both input and output, the class has an instance of both an input filter and output filter. For the current discussion, we are only going to look at the output half of it. We will also save discussion of various terminal settings (such as speed) for a future article, and we'll exclude the read method. Here is the definition of the class, minus settings-related instance data.

type TTerminal = class( TCommon_COM_Interface )
                    protected // General instance data...
                        Stream : TCOM_Stream64 ;
                        Input_Filter : TInput_Filter ;
                        Output_Filter : TOutput_Filter ;
                        Kernel : TUOS_Kernel ;
                        _Writes : cardinal ; // Number of write operations

                    public // API...
                        function Is_Class( N : PAnsiChar ) : boolean ;
                            override ;

                        function Write_Data( var Data ; _Size : TStore_Address64 ;
                            var UEC : TUnified_Exception ) : TStore_Address64 ;
                            virtual ; stdcall ;

                        procedure Write_To_Driver ; virtual ; stdcall ;

                        function Max_Record_Size : TStore_Size64 ;
                            virtual ; stdcall ;

                        function Min_Record_Size : TStore_Address64 ;
                            virtual ; stdcall ;

                        function Get_Column : cardinal ;
                        procedure Set_Column( Value : cardinal ) ;
                        function Get_Line : cardinal ;
                        procedure Set_Line( Value : cardinal ) ;

                    public // Properties...
                        property Column : cardinal
                            read Get_Column
                            write Set_Column ;
                        property Line : cardinal
                            read Get_Line
                            write Set_Line ;
                        property Writes : cardinal
                            read _Writes
                            write _Writes ;
                end ; // TTerminal
The class contains the filters, a stream object, which is the actual device interface. It also has a pointer to the kernel, and the number of write operations for the device. The methods are fairly simple - we'll include them here with very little commentary. The Write_Data method will be discussed afterwards.

// API...

function TTerminal.Is_Class( N : PAnsiChar ) : boolean ;

var _N : string ;

begin
    _N := lowercase( string( N ) ) ;
    Result := _N = 'tterminal' ;
end ;


procedure TTerminal.Write_To_Driver ;

begin
    if( Output_Filter = nil ) then
    begin
        exit ; // Nothing to write to
    end ;
    Output_Filter.Write_To_Driver ;
end ;


function TTerminal.Max_Record_Size : TStore_Size64 ;

begin
    Result := 0 ; // Undefined
end ;


function TTerminal.Min_Record_Size : TStore_Address64 ;

begin
    Result := 1 ;
    case Format of
        TT_Unicode16 : Result := 2 ; // Unicode-16
        TT_Unicode32 : Result := 4 ; // Unicode-32
        // else assume 1
    end ;
end ;


function TTerminal.Get_Column : cardinal ;

begin
    Result := Stream.Position and $FFFFFFFF ;
end ;


procedure TTerminal.Set_Column( Value : cardinal ) ;

begin
    Stream.Position := ( Stream.Position and ( not $FFFFFFFF ) ) or Value ;
end ;


function TTerminal.Get_Line : cardinal ;

begin
    Result := Stream.Position shr 32 ;
end ;


procedure TTerminal.Set_Line( Value : cardinal ) ;

begin
    Stream.Position := ( Stream.Position and $FFFFFFFF ) or ( Value shl 32 ) ;
end ;
I will make only one comment: the stream's position is partitioned into a column (low 32 bits) and a row (high 32 bits).

Here's the Write_Data method.

function TTerminal.Write_Data( var Data ; _Size : TStore_Address64 ;
    var UEC : TUnified_Exception ) : TStore_Address64 ;

var S : string ;

begin
    Result := 0 ;
    if( Stream.Read_Only ) then
    begin
        UEC := Set_Last_Error( Create_Error( UOSErr_Read_Only ) ) ;
        exit ;
    end ;
    UEC := Set_Last_Error( nil ) ;
    setlength( S, _Size ) ;
    move( Data, PChar( S )[ 0 ], _Size ) ;
    if( Output_Filter = nil ) then
    begin
        Output_Filter := TDefault_Output_Filter.Create ;
        Output_Filter.Term := self ;
    end ;
    Output_Filter.Write( S, False ) ;
end ;
The Write_Data method takes data, size of the data (in bytes), and it returns the number of bytes that were written, and an exception object. The method sets the result to 0, in case of an error. If the stream (device) is read-only, we return an exception since we can't write to a read-only device. Otherwise, we clear the exception. Then we copy the data to a string. If the current output filter is nil, we create a default output filter for the terminal. Finally, we call the output filter's Write method (which we discussed in the last article).

Now that we've discussed TTerminal, we will look at where and when the class is created. We will make some modifications to the TFiP.Reload_Devices method (first discussed back in article 36). We extend the work we do for stream devices as follows:

        case ( Info.Flags and DF_Class_Mask ) of
            DFC_Stream :
                begin
                    S := '_TERM' + S + ':' ;
                    _Device_Names.Set_Value( PChar( S ), '', 0 ) ;
                    _Devices.AddObject( S, Device ) ;
                    Device.Stream := TFip_Stream.Create ;
                    TFiP_Stream( Device.Stream )._Device := Device ;
                    TFiP_Stream( Device.Stream ).Driver := HAL.Terminal( Index ) ;
                    Device.Terminal := TTerminal.Create ;
                    Device.Terminal.Stream := Device.Stream ;
                    TFiP_Stream( Device.Stream ).Driver.Send_Status ;
                end ; // DFC_Stream
We create instances of a TFiP_Stream and TTerminal, and then we link everything together (terminal, stream, and device all know about each other). Finally, we call the driver's Send_Status method. This requests the driver to send any device status changes to the Kernel when they happen. We don't want drivers to send status to the Kernel until we are ready to handle it, so by default they don't. Once we've created the infrastructure of the TStream and TTerminal, we are ready to hear from the device when its status changes. This status is largely irrelevant for output, but we will look into it in a future article for handling input.

In the next article, we will introduce the concept and implementation of handles.