Computing: Free Pascal Programming

Implementing mouse support in a Free Pascal DOS program.

This tutorial is about using a mouse in a Free Pascal program on DOS, where DOS means a native DOS operating system; in other words it does not apply to programs running in Windows Command Prompt. The sample programs have been built on FreeDOS, using Free Pascal for go32v2. They work fine on FreeDOS with CuteMouse; they should also work on MS-DOS with the default Microsoft mouse.

Free Pascal includes the old Turbo Pascal units. You probably use Crt in most of your DOS (or Windows Command Prompt) programs. But, did you ever try out the Mouse unit? Adding mouse support to a DOS program can make it more user-friendly and simpler to use. And implementing mouse support in a Free Pascal program is really not a big deal.

A program using the mouse should always include the following two procedure calls:

You should also check if there is actually a mouse installed and if this mouse is correctly detected. This can be done using the function DetectMouse, that returns a byte value, indicating the number of mouse buttons available.

The sample program MOUSE1.PAS checks if there is a mouse installed, and if so displays the number of available mouse buttons.

    program mouse1;
    uses
        Mouse;
    var
        Buttons: Byte;
    begin
        Writeln('Checking if there is a mouse installed...');
        InitMouse;
        Buttons := DetectMouse;
        if Buttons = 0 then
            Writeln('  Error: No mouse found!')
        else
            Writeln('  Found mouse with ', Buttons, ' buttons.');
        DoneMouse;
    end.

The program output should normally be "Found mouse with 2 buttons.".

