Console vs GUI application in Object Pascal - is it either-or?

  •  Console vs GUI application paradigm
  • Console/terminal in POSIX vs in MS Windows
  • Two-in-one with FPC/Lazarus
  • The real magic

Console vs GUI application paradigm

In short, console application (or terminal mode application) is when you have a command line interface like in the MS-DOS time, or like shell in Linux. Or if you start cmd.exe on Windows. Application dedicated to this scenario prints characters to terminal (stdout) and reads input from the terminal (stdin) when you use standard object pascal routines like write/writeln and read/readln.

GUI application is one that produces a window, or many windows, with visual elements like buttons, text input fields, sliders, checkboxes, radio buttons, end so on. Well GUI is actually a generic term, and you can have GUI application in terminal, like for example, FreePascal has a text-mode IDE complete with text editor, and interactive dialog widnows (with buttons and text input) which all work in the terminal, and it works across platforms, on POSIX as well as in terminal on MS Windows:
whereas Lazarus IDE is an actual windowed IDE, which uses standard windows of any graphical window manager of a platform you run it on:
But commonly more often then not GUI application is an expression used to refer to a windowed application as opposed to text terminal mode application.

But I don't want you to think that there are only these two modes either. These are just two commonly used/talked modes, but there are types of applications that don't quite follow either of the two. For example OpenGL or SDL/SDL2 applications, which can run on a system with graphical environment and window manager, but do not render standard windows with standard visual controls, but rather expose direct access to a graphical device handle (a canvas) and allow for drawing directly into it. This is used widely in games. The graphical device on systems with window manager can be encapsulated within a managed window, or can go into full-screen mode, even with it's own resolution setting.  In this post, however, we focus the terminal mode applications and windowed applications.

Console/terminal in POSIX vs in MS Windows

Early versions of MS Windows were graphical desktop environments with windows and visual controls initiated atop of terminal mode of MS-DOS. That was more obviously the case with versions like Windows 3.11, and less obviously with versions like Windows 95/98. Current/modern versions of MS Windows do not depend on MS-DOS any more, and so you cannot opt for not jumping directly into the graphical session but staying in the terminal mode. Or can you??? Well, there is the mode referred to as Safe Mode, or Recovery Mode: How-To Geek: How to Boot Into Safe Mode on Windows 8 or 10.

But even that is not quite the same as MS-DOS.  But that's not the point. What I want to say is that in modern MS Windows, generally, you do not access pure terminal mode. Instead, when you need such a mode, you open a terminal emulator, which on Windows systems is called cmd.exe:
On Linux, if you are using graphical session, you also can start a terminal emulator, and there are many to choose from, unlike on Windows where there is generally just one default. On Ubuntu, the default terminal emulator is gnome-terminal:
Either way, I would like to point out that the differences are not just purely cosmetic. And I don't think that I am helping anyone reading this to discover that shell environment is different on Windows terminal than on POSIX systems either, I assume that this is something that I don't need to explain in this post. Instead I want to focus on two remarks:
  • on many POSIX systems, like on GNU/Linux, it is generally possible and often practised (ie on servers) to boot into pure terminal mode, without even initiating the graphical environment, without any window managers etc. Even more, on Linux it is a standard that you can switch between virtual terminals with your keyboard F1..F12 keys, where F1..F6 are all pure text mode terminals, and F7..F12 are for graphical sessions. This remark itself may seem not very important, but see the next one
  • on POSIX systems, generally, every time you run a program, it has assigned terminal, even if you don't see it. This is not the case on MS Windows. On Windows, generally, unless you start your windowed application from the command line of cmd.exe, it will start without assigned terminal/console. On Linux every windowed GUI application will always also have assigned terminal/console, just that most commonly it will not show up (unless you request it).
The later remark has very important implications. On MS Windows, differently than on POSIX systems, if you start windowed GUI application, by default it will have stdout and stdin streams/devices unavailable for writing/reading. This means, that at run-time any call to write/writeln or read/readln would raise an exception. On POSIX this is not the case, the stdin and stdout are generally always available, without any special pre-conditions. Your program even if it is a windowed program without any visible terminal window, will still be able to write to stdin, for example.

