1023 lines
28 KiB
ObjectPascal
1023 lines
28 KiB
ObjectPascal
unit FSXSimConnectClient;
|
|
|
|
// Determines if a Win32 event will be used to wait for new
|
|
// messages instead of the old 0.x method of polling via a timer.
|
|
{$DEFINE SCUSEEVENT}
|
|
|
|
interface
|
|
uses
|
|
Classes,
|
|
|
|
OtlTaskControl,
|
|
X2Log.Intf,
|
|
|
|
FSXSimConnectIntf,
|
|
Profile,
|
|
ProfileManager;
|
|
|
|
|
|
type
|
|
TFSXSimConnectInterface = class(TInterfacedObject, IFSXSimConnect, IFSXSimConnectProfileMenu, IProfileObserver)
|
|
private
|
|
FClient: IOmniTaskControl;
|
|
FObservers: TInterfaceList;
|
|
|
|
FObservingProfileManager: Boolean;
|
|
protected
|
|
property Client: IOmniTaskControl read FClient;
|
|
property Observers: TInterfaceList read FObservers;
|
|
protected
|
|
{ IFSXSimConnect }
|
|
procedure Attach(AObserver: IFSXSimConnectObserver);
|
|
procedure Detach(AObserver: IFSXSimConnectObserver);
|
|
|
|
function CreateDefinition: IFSXSimConnectDefinition;
|
|
function AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer;
|
|
procedure RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler);
|
|
|
|
{ IFSXSimConnectProfileMenu }
|
|
procedure SetProfileMenu(AEnabled, ACascaded: Boolean);
|
|
|
|
{ IProfileObserver }
|
|
procedure ObserveAdd(AProfile: TProfile);
|
|
procedure ObserveRemove(AProfile: TProfile);
|
|
procedure ObserveActiveChanged(AProfile: TProfile);
|
|
public
|
|
constructor Create(ALog: IX2Log);
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
uses
|
|
Generics.Collections,
|
|
System.Math,
|
|
System.SyncObjs,
|
|
System.SysUtils,
|
|
Winapi.Windows,
|
|
|
|
OtlComm,
|
|
OtlCommon,
|
|
SimConnect,
|
|
X2UtApp,
|
|
|
|
ControlIntf,
|
|
FSXResources,
|
|
FSXSimConnectStateMonitor;
|
|
|
|
|
|
const
|
|
TM_ADDDEFINITION = 3001;
|
|
TM_REMOVEDEFINITION = 3002;
|
|
TM_TRYSIMCONNECT = 3003;
|
|
TM_PROCESSMESSAGES = 3004;
|
|
TM_SETPROFILEMENU = 3005;
|
|
TM_UPDATEPROFILEMENU = 3006;
|
|
|
|
TIMER_TRYSIMCONNECT = 201;
|
|
INTERVAL_TRYSIMCONNECT = 5000;
|
|
|
|
{$IFNDEF SCUSEEVENT}
|
|
TIMER_PROCESSMESSAGES = 202;
|
|
INTERVAL_PROCESSMESSAGES = 50;
|
|
{$ENDIF}
|
|
|
|
MENU_RESTART = 65536;
|
|
|
|
|
|
type
|
|
TFSXSimConnectDefinitionRef = class(TObject)
|
|
private
|
|
FDefinition: IFSXSimConnectDefinition;
|
|
FDataHandlers: TInterfaceList;
|
|
protected
|
|
property DataHandlers: TInterfaceList read FDataHandlers;
|
|
public
|
|
constructor Create(ADefinition: IFSXSimConnectDefinition);
|
|
destructor Destroy; override;
|
|
|
|
function Attach(ADataHandler: IFSXSimConnectDataHandler): Integer;
|
|
function Detach(ADataHandler: IFSXSimConnectDataHandler): Integer;
|
|
|
|
procedure HandleData(AData: Pointer);
|
|
|
|
property Definition: IFSXSimConnectDefinition read FDefinition;
|
|
end;
|
|
|
|
|
|
TFSXSimConnectDefinitionMap = class(TObjectDictionary<Cardinal, TFSXSimConnectDefinitionRef>)
|
|
public
|
|
constructor Create(ACapacity: Integer = 0); reintroduce;
|
|
end;
|
|
|
|
TFSXSimConnectClient = class(TOmniWorker)
|
|
private
|
|
FDefinitions: TFSXSimConnectDefinitionMap;
|
|
FLastDefinitionID: Cardinal;
|
|
FSimConnectHandle: THandle;
|
|
{$IFDEF SCUSEEVENT}
|
|
FSimConnectDataEvent: TEvent;
|
|
{$ENDIF}
|
|
|
|
FProfileMenu: Boolean;
|
|
FProfileMenuCascaded: Boolean;
|
|
|
|
FMenuProfiles: TStringList;
|
|
FMenuWasCascaded: Boolean;
|
|
FLog: IX2Log;
|
|
protected
|
|
procedure TMAddDefinition(var Msg: TOmniMessage); message TM_ADDDEFINITION;
|
|
procedure TMRemoveDefinition(var Msg: TOmniMessage); message TM_REMOVEDEFINITION;
|
|
procedure TMTrySimConnect(var Msg: TOmniMessage); message TM_TRYSIMCONNECT;
|
|
{$IFNDEF SCUSEEVENT}
|
|
procedure TMProcessMessages(var Msg: TOmniMessage); message TM_PROCESSMESSAGES;
|
|
{$ENDIF}
|
|
procedure TMSetProfileMenu(var Msg: TOmniMessage); message TM_SETPROFILEMENU;
|
|
procedure TMUpdateProfileMenu(var Msg: TOmniMessage); message TM_UPDATEPROFILEMENU;
|
|
|
|
procedure HandleSimConnectDataEvent;
|
|
procedure HandleEvent(AEventID: Integer);
|
|
protected
|
|
function Initialize: Boolean; override;
|
|
procedure Cleanup; override;
|
|
|
|
procedure TrySimConnect; overload;
|
|
procedure TrySimConnect(const ALibraryName: string); overload;
|
|
|
|
procedure RegisterDefinitions;
|
|
procedure RegisterDefinition(ADefinitionID: Cardinal; ADefinition: IFSXSimConnectDefinition);
|
|
procedure UpdateDefinition(ADefinitionID: Cardinal);
|
|
procedure UnregisterDefinition(ADefinitionID: Cardinal);
|
|
|
|
function SameDefinition(ADefinition1, ADefinition2: IFSXSimConnectDefinition): Boolean;
|
|
|
|
procedure UpdateProfileMenu;
|
|
|
|
property Definitions: TFSXSimConnectDefinitionMap read FDefinitions;
|
|
property LastDefinitionID: Cardinal read FLastDefinitionID;
|
|
property SimConnectHandle: THandle read FSimConnectHandle;
|
|
{$IFDEF SCUSEEVENT}
|
|
property SimConnectDataEvent: TEvent read FSimConnectDataEvent;
|
|
{$ENDIF}
|
|
|
|
property Log: IX2Log read FLog;
|
|
property ProfileMenu: Boolean read FProfileMenu;
|
|
property ProfileMenuCascaded: Boolean read FProfileMenuCascaded;
|
|
public
|
|
constructor Create(ALog: IX2Log);
|
|
end;
|
|
|
|
|
|
TFSXSimConnectVariable = class(TInterfacedPersistent, IFSXSimConnectVariable)
|
|
private
|
|
FVariableName: string;
|
|
FUnitsName: string;
|
|
FDataType: SIMCONNECT_DATAType;
|
|
FEpsilon: Single;
|
|
protected
|
|
{ IFSXSimConnectVariable }
|
|
function GetVariableName: string;
|
|
function GetUnitsName: string;
|
|
function GetDataType: SIMCONNECT_DATAType;
|
|
function GetEpsilon: Single;
|
|
public
|
|
constructor Create(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single);
|
|
end;
|
|
|
|
|
|
TFSXSimConnectVariableList = TObjectList<TFSXSimConnectVariable>;
|
|
|
|
TFSXSimConnectDefinition = class(TInterfacedObject, IFSXSimConnectDefinition)
|
|
private
|
|
FSimConnect: IFSXSimConnect;
|
|
FVariables: TFSXSimConnectVariableList;
|
|
protected
|
|
property SimConnect: IFSXSimConnect read FSimConnect;
|
|
property Variables: TFSXSimConnectVariableList read FVariables;
|
|
protected
|
|
{ IFSXSimConnectDefinition }
|
|
procedure AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single = 0);
|
|
|
|
function GetVariableCount: Integer;
|
|
function GetVariable(AIndex: Integer): IFSXSimConnectVariable;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
|
|
|
|
TAddDefinitionValue = class(TOmniWaitableValue)
|
|
private
|
|
FDataHandler: IFSXSimConnectDataHandler;
|
|
FDefinition: IFSXSimConnectDefinition;
|
|
FDefinitionID: Cardinal;
|
|
|
|
procedure SetDefinitionID(const Value: Cardinal);
|
|
public
|
|
constructor Create(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler);
|
|
|
|
property DataHandler: IFSXSimConnectDataHandler read FDataHandler;
|
|
property Definition: IFSXSimConnectDefinition read FDefinition;
|
|
|
|
property DefinitionID: Cardinal read FDefinitionID write SetDefinitionID;
|
|
end;
|
|
|
|
|
|
TRemoveDefinitionValue = class(TOmniWaitableValue)
|
|
private
|
|
FDataHandler: IFSXSimConnectDataHandler;
|
|
FDefinitionID: Cardinal;
|
|
public
|
|
constructor Create(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler);
|
|
|
|
property DataHandler: IFSXSimConnectDataHandler read FDataHandler;
|
|
property DefinitionID: Cardinal read FDefinitionID;
|
|
end;
|
|
|
|
|
|
{ TFSXSimConnectInterface }
|
|
constructor TFSXSimConnectInterface.Create(ALog: IX2Log);
|
|
var
|
|
worker: IOmniWorker;
|
|
|
|
begin
|
|
inherited Create;
|
|
|
|
FObservers := TInterfaceList.Create;
|
|
|
|
worker := TFSXSimConnectClient.Create(ALog);
|
|
FClient := CreateTask(worker).Run;
|
|
end;
|
|
|
|
|
|
destructor TFSXSimConnectInterface.Destroy;
|
|
var
|
|
observer: IInterface;
|
|
|
|
begin
|
|
for observer in Observers do
|
|
(observer as IFSXSimConnectObserver).ObserveDestroy(Self);
|
|
|
|
FreeAndNil(FObservers);
|
|
|
|
FClient.Terminate;
|
|
FClient := nil;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectInterface.Attach(AObserver: IFSXSimConnectObserver);
|
|
begin
|
|
Observers.Add(AObserver as IFSXSimConnectObserver);
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectInterface.Detach(AObserver: IFSXSimConnectObserver);
|
|
begin
|
|
Observers.Remove(AObserver as IFSXSimConnectObserver);
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectInterface.CreateDefinition: IFSXSimConnectDefinition;
|
|
begin
|
|
Result := TFSXSimConnectDefinition.Create;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectInterface.AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer;
|
|
var
|
|
addDefinition: TAddDefinitionValue;
|
|
|
|
begin
|
|
addDefinition := TAddDefinitionValue.Create(ADefinition, ADataHandler);
|
|
Client.Comm.Send(TM_ADDDEFINITION, addDefinition);
|
|
|
|
addDefinition.WaitFor(INFINITE);
|
|
Result := addDefinition.DefinitionID;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectInterface.RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler);
|
|
var
|
|
removeDefinition: TRemoveDefinitionValue;
|
|
|
|
begin
|
|
removeDefinition := TRemoveDefinitionValue.Create(ADefinitionID, ADataHandler);
|
|
Client.Comm.Send(TM_REMOVEDEFINITION, removeDefinition);
|
|
|
|
removeDefinition.WaitFor(INFINITE);
|
|
end;
|
|
|
|
|
|
|
|
procedure TFSXSimConnectInterface.SetProfileMenu(AEnabled, ACascaded: Boolean);
|
|
begin
|
|
Client.Comm.Send(TM_SETPROFILEMENU, [AEnabled, ACascaded]);
|
|
|
|
if AEnabled <> FObservingProfileManager then
|
|
begin
|
|
if AEnabled then
|
|
TProfileManager.Attach(Self)
|
|
else
|
|
TProfileManager.Detach(Self);
|
|
|
|
FObservingProfileManager := AEnabled;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectInterface.ObserveAdd(AProfile: TProfile);
|
|
begin
|
|
Client.Comm.Send(TM_UPDATEPROFILEMENU);
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectInterface.ObserveRemove(AProfile: TProfile);
|
|
begin
|
|
Client.Comm.Send(TM_UPDATEPROFILEMENU);
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectInterface.ObserveActiveChanged(AProfile: TProfile);
|
|
begin
|
|
end;
|
|
|
|
|
|
|
|
{ TFSXSimConnectDefinition }
|
|
constructor TFSXSimConnectDefinition.Create;
|
|
begin
|
|
inherited Create;
|
|
|
|
FVariables := TFSXSimConnectVariableList.Create(True);
|
|
end;
|
|
|
|
|
|
destructor TFSXSimConnectDefinition.Destroy;
|
|
begin
|
|
FreeAndNil(FVariables);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectDefinition.AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single);
|
|
begin
|
|
Variables.Add(TFSXSimConnectVariable.Create(AVariableName, AUnitsName, ADataType, AEpsilon));
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectDefinition.GetVariable(AIndex: Integer): IFSXSimConnectVariable;
|
|
begin
|
|
Result := Variables[AIndex];
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectDefinition.GetVariableCount: Integer;
|
|
begin
|
|
Result := Variables.Count;
|
|
end;
|
|
|
|
|
|
{ TFSXSimConnectClient }
|
|
constructor TFSXSimConnectClient.Create(ALog: IX2Log);
|
|
begin
|
|
inherited Create;
|
|
|
|
FLog := ALog;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectClient.Initialize: Boolean;
|
|
begin
|
|
Log.Info('Initializing');
|
|
|
|
Result := inherited Initialize;
|
|
if not Result then
|
|
exit;
|
|
|
|
FDefinitions := TFSXSimConnectDefinitionMap.Create;
|
|
FMenuProfiles := TStringList.Create;
|
|
|
|
{$IFDEF SCUSEEVENT}
|
|
FSimConnectDataEvent := TEvent.Create(nil, False, False, '');
|
|
Task.RegisterWaitObject(SimConnectDataEvent.Handle, HandleSimConnectDataEvent);
|
|
{$ENDIF}
|
|
|
|
TrySimConnect;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.Cleanup;
|
|
begin
|
|
Log.Info('Cleaning up');
|
|
|
|
{$IFDEF SCUSEEVENT}
|
|
FreeAndNil(FSimConnectDataEvent);
|
|
{$ENDIF}
|
|
|
|
FreeAndNil(FMenuProfiles);
|
|
FreeAndNil(FDefinitions);
|
|
|
|
if SimConnectHandle <> 0 then
|
|
SimConnect_Close(SimConnectHandle);
|
|
|
|
TFSXSimConnectStateMonitor.SetCurrentState(scsDisconnected);
|
|
|
|
inherited Cleanup;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.TrySimConnect;
|
|
begin
|
|
if SimConnectHandle <> 0 then
|
|
exit;
|
|
|
|
TFSXSimConnectStateMonitor.SetCurrentState(scsConnecting);
|
|
|
|
TrySimConnect('FSX-SimConnect.dll');
|
|
if SimConnectHandle = 0 then
|
|
TrySimConnect('FSXSP2-SimConnect.dll');
|
|
if SimConnectHandle = 0 then
|
|
TrySimConnect('FSX-SE-SimConnect.dll');
|
|
|
|
if SimConnectHandle = 0 then
|
|
begin
|
|
Log.Info(Format('FSX SimConnect: Connection failed, trying again in %d seconds', [INTERVAL_TRYSIMCONNECT div 1000]));
|
|
TFSXSimConnectStateMonitor.SetCurrentState(scsFailed);
|
|
|
|
Task.SetTimer(TIMER_TRYSIMCONNECT, INTERVAL_TRYSIMCONNECT, TM_TRYSIMCONNECT);
|
|
{$IFNDEF SCUSEEVENT}
|
|
Task.ClearTimer(TIMER_PROCESSMESSAGES);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.TrySimConnect(const ALibraryName: string);
|
|
var
|
|
eventHandle: THandle;
|
|
|
|
begin
|
|
Log.Info('Attempting to connect to SimConnect using ' + ALibraryName);
|
|
|
|
if InitSimConnectFromLibrary(App.Path + ALibraryName) then
|
|
begin
|
|
{$IFDEF SCUSEEVENT}
|
|
eventHandle := SimConnectDataEvent.Handle;
|
|
{$ELSE}
|
|
eventHandle := 0;
|
|
{$ENDIF}
|
|
|
|
if SimConnect_Open(FSimConnectHandle, FSXSimConnectAppName, 0, 0, eventHandle, 0) = S_OK then
|
|
begin
|
|
Log.Info('Succesfully connected');
|
|
TFSXSimConnectStateMonitor.SetCurrentState(scsConnected);
|
|
|
|
{ Attempt to discover if it's FSX or Prepar3D }
|
|
if WaitNamedPipe('\\.\pipe\Lockheed Martin Prepar3D v2\SimConnect', 0) or
|
|
WaitNamedPipe('\\.\pipe\Lockheed Martin Prepar3D v3\SimConnect', 0) or
|
|
WaitNamedPipe('\\.\pipe\Lockheed Martin Prepar3D v4\SimConnect', 0) then
|
|
TFSXSimConnectStateMonitor.SetSimulator(scsPrepar3D)
|
|
else
|
|
TFSXSimConnectStateMonitor.SetSimulator(scsFSX);
|
|
|
|
Task.ClearTimer(TIMER_TRYSIMCONNECT);
|
|
RegisterDefinitions;
|
|
UpdateProfileMenu;
|
|
|
|
{$IFNDEF SCUSEEVENT}
|
|
Task.SetTimer(TIMER_PROCESSMESSAGES, INTERVAL_PROCESSMESSAGES, TM_PROCESSMESSAGES);
|
|
{$ENDIF}
|
|
end else
|
|
FSimConnectHandle := 0;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.HandleSimConnectDataEvent;
|
|
const
|
|
RecvMessageName: array[SIMCONNECT_RECV_ID] of string =
|
|
(
|
|
'Null',
|
|
'Exception',
|
|
'Open',
|
|
'Quit',
|
|
'Event',
|
|
'Event Object Addremove',
|
|
'Event Filename',
|
|
'Event Frame',
|
|
'Simobject Data',
|
|
'Simobject Data Bytype',
|
|
'Weather Observation',
|
|
'Cloud State',
|
|
'Assigned Object Id',
|
|
'Reserved Key',
|
|
'Custom Action',
|
|
'System State',
|
|
'Client Data'
|
|
);
|
|
|
|
|
|
var
|
|
data: PSimConnectRecv;
|
|
dataSize: Cardinal;
|
|
simObjectData: PSimConnectRecvSimObjectData;
|
|
eventData: PSimConnectRecvEvent;
|
|
definitionRef: TFSXSimConnectDefinitionRef;
|
|
simObjectDataByType: PSimConnectRecvSimObjectDataByType;
|
|
|
|
begin
|
|
Log.Verbose('Handling messages');
|
|
|
|
while (SimConnectHandle <> 0) and
|
|
(SimConnect_GetNextDispatch(SimConnectHandle, data, dataSize) = S_OK) do
|
|
begin
|
|
case SIMCONNECT_RECV_ID(data^.dwID) of
|
|
SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
|
|
begin
|
|
simObjectData := PSimConnectRecvSimObjectData(data);
|
|
Log.Verbose(Format('Received Sim Object Data message (definition = %d)', [simObjectData^.dwDefineID]));
|
|
|
|
if Definitions.ContainsKey(simObjectData^.dwDefineID) then
|
|
begin
|
|
definitionRef := Definitions[simObjectData^.dwDefineID];
|
|
definitionRef.HandleData(@simObjectData^.dwData);
|
|
end;
|
|
end;
|
|
|
|
SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
|
|
begin
|
|
simObjectDataByType := PSimConnectRecvSimObjectDataByType(data);
|
|
Log.Verbose(Format('Received Sim Object Data By Type message (definition = %d)', [simObjectDataByType^.dwDefineID]));
|
|
|
|
if Definitions.ContainsKey(simObjectDataByType^.dwDefineID) then
|
|
begin
|
|
definitionRef := Definitions[simObjectDataByType^.dwDefineID];
|
|
definitionRef.HandleData(@simObjectDataByType^.dwData);
|
|
end;
|
|
end;
|
|
|
|
SIMCONNECT_RECV_ID_EVENT:
|
|
begin
|
|
eventData := PSimConnectRecvEvent(data);
|
|
Log.Verbose(Format('Received Event message (eventId = %d)', [eventData^.uEventID]));
|
|
|
|
HandleEvent(eventData^.uEventID);
|
|
end;
|
|
|
|
SIMCONNECT_RECV_ID_QUIT:
|
|
begin
|
|
Log.Verbose('Received Quit message');
|
|
|
|
FSimConnectHandle := 0;
|
|
{$IFNDEF SCUSEEVENT}
|
|
Task.ClearTimer(TIMER_PROCESSMESSAGES);
|
|
{$ENDIF}
|
|
Task.SetTimer(TIMER_TRYSIMCONNECT, INTERVAL_TRYSIMCONNECT, TM_TRYSIMCONNECT);
|
|
|
|
FMenuProfiles.Clear;
|
|
|
|
TFSXSimConnectStateMonitor.SetCurrentState(scsDisconnected);
|
|
end;
|
|
else
|
|
if SIMCONNECT_RECV_ID(data^.dwID) in [Low(SIMCONNECT_RECV_ID)..High(SIMCONNECT_RECV_ID)] then
|
|
Log.Verbose(Format('Received unhandled message (%s)', [RecvMessageName[SIMCONNECT_RECV_ID(data^.dwID)]]))
|
|
else
|
|
Log.Verbose(Format('Received unknown message (%d)', [data^.dwID]));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.HandleEvent(AEventID: Integer);
|
|
var
|
|
profileName: string;
|
|
profile: TProfile;
|
|
|
|
begin
|
|
if AEventID = MENU_RESTART then
|
|
begin
|
|
GetControlHandler.Restart;
|
|
exit;
|
|
end;
|
|
|
|
|
|
if (AEventID <= 0) or (AEventID > FMenuProfiles.Count) then
|
|
exit;
|
|
|
|
profileName := FMenuProfiles[Pred(AEventID)];
|
|
profile := TProfileManager.Find(profileName);
|
|
if Assigned(profile) then
|
|
TProfileManager.Instance.ActiveProfile := profile;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.RegisterDefinitions;
|
|
var
|
|
definitionID: Cardinal;
|
|
|
|
begin
|
|
if SimConnectHandle = 0 then
|
|
exit;
|
|
|
|
UpdateProfileMenu;
|
|
|
|
for definitionID in Definitions.Keys do
|
|
RegisterDefinition(definitionID, Definitions[definitionID].Definition);
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.RegisterDefinition(ADefinitionID: Cardinal; ADefinition: IFSXSimConnectDefinition);
|
|
var
|
|
variableIndex: Integer;
|
|
variable: IFSXSimConnectVariable;
|
|
|
|
begin
|
|
if SimConnectHandle = 0 then
|
|
exit;
|
|
|
|
Log.Verbose(Format('Registering definition %d', [ADefinitionID]));
|
|
|
|
for variableIndex := 0 to Pred(ADefinition.GetVariableCount) do
|
|
begin
|
|
variable := ADefinition.GetVariable(variableIndex);
|
|
SimConnect_AddToDataDefinition(SimConnectHandle, ADefinitionID,
|
|
AnsiString(variable.GetVariableName),
|
|
AnsiString(variable.GetUnitsName),
|
|
variable.GetDataType,
|
|
variable.GetEpsilon);
|
|
end;
|
|
|
|
SimConnect_RequestDataOnSimObject(SimConnectHandle, ADefinitionID, ADefinitionID,
|
|
SIMCONNECT_OBJECT_ID_USER,
|
|
SIMCONNECT_PERIOD_SIM_FRAME,
|
|
SIMCONNECT_DATA_REQUEST_FLAG_CHANGED);
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.UpdateDefinition(ADefinitionID: Cardinal);
|
|
begin
|
|
if SimConnectHandle <> 0 then
|
|
{ One-time data update; the RequestID is counted backwards to avoid conflicts with
|
|
the FLAG_CHANGED request which is still active }
|
|
SimConnect_RequestDataOnSimObject(SimConnectHandle, High(Cardinal) - ADefinitionID, ADefinitionID,
|
|
SIMCONNECT_OBJECT_ID_USER,
|
|
SIMCONNECT_PERIOD_SIM_FRAME,
|
|
0, 0, 0, 1);
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.UnregisterDefinition(ADefinitionID: Cardinal);
|
|
begin
|
|
if SimConnectHandle <> 0 then
|
|
begin
|
|
Log.Verbose(Format('Unregistering definition: %d', [ADefinitionID]));
|
|
SimConnect_ClearDataDefinition(SimConnectHandle, ADefinitionID);
|
|
end;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectClient.SameDefinition(ADefinition1, ADefinition2: IFSXSimConnectDefinition): Boolean;
|
|
var
|
|
variableIndex: Integer;
|
|
variable1: IFSXSimConnectVariable;
|
|
variable2: IFSXSimConnectVariable;
|
|
|
|
begin
|
|
if ADefinition1.GetVariableCount = ADefinition2.GetVariableCount then
|
|
begin
|
|
Result := True;
|
|
|
|
{ Order is very important in the definitions, as the Data Handler depends
|
|
on it to interpret the data. }
|
|
for variableIndex := 0 to Pred(ADefinition1.GetVariableCount) do
|
|
begin
|
|
variable1 := ADefinition1.GetVariable(variableIndex);
|
|
variable2 := ADefinition2.GetVariable(variableIndex);
|
|
|
|
if (variable1.GetVariableName <> variable2.GetVariableName) or
|
|
(variable1.GetUnitsName <> variable2.GetUnitsName) or
|
|
(variable1.GetDataType <> variable2.GetDataType) or
|
|
(not SameValue(variable1.GetEpsilon, variable2.GetEpsilon, 0.00001)) then
|
|
begin
|
|
Result := False;
|
|
break;
|
|
end;
|
|
end;
|
|
end else
|
|
Result := False;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.UpdateProfileMenu;
|
|
var
|
|
profile: TProfile;
|
|
profileIndex: Integer;
|
|
menuIndex: Integer;
|
|
profileName: string;
|
|
|
|
begin
|
|
if SimConnectHandle = 0 then
|
|
exit;
|
|
|
|
Log.Info('Updating profile menu');
|
|
|
|
if FMenuWasCascaded then
|
|
begin
|
|
for menuIndex := Pred(FMenuProfiles.Count) downto 0 do
|
|
SimConnect_MenuDeleteSubItem(SimConnectHandle, 1, Cardinal(FMenuProfiles.Objects[menuIndex]));
|
|
|
|
SimConnect_MenuDeleteItem(SimConnectHandle, 1);
|
|
SimConnect_MenuDeleteItem(SimConnectHandle, 2);
|
|
end else
|
|
begin
|
|
for menuIndex := Pred(FMenuProfiles.Count) downto 0 do
|
|
SimConnect_MenuDeleteItem(SimConnectHandle, Cardinal(FMenuProfiles.Objects[menuIndex]));
|
|
|
|
SimConnect_MenuDeleteItem(SimConnectHandle, MENU_RESTART);
|
|
end;
|
|
|
|
FMenuProfiles.Clear;
|
|
|
|
|
|
if ProfileMenu then
|
|
begin
|
|
for profile in TProfileManager.Instance do
|
|
FMenuProfiles.Add(profile.Name);
|
|
|
|
FMenuProfiles.Sort;
|
|
|
|
|
|
if ProfileMenuCascaded then
|
|
begin
|
|
SimConnect_MenuAddItem(SimConnectHandle, FSXMenuProfiles, 1, 0);
|
|
|
|
for profileIndex := 0 to Pred(FMenuProfiles.Count) do
|
|
begin
|
|
profileName := Format(FSXMenuProfileFormatCascaded, [FMenuProfiles[profileIndex]]);
|
|
|
|
SimConnect_MenuAddSubItem(SimConnectHandle, 1, PAnsiChar(AnsiString(profileName)), Succ(profileIndex), Succ(profileIndex));
|
|
FMenuProfiles.Objects[profileIndex] := TObject(Succ(profileIndex));
|
|
end;
|
|
|
|
SimConnect_MenuAddItem(SimConnectHandle, FSXMenuRestart, MENU_RESTART, 0);
|
|
end else
|
|
begin
|
|
for profileIndex := 0 to Pred(FMenuProfiles.Count) do
|
|
begin
|
|
profileName := Format(FSXMenuProfileFormat, [FMenuProfiles[profileIndex]]);
|
|
|
|
SimConnect_MenuAddItem(SimConnectHandle, PAnsiChar(AnsiString(profileName)), Succ(profileIndex), Succ(profileIndex));
|
|
FMenuProfiles.Objects[profileIndex] := TObject(Succ(profileIndex));
|
|
end;
|
|
|
|
SimConnect_MenuAddItem(SimConnectHandle, FSXMenuRestart, MENU_RESTART, 0);
|
|
end;
|
|
|
|
FMenuWasCascaded := ProfileMenuCascaded;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.TMAddDefinition(var Msg: TOmniMessage);
|
|
var
|
|
addDefinition: TAddDefinitionValue;
|
|
definitionID: Cardinal;
|
|
definitionRef: TFSXSimConnectDefinitionRef;
|
|
definitionAccess: IFSXSimConnectDefinition;
|
|
hasDefinition: Boolean;
|
|
refCount: Integer;
|
|
|
|
begin
|
|
addDefinition := Msg.MsgData;
|
|
definitionAccess := (addDefinition.Definition as IFSXSimConnectDefinition);
|
|
hasDefinition := False;
|
|
|
|
Log.Verbose('Received request to add a definition');
|
|
|
|
{ Attempt to re-use existing definition to save on SimConnect traffic }
|
|
for definitionID in Definitions.Keys do
|
|
begin
|
|
definitionRef := Definitions[definitionID];
|
|
|
|
if SameDefinition(definitionRef.Definition, definitionAccess) then
|
|
begin
|
|
refCount := definitionRef.Attach(addDefinition.DataHandler);
|
|
addDefinition.DefinitionID := definitionID;
|
|
|
|
Log.Verbose(Format('Definition exists, incremented reference count (definitionID = %d, refCount = %d)', [definitionID, refCount]));
|
|
|
|
|
|
{ Request an update on the definition to update the new worker }
|
|
UpdateDefinition(definitionID);
|
|
|
|
hasDefinition := True;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
if not hasDefinition then
|
|
begin
|
|
{ Add as new definition }
|
|
Inc(FLastDefinitionID);
|
|
Log.Verbose(Format('Adding as new definition (%d)', [FLastDefinitionID]));
|
|
|
|
definitionRef := TFSXSimConnectDefinitionRef.Create(definitionAccess);
|
|
definitionRef.Attach(addDefinition.DataHandler);
|
|
|
|
Definitions.Add(LastDefinitionID, definitionRef);
|
|
addDefinition.DefinitionID := LastDefinitionID;
|
|
|
|
{ Register with SimConnect }
|
|
RegisterDefinition(LastDefinitionID, definitionAccess);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.TMRemoveDefinition(var Msg: TOmniMessage);
|
|
var
|
|
removeDefinition: TRemoveDefinitionValue;
|
|
definitionRef: TFSXSimConnectDefinitionRef;
|
|
refCount: Integer;
|
|
|
|
begin
|
|
removeDefinition := Msg.MsgData;
|
|
Log.Verbose(Format('Received request to remove a definition (%d)', [removeDefinition.DefinitionID]));
|
|
|
|
if Definitions.ContainsKey(removeDefinition.DefinitionID) then
|
|
begin
|
|
definitionRef := Definitions[removeDefinition.DefinitionID];
|
|
refCount := definitionRef.Detach(removeDefinition.DataHandler);
|
|
|
|
Log.Verbose(Format('Definition exists, decreased reference count (refCount = %d)', [refCount]));
|
|
|
|
if refCount = 0 then
|
|
begin
|
|
Log.Verbose('Removing definition');
|
|
|
|
{ Unregister with SimConnect }
|
|
UnregisterDefinition(removeDefinition.DefinitionID);
|
|
|
|
Definitions.Remove(removeDefinition.DefinitionID);
|
|
end;
|
|
end;
|
|
|
|
removeDefinition.Signal;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.TMTrySimConnect(var Msg: TOmniMessage);
|
|
begin
|
|
TrySimConnect;
|
|
end;
|
|
|
|
|
|
{$IFNDEF SCUSEEVENT}
|
|
procedure TFSXSimConnectClient.TMProcessMessages(var Msg: TOmniMessage);
|
|
begin
|
|
HandleSimConnectDataEvent;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
|
|
procedure TFSXSimConnectClient.TMSetProfileMenu(var Msg: TOmniMessage);
|
|
var
|
|
newProfileMenu: Boolean;
|
|
newProfileMenuCascaded: Boolean;
|
|
|
|
begin
|
|
newProfileMenu := Msg.MsgData[0];
|
|
newProfileMenuCascaded := Msg.MsgData[1];
|
|
|
|
if (newProfileMenu <> FProfileMenu) or (newProfileMenuCascaded <> FProfileMenuCascaded) then
|
|
begin
|
|
FProfileMenu := newProfileMenu;
|
|
FProfileMenuCascaded := newProfileMenuCascaded;
|
|
|
|
UpdateProfileMenu;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectClient.TMUpdateProfileMenu(var Msg: TOmniMessage);
|
|
begin
|
|
UpdateProfileMenu;
|
|
end;
|
|
|
|
|
|
{ TFSXSimConnectDefinitionRef }
|
|
constructor TFSXSimConnectDefinitionRef.Create(ADefinition: IFSXSimConnectDefinition);
|
|
begin
|
|
inherited Create;
|
|
|
|
FDataHandlers := TInterfaceList.Create;
|
|
FDefinition := ADefinition;
|
|
end;
|
|
|
|
|
|
destructor TFSXSimConnectDefinitionRef.Destroy;
|
|
begin
|
|
FreeAndNil(FDataHandlers);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TFSXSimConnectDefinitionRef.HandleData(AData: Pointer);
|
|
var
|
|
dataHandler: IInterface;
|
|
|
|
begin
|
|
for dataHandler in DataHandlers do
|
|
(dataHandler as IFSXSimConnectDataHandler).HandleData(AData);
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectDefinitionRef.Attach(ADataHandler: IFSXSimConnectDataHandler): Integer;
|
|
begin
|
|
DataHandlers.Add(ADataHandler as IFSXSimConnectDataHandler);
|
|
Result := DataHandlers.Count;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectDefinitionRef.Detach(ADataHandler: IFSXSimConnectDataHandler): Integer;
|
|
begin
|
|
DataHandlers.Remove(ADataHandler as IFSXSimConnectDataHandler);
|
|
Result := DataHandlers.Count;
|
|
end;
|
|
|
|
|
|
{ TFSXSimConnectDefinitionMap }
|
|
constructor TFSXSimConnectDefinitionMap.Create(ACapacity: Integer);
|
|
begin
|
|
inherited Create([doOwnsValues], ACapacity);
|
|
end;
|
|
|
|
|
|
{ TFSXSimConnectVariable }
|
|
constructor TFSXSimConnectVariable.Create(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single);
|
|
begin
|
|
inherited Create;
|
|
|
|
FVariableName := AVariableName;
|
|
FUnitsName := AUnitsName;
|
|
FDataType := ADataType;
|
|
FEpsilon := AEpsilon;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectVariable.GetVariableName: string;
|
|
begin
|
|
Result := FVariableName;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectVariable.GetUnitsName: string;
|
|
begin
|
|
Result := FUnitsName;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectVariable.GetDataType: SIMCONNECT_DATAType;
|
|
begin
|
|
Result := FDataType;
|
|
end;
|
|
|
|
|
|
function TFSXSimConnectVariable.GetEpsilon: Single;
|
|
begin
|
|
Result := FEpsilon;
|
|
end;
|
|
|
|
|
|
{ TAddDefinitionValue }
|
|
constructor TAddDefinitionValue.Create(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler);
|
|
begin
|
|
inherited Create;
|
|
|
|
FDefinition := ADefinition;
|
|
FDataHandler := ADataHandler;
|
|
end;
|
|
|
|
|
|
procedure TAddDefinitionValue.SetDefinitionID(const Value: Cardinal);
|
|
begin
|
|
FDefinitionID := Value;
|
|
Signal;
|
|
end;
|
|
|
|
|
|
{ TRemoveDefinitionValue }
|
|
constructor TRemoveDefinitionValue.Create(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler);
|
|
begin
|
|
inherited Create;
|
|
|
|
FDefinitionID := ADefinitionID;
|
|
FDataHandler := ADataHandler;
|
|
end;
|
|
|
|
end.
|