Neither the call to InitMouse, nor the call to DetectMouse will result in the display of the mouse cursor. To get the cursor onto the screen, you have to call the procedure ShowMouse (if, for one reason or another, you don't want the mouse cursor to be visible, you can call the procedure HideMouse).

The Free Pascal Mouse unit includes several routines to check the mouse status. I don't know if I missed something, but the fact is that I failed to make work GetMouseEvent and PollMouseEvent. On the other hand, GetMouseButtons works fine. The function returns the current button state of the mouse as a word. If no button has been pushed the return value is zero. The left button returns $01, the right one $02, the middle one $04. If several buttons are pushed together, the return is the or-ed value of the corresponding constants; e.g. left + right button = $01 or $02 = $03. In order to simplify things for the programmer, there are 3 pre-defined constants to reference the function return value: MouseLeftButton, MouseRightButton, MouseMiddleButton.

The sample program MOUSE2.PAS waits for the user to push a mouse button. If the left button was pressed, a message is displayed and the program exits, if the right button was pressed, the program exits without displaying a message.

    program mouse2;
    uses
        Mouse;
    var
        Buttons: Byte;
        ButtonPressed: Word;
    begin
        InitMouse;
        Buttons := DetectMouse;
        if Buttons = 0 then
            Writeln('Error: No mouse found!')
        else begin
            ShowMouse;
            Writeln('Should I display a message?');
            Write('  Press left mouse button for yes, right mouse button for no... ');
            ButtonPressed := 0;
            repeat
                ButtonPressed := GetMouseButtons;
            until (ButtonPressed = MouseLeftButton) or (ButtonPressed = MouseRightButton);
            if ButtonPressed = MouseLeftButton then begin
                Writeln; Writeln('Hello World!');
            end;
        end;
        DoneMouse;
    end.

Knowing that a mouse button has been pushed is, of course, only useful if we know what position on the screen the mouse was at, when the user made the left- or right-click. There are two functions that inform us about the actual mouse position: GetMouseX returns a word that contains the actual horizontal position of the mouse, i.e. the actual screen line. It's a value between 1 and 80, 1 being the left side of the screen, 80 the right side. GetMouseY returns the actual vertical position of the mouse, i.e. the actual screen column. It's a value between 1 and 25 (between 1 and 43, or 1 and 50, if you use the 43-lines, resp. the 50-lines mode), 1 being the top of the screen, 25 the bottom.

Similarly to the Crt unit that allows to position the "write" cursor using the procedure GotoXY, the Mouse unit allows to position the mouse cursor using the procedure SetMouseXY. The procedure requires two word arguments, referencing the horizontal resp. vertical position on the screen where the mouse cursor has to be placed. Position 1,1 is the top-left corner, position 25,80 the bottom-right corner of the screen.

Note: There is a mistake in the official Free Pascal documentation, where they say that the mouse cursor positions start at 0. This is false, as you can see on the screenshots further down in the text...

The sample program MOUSE3.PAS waits for the user to push the left mouse button and then displays the screen position where the user clicked.

    program mouse3;
    uses
        Mouse;
    var
        Buttons: Byte;
        X, Y: Word;
    begin
        InitMouse;
        Buttons := DetectMouse;
        if Buttons = 0 then
            Writeln('Error: No mouse found!')
        else begin
            ShowMouse;
            Write('Please, click somewhere on the screen...');
            repeat
            until GetMouseButtons = MouseLeftButton;
            X := GetMouseX; Y := GetMouseY;
            Writeln; Writeln('You clicked at screen position: ', Y, ',', X);
        end;
        DoneMouse;
    end.

The screenshots show the program output before the user has clicked with the mouse (screenshot on the left), resp. after the user has clicked (screenshot on the right).

Free Pascal mouse support on DOS: Mouse click position [1]
Free Pascal mouse support on DOS: Mouse click position [2]

The sample program MOUSE4.PAS continually displays the mouse position (thus tracks the mouse movement) until the user pushes one of the two mouse buttons. Note the usage of the variables "OldX" and "OldY" that allow to check if the mouse position has changed, and only doing the position display if this is the case; this prevents the flickering of the text that you would get otherwise.

    program mouse4;
    uses
        Crt, Mouse;
    var
        Buttons: Byte;
        X, Y, OldX, OldY: Word;
    begin
        ClrScr; CursorOff;
        Writeln('Mouse position test.'); Writeln;
        InitMouse;
        Buttons := DetectMouse;
        if Buttons = 0 then
            Writeln('Error: No mouse found!')
        else begin
            ShowMouse;
            Writeln('Move the mouse around, click to terminate the program...');
            OldX := -1; OldY := -1;
            repeat
                X := GetMouseX; Y := GetMouseY;
                if (X <> OldX) or (Y <> OldY) then begin
                    GotoXY(1, 5);
                    Write('Mouse position: line = ');
                    TextColor(Yellow); Write(Y);
                    TextColor(LightGray); Write(', column = ');
                    TextColor(Yellow); Write(X);
                    TextColor(LightGray); Writeln('. ');
                    OldX := X; OldY := Y;
                end;
            until (GetMouseButtons = MouseLeftButton) or (GetMouseButtons = MouseRightButton);
            X := GetMouseX; Y := GetMouseY;
            Writeln; Writeln('Mouse stopped at screen position: ', Y, ', ', X, '.');
            Writeln; CursorOn;
        end;
        DoneMouse;
    end.

The screenshot shows the program output during execution.

Free Pascal mouse support on DOS: Mouse movement position

An undocumented problem with the usage of the Mouse unit is that (at least it seems so...) it takes some time before the mouse status changes. This was no issue in our sample programs, where we had to deal with a single mouse click. It becomes a real issue if within an outer loop, we test the GetMouseButtons result and do something when the function return suggests that a button has been pushed. There are chances that one single mouse click will result in a non-zero function result for all iteration steps of the loop, and if the whole proceeds without any keyboard input, the only steps visible to the user will be the first one (where the program waits for the user to push the mouse button) and the last one (the loop being finished). The solution of this problem is quite simple: Wait for the mouse button to be pushed, read the mouse position (if you need it) and then, before continuing, wait for the mouse button to be "no more pushed". Here is the code that works well in my EUROQUIZ.PAS program (described below):

    repeat
    until GetMouseButtons = MouseLeftButton;
    X := GetMouseX; Y := GetMouseY;
    repeat
    until GetMouseButtons <> MouseLeftButton;

EUROQUIZ.PAS is fully functional example of a Free Pascal DOS program with mouse support. It's a yes/no quiz about the capitals of the European countries, where the user answers to the quiz questions, as well as the request for a new question, are made by clicking with the mouse on the corresponding "buttons" (actually a colored text area). Here is the code (click the following link to download source + executable):

    program euroquiz;
    uses
        Crt, Mouse;
    const
        Countries: array[0..47] of string = (
            'Albania', 'Andorra', 'Armenia', 'Austria', 'Azerbaijan', 'Belarus', 'Belgium',
            'Bosnia and Herzegovina', 'Bulgaria', 'Croatia', 'Cyprus', 'Czech Republic',
            'Denmark', 'Estonia', 'Finland', 'France', 'Georgia', 'Germany', 'Greece',
            'Holy See', 'Hungary', 'Iceland', 'Ireland', 'Italy', 'Kazakhstan', 'Latvia',
            'Liechtenstein', 'Lithuania', 'Luxembourg', 'Malta', 'Moldova', 'Monaco',
            'Montenegro', 'Netherlands', 'North Macedonia', 'Norway', 'Poland', 'Portugal',
            'Romania', 'San Marino', 'Serbia', 'Slovakia', 'Slovenia',
            'Spain', 'Sweden', 'Switzerland', 'Ukraine', 'United Kingdom'
        );
        Capitals: array[0..47] of string = (
            'Tirana', 'Andorra la Vella', 'Yerevan', 'Vienna', 'Baku', 'Minsk', 'Brussels',
            'Sarajevo', 'Sofia', 'Zagreb', 'Nicosia', 'Prague',
            'Copenhagen', 'Tallinn', 'Helsinki', 'Paris', 'Tbilisi', 'Berlin', 'Athens',
            'Vatican City', 'Budapest', 'Reykjavik', 'Dublin', 'Rome', 'Nur-Sultan', 'Riga',
            'Vaduz', 'Vilnius', 'Luxembourg', 'Valletta', 'Chisinau', 'Monaco',
            'Podgorica', 'Amsterdam', 'Skopje', 'Oslo', 'Warsaw', 'Lisbon',
            'Bucharest', 'City of San Marino', 'Belgrade', 'Bratislava', 'Ljubljana',
            'Madrid', 'Stockholm', 'Berne', 'Kyiv', 'London'
        );
    var
        Q, C, R, M, N, I: Integer;
        Buttons: Byte;
        X, Y: Word;
        Country, Capital: string;
        ButtonPushed, PExit: Boolean;
        CountriesDone, CapitalsDone: array[0..46] of Boolean;
    begin
        ClrScr; CursorOff;
        TextColor(Yellow);
        Writeln('European capitals quiz.');
        Writeln('=======================');
        Writeln;
        TextColor(LightGray);
        Randomize;
        InitMouse;
        Buttons := DetectMouse;
        if Buttons = 0 then
            Writeln('Error: No mouse found!')
        else begin
            ShowMouse;
            GotoXY(1, 6); Write('Answer:   ');
            TextColor(Yellow);
            TextBackground(Blue); Write('Yes');
            TextBackground(Black); Write('   ');
            TextBackground(Blue); Write('No');
            TextBackground(Black); Write('   ');
            TextBackground(Blue); Write('Next');
            TextBackground(Black); Write('   ');
            TextBackground(Blue); Write('Exit');
            TextBackground(Black);
            TextColor(LightGray);
            Q := 0; C := 0; PExit := False;
            for I := 0 to 46 do begin
                CountriesDone[I] := False; CapitalsDone[I] := False;
            end;
            while (Q < 20) and (not PExit) do begin
                Inc(Q);
                repeat
                    M := Random(46);
                until not CountriesDone[M];
                Country := Countries[M]; CountriesDone[M] := True;
                R := Random(2);
                if R = 0 then begin
                    if CapitalsDone[M] then
                        R := 1
                    else
                        Capital := Capitals[M];
                end;
                if R = 1 then begin
                    repeat
                        N := Random(46);
                    until (N <> M) and (not CapitalsDone[N]);
                    Capital := Capitals[N]; CapitalsDone[N] := True;
                end;
                GotoXY(1, 4); ClrEoL;
                Write('Question ', Q, ': The capital of ', Country, ' is ', Capital, '?');
                GotoXY(1, 8); ClrEoL;
                ButtonPushed := False;
                repeat
                    repeat
                    until GetMouseButtons = MouseLeftButton;
                    X := GetMouseX; Y := GetMouseY;
                    repeat
                    until GetMouseButtons <> MouseLeftButton;
                    if (Y = 6) and (X in [29..32]) then begin
                        ButtonPushed := True; PExit := True;
                    end
                    else if (Y = 6) and (X in [11..13, 17..18]) then begin
                        ButtonPushed := True;
                        GotoXY(1, 8); Write('This answer is ');
                        if X in [11..14] then begin
                            if R = 0 then begin
                                TextColor(LightGreen); Write('correct!');
                                Inc(C);
                            end
                            else begin
                                TextColor(LightRed); Write('false!');
                            end;
                        end
                        else begin
                            if R = 1 then begin
                                TextColor(LightGreen); Write('correct!');
                                Inc(C);
                            end
                            else begin
                                TextColor(LightRed); Write('false!');
                            end;
                        end;
                        TextColor(LightGray);
                        if Q < 20 then begin
                            repeat
                                repeat
                                until GetMouseButtons = MouseLeftButton;
                                X := GetMouseX; Y := GetMouseY;
                                repeat
                                until GetMouseButtons <> MouseLeftButton;
                            until (Y = 6) and (X in [22..25, 29..32]);
                            if X in [29..32] then
                                PExit := True;
                        end;
                    end;
                until ButtonPushed;
            end;
        end;
        DoneMouse;
        GotoXY(1, 10);
        if not PExit then begin
            Writeln('You got ', C, ' correct answers out of 20.');
            Writeln('That is a success rate of ', 100*(C/Q):0:2, '%.');
            Writeln;
        end;
        CursorOn;
    end.

The number of questions is set to 20; each country is only asked once. The number of "yes" answers and "no" answers is more or less the same. After the question has been displayed, the program waits for the left mouse button to be pushed. If, for the click, the mouse position was above one of the answer buttons ("Yes", and "No"), the answer is evaluated. Unless all questions have been done, the program than waits for a new mouse click (push of the "Next" button). Pushing the "Exit" button at any moment, immediately terminates the program. Clicking outside the buttons, or onto an inappropriate button (e.g. onto the "Next" button when an answer is waited for) has no effect. When all questions are done, the success result is displayed.

The screenshot shows the program output.

Free Pascal mouse support on DOS: European capitals yes/no quiz

If you find this text helpful, please, support me and this website by signing my guestbook.