Next piece of the puzzle is that on MS Windows you also can achieve the same effect. You can explicitly request to attach terminal to a windowed application, using dedicated winapi calls. Also, there are dedicated calls for detecting whether or not your application has a terminal attached, so that you can test at run-time whether or not stdin/stdout are available and whether or not it is safe to call write/writeln and read/readln. This is VERY different from choosing your Object Pascal application to be either windowed or console type application with a compiler directive {$APPTYPE CONSOLE} as described here: http://wiki.freepascal.org/Console_Mode_Pascal
Rather, I am referring to actually detecting whether or not console with stdin and stdout is attached to your application and available, at the run-time. Has nothing to do with design-time configuration, and does not always have to do with how you start your application: by double clicking on it's icon, or from command line.

To detect presence of terminal attached to your application in Delphi or Lazarus, you can use this winapi calls:
 
  function HasTerminal: Boolean;
   var
     Stdout: THandle;
   begin
     Stdout := GetStdHandle(Std_Output_Handle);
     Win32Check(Stdout <> Invalid_Handle_Value);
     Result := Stdout <> 0;
   end;
 
And following winapi documentation is available via Microsoft Decumentation: GetStdHandle
Then there is the Win32Check function available in Delphi, which in case of Lazarus is also available for Windows platform by SysUtils unit.

If you detect that you do not have console attached, but you need one, there is winapi for that as well: AllocConsole, and you can use it like this:

  program Project2;

  uses
    Forms,
    SysUtils,
    Windows,
    Unit1 in 'Unit1.pas' {Form1};

  begin
    Application.Initialize;
    Application.CreateForm(TForm1, Form1);
    AllocConsole;
    writeln('Hello, World!');
    Application.Run;
  end.

These are basics and I encourage you to read up more about it, but this little information should suffice for the purpose of this post.

Two-in-one with FPC/Lazarus

First a DISCLAIMER: I have only tested this on Linux, and while I am pretty sure it will also work for any POSIX platform, I have not tested this on MS Windows, because I would need to access MS Windows computer or vm somewhere, and I don't have any around. One thing though, that you do most certainly need to take care of in addition to what I am showing here, if on Windows, is to test the presence of console. And if it is not attached to your application, either exit, or attach it at run-time. If you don't understand this disclaimer, than it is because you did not read the previous chapter ;)

So my environment in Ubuntu Linux. In Lazarus IDE, I have created a standard, new windowed application Project:
Then I just only created one button  and one TOpenDialog on the main form like this:
This demo is overly simplistic: when you press the button, a dialog pops up to let you select a file from your local filesystem, and then the program will use ShowMessage to pop an information about the size of the file you selected:
At this stage, I will start adopting this program to work from command line, the least I should think of doing is to allow for the file to be specified from the command line instead of via a dialog window:
Now, it seems all good and you'd think that does the trick but look what happens if I try to use this program, first in the GUI (windowed) mode:
No problem here, so now I try the same from the command line from terminal:
It worked? Well, sort of, but not really. Firstly, the way I implemented ParamStr(1) portion of the code is stupid, in the way that even despite passing the file via command line, the program still will wait for me to click on the button before it shows result, because only in the OnClick event the code will get executed. But that is just me being tired because it is late night time now that I am writing this sample code, and it is very easy thing to fix. Suppose I move the code to Form's OnCreate, right? Well, no. First let's change the code anyways:
So seems OK, or doesn't it? Well, if you actually test it on your computer, you will notice that the application's main form shortly appears on the screen only to disappear immediately afterwards. Because even though we support command line parameter, and even despite printing information to the stdout, the application still is a windowed one. This would be much bigger problem if you tried to run it form a pure shell not from a terminal emulator inside a graphical session, see me switching over to TTY at Alt+F1 and also testing same program:
I took the photo of my laptop with my phone, because in the Alt+F1 terminal the screenshot app from my graphical session does not work, obviously. So can you see it? The exception crashes the app before it even reaches the relevant code. This is because the application fails to initiate, because you cannot run windowed application in pure shell. And I am not talking about hacks like virtual graphical session via Xvfb and such.

