I think the simplest solution would be to use only two arrow keys (say Left and Right) in combination with two modifier keys (say Shift for Up and Ctrl for Down).
This means you can read all the information from a single KeyDown call.
However, if you want to try to intercept successive keystrokes, the following unit (to my surprise) works most of the time. Its accuracy does depend on the keyboard, and the typist's skill, and not having a fast keyboard repeat rate.
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, Forms, LazLogger;
const
ArrowKeys: set of Byte = [37, 38, 39, 40];
Diagonals: set of Byte = [0, 74, 76, 154];
type
TDiagonal = (dNone, dUpLeft, dUpRight, dDownLeft, dDownRight);
TForm1 = class(TForm)
private
arr: array[1..2] of Byte;
even: Boolean;
function AddedDiagonalKey(aKey: Word; out diagonal: TDiagonal): Boolean;
protected
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
end;
function DiagonalToStr(aDiagonal: TDiagonal): String;
var
Form1: TForm1;
implementation
function DiagonalToStr(aDiagonal: TDiagonal): String;
begin
WriteStr(Result, aDiagonal);
Delete(Result, 1, 1);
end;
{$R *.lfm}
{ TForm1 }
function TForm1.AddedDiagonalKey(aKey: Word; out diagonal: TDiagonal): Boolean;
var
tmp: Int64;
begin
if aKey > High(Byte) then
begin
Diagonal := dNone;
Exit(False);
end;
if even then arr[2] := Byte(aKey)
else arr[1] := Byte(aKey);
even := not even;
tmp := arr[1] * arr[2];
Dec(tmp, 1406);
Result := (arr[1] <> arr[2]) and (arr[1] in ArrowKeys) and (arr[2] in ArrowKeys) and
(Byte(tmp) in Diagonals);
case Result of
True: case tmp of
0: diagonal := dUpLeft;
74: diagonal := dDownLeft;
76: diagonal := dUpRight;
154: diagonal := dDownRight;
end;
False: diagonal := dNone;
end;
end;
procedure TForm1.KeyDown(var Key: Word; Shift: TShiftState);
var
diagonal: TDiagonal;
begin
inherited KeyDown(Key, Shift);
case AddedDiagonalKey(Key, diagonal) of
True: DebugLn(['diagonal key combination ',DiagonalToStr(diagonal)]);
False: DebugLn('non-diagonal key combination');
end;
end;
end.