So essentially, what I am about to show you in this post is how to let the application to decide at run-time whether or not it is going to be a pure shell mode (text terminal) application, or a windowed application. Yes, you read that right! You see, Lazarus implements windowed application in an interesting way, it is not linked against any fixed widgetset. On Linux, by default it links against GTK+ widgetset, but you can change it to use something else, like qt4 or qt5. On Other platforms it will link against other widgetset. On Windows it links against windows native widgetset which are native Windows controls. All of this linking part happens at run-time, and is triggered in the initialization part of the relevant units. If you were to execute code prior to those parts, you could then detect your scenario, and if in text terminal, you could run alternative code and exit the application before it even reaches the code related to the widgetset! So where to put our code? Let's see the project main source code, the very first file that compiler reads, where the program begins, in Lazarus IDE just navigate the system menu to Project>Project Options...:
Just reading comment line next to Interfaces unit on the uses list, you see that it is where all the LCL initialization happens. So we should execute our code before that. Perhaps we actually want our text mode alternative program version to be multi-threaded, so we can leave cthreads unit as first, and add our custom unit right after it, let's call it terminalmode.pas, so the only thing we change in the main project source file is that we add one unit in the uses list:

and let it implement our logic as a function that returns true if the program should prevent widgetset from loading, or false otherwise:

The magic is in the initialization part, please let it sink ;) Basically, if we detect that we don't want to let the application to initialize windowed mode widgetset, we terminate (using halt) the program from unit's initialization section, which will run before the LCL units have chance to run their initialization section(s). And there  will be no exceptions raised, because we will prevent the program from ever starting any graphical session related bits, but also after we run our alternative program dedicated for the text terminal mode. And which "version" of our program will run is decided at run-time based on a command line switch. On MS Windows you may use different criteria, ie, use winapi to detect whether or not console is attached.
And it just works! see what happens when I recompile this changed program and run it again from my Alt+F1 TTY:

You see, no exception, program executes the portion for the text mode terminal, and ends without the GUI part kicking in at all, in a pure shell TTY, a headless one! To show this even more obviously see me now executing the very same program from the terminal emulator in the graphical session first with and then without command switch:

Let it sink ;) The same executable, depending on whether or not file name was passed from command line, decides at run-time if to run as text mode terminal application or as GUI application for windowed mode in a graphical session!!!
And just if you haven't figured it out yet, the code previously placed in the main form's OnCreate event is now obsolete, you can remove it. Well, move it, to the terminalmode.pas unit, like I did.

The real magic

Now this last chapter is to completely blow your mind, even if you already felt like you got your mind blown before this moment :D

I will now make an example of something I use a lot on Linux, especially in my open source project ATU that runs on Linux across desktops, single board computers (like raspberry pi) and even phones/tablets (Ubuntu Touch, now UBports). When I need to run some logic that requires root from a GUI application which runs as a regular user, this is what I do:
  1. I enclose the logic that needs to run as root in a function, which also internally uses Linux's whoami command (via Process.RunCommand routine) to check if  the app is running as root or not
  2. I use the technique from the previous chapter to assign that logic to a specific command line switch
  3. in the main program logic for the windowed GUI scenario, I use RunCommand again, so that my application executes itself with the dedicated command line switch via gksudo, and BOOM!
Let it sink, if you need ;) Just in case you didn't understand the magic, let me offer you a hint. The Application starts as regular user, and when time comes, it uses RunCommand like this:

//(...)
  if not Process.RunCommand('gksudo',[Application.ExeName,'--root-logic'],s)then
    begin
       ShowMessage('your custom error message');
       exit;
     end;
//here you can parse s variable which will hold output of your root routine !!! 
//(...)

Where "--root-logic" is whatever command line switch you chose to install your text mode terminal version of your program under. I love this approach because:

  1. The very same executable can be used from bash or on a headless server, as well as it can be used as windowed GUI
  2. the application while in windowed mode can call text mode version of itself to handle additional logic, which allows me to bundle both the calling GUI wrapper and the called command line tool as one and the same executable!
  3. I can bundle both the logic for non-root user and one for root-user in one single executable and create countless scenarios at run-time!
  4. I can target desktop users and server power users simultaneously with just one executable, one code, one application!
 If you found this inspiring, please share this with others and/or leave me a comment!

Comments

Post a Comment

Popular posts from this blog

Lazarus IDE on ARM Ubuntu (Raspberry Pi, Ubuntu Touch, etc)

SSH and Ubuntu Touch - everything you want to ask but are afraid to ask

Desktop Apps on Ubuntu Phone