From 4d0e2a5af6bef8499909347d044c5ab5ba0dab9c Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Thu, 21 Feb 2013 21:59:29 +0000 Subject: [PATCH] Added: automatic detection of Throttle plug in / out Added: actual SimConnect connection and auto-reconnect --- G940LEDControl/Forms/ButtonFunctionFrm.dfm | 3 - G940LEDControl/Forms/ButtonFunctionFrm.pas | 2 +- G940LEDControl/Forms/MainFrm.dfm | 15 +- G940LEDControl/Forms/MainFrm.pas | 219 ++++---- G940LEDControl/G940LEDControl.dproj | 1 - G940LEDControl/Units/FSXLEDFunction.pas | 30 +- .../Units/FSXLEDFunctionProvider.pas | 131 ++++- G940LEDControl/Units/FSXResources.pas | 2 + G940LEDControl/Units/FSXSimConnectClient.pas | 511 +++++++++++++++++- G940LEDControl/Units/FSXSimConnectIntf.pas | 22 +- G940LEDControl/Units/G940LEDStateConsumer.pas | 130 +---- G940LEDControl/Units/LEDFunction.pas | 10 +- G940LEDControl/Units/LEDFunctionRegistry.pas | 4 +- G940LEDControl/Units/LEDStateConsumer.pas | 53 +- G940LEDControl/Units/Profile.pas | 2 +- 15 files changed, 801 insertions(+), 334 deletions(-) diff --git a/G940LEDControl/Forms/ButtonFunctionFrm.dfm b/G940LEDControl/Forms/ButtonFunctionFrm.dfm index 5b4ac44..a58a285 100644 --- a/G940LEDControl/Forms/ButtonFunctionFrm.dfm +++ b/G940LEDControl/Forms/ButtonFunctionFrm.dfm @@ -101,7 +101,6 @@ object ButtonFunctionForm: TButtonFunctionForm OnFocusChanged = vstFunctionsFocusChanged OnGetText = vstFunctionsGetText OnPaintText = vstFunctionsPaintText - ExplicitHeight = 383 Columns = < item Position = 0 @@ -122,7 +121,6 @@ object ButtonFunctionForm: TButtonFunctionForm Align = alClient BevelOuter = bvNone TabOrder = 1 - ExplicitHeight = 383 object pnlName: TPanel Left = 0 Top = 0 @@ -194,7 +192,6 @@ object ButtonFunctionForm: TButtonFunctionForm Align = alClient BorderStyle = bsNone TabOrder = 1 - ExplicitHeight = 286 end end object pnlHeader: TPanel diff --git a/G940LEDControl/Forms/ButtonFunctionFrm.pas b/G940LEDControl/Forms/ButtonFunctionFrm.pas index 60072dc..f442d4c 100644 --- a/G940LEDControl/Forms/ButtonFunctionFrm.pas +++ b/G940LEDControl/Forms/ButtonFunctionFrm.pas @@ -469,7 +469,7 @@ begin FreeAndNil(FComboBox); FreeAndNil(FStateLabel); - inherited; + inherited Destroy; end; end. diff --git a/G940LEDControl/Forms/MainFrm.dfm b/G940LEDControl/Forms/MainFrm.dfm index f1ed398..857ac1a 100644 --- a/G940LEDControl/Forms/MainFrm.dfm +++ b/G940LEDControl/Forms/MainFrm.dfm @@ -356,6 +356,10 @@ object MainForm: TMainForm object tsAbout: TTabSheet Caption = 'About' ImageIndex = 1 + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblVersionCaption: TLabel Left = 16 Top = 67 @@ -842,16 +846,5 @@ object MainForm: TMainForm ParentFont = False ExplicitWidth = 401 end - object btnRetry: TButton - Left = 374 - Top = 20 - Width = 75 - Height = 25 - Anchors = [akTop, akRight] - Caption = '&Retry' - TabOrder = 0 - Visible = False - OnClick = btnRetryClick - end end end diff --git a/G940LEDControl/Forms/MainFrm.pas b/G940LEDControl/Forms/MainFrm.pas index bd5a9de..7cb4299 100644 --- a/G940LEDControl/Forms/MainFrm.pas +++ b/G940LEDControl/Forms/MainFrm.pas @@ -2,15 +2,15 @@ unit MainFrm; interface uses - Classes, - Contnrs, - Controls, - ComCtrls, - ExtCtrls, - Forms, - Messages, - StdCtrls, - Windows, + System.Classes, + System.Contnrs, + Vcl.ComCtrls, + Vcl.Controls, + Vcl.ExtCtrls, + Vcl.Forms, + Vcl.StdCtrls, + Winapi.Messages, + Winapi.Windows, OtlComm, OtlEventMonitor, @@ -20,7 +20,7 @@ uses X2UtPersistIntf, LEDStateConsumer, - Profile; + Profile, Vcl.AppEvnts; const @@ -31,6 +31,12 @@ const LED_COUNT = 8; + DBT_DEVICEARRIVAL = $8000; + DBT_DEVICEREMOVECOMPLETE = $8004; + DBT_DEVTYP_DEVICEINTERFACE = $0005; + DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = $0004; + + type TLEDControls = record ConfigureButton: TButton; @@ -44,7 +50,6 @@ type lblG940Throttle: TLabel; imgStateFound: TImage; lblG940ThrottleState: TLabel; - btnRetry: TButton; PageControl: TPageControl; pnlG940: TPanel; tsAbout: TTabSheet; @@ -91,7 +96,6 @@ type bvlProfiles: TBevel; procedure FormCreate(Sender: TObject); - procedure btnRetryClick(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure lblLinkLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType); procedure btnCheckUpdatesClick(Sender: TObject); @@ -107,6 +111,14 @@ type FActiveProfile: TProfile; FLoadingProfiles: Boolean; FStateConsumerTask: IOmniTaskControl; + + FDeviceNotification: Pointer; + FG940Found: Boolean; + protected + procedure RegisterDeviceArrival; + procedure UnregisterDeviceArrival; + + procedure WMDeviceChange(var Msg: TMessage); message WM_DEVICECHANGE; protected procedure FindLEDControls; procedure LoadProfiles; @@ -126,9 +138,6 @@ type procedure EventMonitorTerminated(const task: IOmniTaskControl); procedure HandleDeviceStateMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); - procedure HandleRunInMainThreadMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); - procedure HandleProviderKilled(ATask: IOmniTaskControl; AMessage: TOmniMessage); - procedure HandleProviderKilledFSX(ATask: IOmniTaskControl; AMessage: TOmniMessage); procedure CMAskAutoUpdate(var Msg: TMessage); message CM_ASKAUTOUPDATE; @@ -204,9 +213,10 @@ begin FProfiles := TProfileList.Create(True); LoadProfiles; + // TODO implement profile changing properly FStateConsumerTask.Comm.Send(TM_LOADPROFILE, ActiveProfile); -// LoadFunctions(TFSXLEDStateProvider, FFSXComboBoxes); -// LoadDefaultProfile; + + RegisterDeviceArrival; end; @@ -225,10 +235,63 @@ end; procedure TMainForm.FormDestroy(Sender: TObject); begin + UnregisterDeviceArrival; + FreeAndNil(FProfiles); end; +procedure TMainForm.RegisterDeviceArrival; +type + TDevBroadcastDeviceInterface = packed record + dbcc_size: DWORD; + dbcc_devicetype: DWORD; + dbcc_reserved: DWORD; + dbcc_classguid: TGUID; + dbcc_name: PChar; + end; + +var + request: TDevBroadcastDeviceInterface; + +begin + ZeroMemory(@request, SizeOf(request)); + request.dbcc_size := SizeOf(request); + request.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE; + + FDeviceNotification := RegisterDeviceNotification(Self.Handle, @request, + DEVICE_NOTIFY_WINDOW_HANDLE or + DEVICE_NOTIFY_ALL_INTERFACE_CLASSES); +end; + + +procedure TMainForm.UnregisterDeviceArrival; +begin + if Assigned(FDeviceNotification) then + begin + UnregisterDeviceNotification(FDeviceNotification); + FDeviceNotification := nil; + end; +end; + + +procedure TMainForm.WMDeviceChange(var Msg: TMessage); +begin + if not Assigned(StateConsumerTask) then + exit; + + case Msg.WParam of + DBT_DEVICEARRIVAL: + if (not FG940Found) then + StateConsumerTask.Comm.Send(TM_FINDTHROTTLEDEVICE); + + DBT_DEVICEREMOVECOMPLETE: + if FG940Found then + StateConsumerTask.Comm.Send(TM_TESTTHROTTLEDEVICE); + end; +end; + + procedure TMainForm.FindLEDControls; function ComponentByName(const AName: string; ATag: NativeInt): TComponent; @@ -417,36 +480,11 @@ begin imgStateFound.Visible := AFound; imgStateNotFound.Visible := not AFound; + + FG940Found := AFound; end; -//procedure TMainForm.ReadFunctions(AReader: IX2PersistReader; AComboBoxes: TComboBoxArray); -//var -// comboBox: TComboBoxEx; -// value: Integer; -// itemIndex: Integer; -// -//begin -// if AReader.BeginSection(SECTION_FSX) then -// try -// for comboBox in AComboBoxes do -// begin -// if AReader.ReadInteger('Function' + IntToStr(comboBox.Tag), value) then -// begin -// for itemIndex := 0 to Pred(comboBox.ItemsEx.Count) do -// if Integer(comboBox.ItemsEx[itemIndex].Data) = value then -// begin -// comboBox.ItemIndex := itemIndex; -// break; -// end; -// end; -// end; -// finally -// AReader.EndSection; -// end; -//end; - - //procedure TMainForm.ReadAutoUpdate(AReader: IX2PersistReader); //var // checkUpdates: Boolean; @@ -484,29 +522,6 @@ end; //end; -//procedure TMainForm.InitializeStateProvider(AProviderClass: TLEDStateProviderClass); -//begin -// UpdateMapping; -// LEDStateConsumer.InitializeStateProvider(StateConsumerTask, AProviderClass); -//end; -// -// -//procedure TMainForm.FinalizeStateProvider; -//begin -// LEDStateConsumer.FinalizeStateProvider(StateConsumerTask); -//end; - - -//procedure TMainForm.UpdateMapping; -//begin -// if not Assigned(StateConsumerTask) then -// Exit; -// -// LEDStateConsumer.ClearFunctions(StateConsumerTask); -// SetFunctions(FFSXComboBoxes); -//end; - - procedure TMainForm.LEDButtonClick(Sender: TObject); var buttonIndex: NativeInt; @@ -628,32 +643,31 @@ end; procedure TMainForm.EventMonitorMessage(const task: IOmniTaskControl; const msg: TOmniMessage); begin case msg.MsgID of - TM_NOTIFY_DEVICESTATE: HandleDeviceStateMessage(task, msg); -// MSG_RUN_IN_MAINTHREAD: HandleRunInMainThreadMessage(task, msg); -// MSG_PROVIDER_KILLED: HandleProviderKilled(task, msg); + TM_NOTIFY_DEVICESTATE: + HandleDeviceStateMessage(task, msg); MSG_UPDATE: if MessageBox(Self.Handle, 'An update is available on the G940 LED Control website.'#13#10'Do you want to go there now?', 'Update available', MB_YESNO or MB_ICONINFORMATION) = ID_YES then ShellExecute(Self.Handle, 'open', PChar('http://g940.x2software.net/#download'), nil, nil, SW_SHOWNORMAL); -// MSG_NOUPDATE: -// if msg.MsgData.AsBoolean then -// MessageBox(Self.Handle, 'You are using the latest version.', 'No update available', MB_OK or MB_ICONINFORMATION) -// else -// MessageBox(Self.Handle, 'Failed to check for updates. Maybe try again later?', 'Uh-oh', MB_OK or MB_ICONWARNING); + MSG_NOUPDATE: + if msg.MsgData.AsBoolean then + MessageBox(Self.Handle, 'You are using the latest version.', 'No update available', MB_OK or MB_ICONINFORMATION) + else + MessageBox(Self.Handle, 'Failed to check for updates. Maybe try again later?', 'Uh-oh', MB_OK or MB_ICONWARNING); end; end; procedure TMainForm.EventMonitorTerminated(const task: IOmniTaskControl); begin -// if task = StateConsumerTask then -// begin -// FStateConsumerTask := nil; -// Close; -// end else if task.Name = 'CheckForUpdatesThread' then -// btnCheckUpdates.Enabled := True; + if task = StateConsumerTask then + begin + FStateConsumerTask := nil; + Close; + end else if task.Name = 'CheckForUpdatesThread' then + btnCheckUpdates.Enabled := True; end; @@ -667,58 +681,17 @@ begin SetDeviceState(TEXT_STATE_FOUND, True); DEVICESTATE_NOTFOUND: - begin - SetDeviceState(TEXT_STATE_NOTFOUND, False); - btnRetry.Visible := True; - end; + SetDeviceState(TEXT_STATE_NOTFOUND, False); end; end; -procedure TMainForm.HandleRunInMainThreadMessage(ATask: IOmniTaskControl; AMessage: TOmniMessage); -var - executor: IRunInMainThread; - -begin - executor := (AMessage.MsgData.AsInterface as IRunInMainThread); - executor.Execute; - executor.Signal; -end; - - -procedure TMainForm.HandleProviderKilled(ATask: IOmniTaskControl; AMessage: TOmniMessage); -begin - HandleProviderKilledFSX(ATask, AMessage); -end; - - -procedure TMainForm.HandleProviderKilledFSX(ATask: IOmniTaskControl; AMessage: TOmniMessage); -var - msg: string; - -begin -// btnFSXDisconnect.Enabled := False; -// btnFSXConnect.Enabled := True; - - msg := AMessage.MsgData; - if Length(msg) > 0 then - ShowMessage(msg); -end; - - procedure TMainForm.btnCheckUpdatesClick(Sender: TObject); begin CheckForUpdates(True); end; -procedure TMainForm.btnRetryClick(Sender: TObject); -begin - btnRetry.Visible := False; -// StateConsumerTask.Comm.Send(MSG_FINDTHROTTLEDEVICE); -end; - - procedure TMainForm.lblLinkLinkClick(Sender: TObject; const Link: string; LinkType: TSysLinkType); begin ShellExecute(Self.Handle, 'open', PChar(Link), nil, nil, SW_SHOWNORMAL); diff --git a/G940LEDControl/G940LEDControl.dproj b/G940LEDControl/G940LEDControl.dproj index a3cb490..d357a23 100644 --- a/G940LEDControl/G940LEDControl.dproj +++ b/G940LEDControl/G940LEDControl.dproj @@ -98,7 +98,6 @@ False - true false CompanyName=;FileDescription=;FileVersion=0.2.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=0.2;Comments= F:\Components\X2Utils\Resources\VistaManAsInvoker.manifest diff --git a/G940LEDControl/Units/FSXLEDFunction.pas b/G940LEDControl/Units/FSXLEDFunction.pas index 0f8c13c..f829279 100644 --- a/G940LEDControl/Units/FSXLEDFunction.pas +++ b/G940LEDControl/Units/FSXLEDFunction.pas @@ -92,13 +92,13 @@ type { Worker implementations } TFSXEngineFunctionWorker = class(TCustomFSXFunctionWorker) protected - procedure RegisterVariables; override; + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; procedure HandleData(AData: Pointer); override; end; TFSXGearFunctionWorker = class(TCustomFSXFunctionWorker) protected - procedure RegisterVariables; override; + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; procedure HandleData(AData: Pointer); override; end; @@ -106,7 +106,7 @@ type private FStateMask: Integer; protected - procedure RegisterVariables; override; + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); override; procedure HandleData(AData: Pointer); override; public property StateMask: Integer read FStateMask write FStateMask; @@ -140,21 +140,21 @@ end; { TFSXEngineFunctionWorker } -procedure TFSXEngineFunctionWorker.RegisterVariables; +procedure TFSXEngineFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); var engineIndex: Integer; begin - Definition.AddVariable('NUMBER OF ENGINES', FSX_UNIT_NUMBER, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('NUMBER OF ENGINES', FSX_UNIT_NUMBER, SIMCONNECT_DATAType_INT32); for engineIndex := 1 to FSX_MAX_ENGINES do - Definition.AddVariable(Format('GENERAL ENG COMBUSTION:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable(Format('GENERAL ENG COMBUSTION:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); for engineIndex := 1 to FSX_MAX_ENGINES do - Definition.AddVariable(Format('ENG FAILED:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable(Format('ENG FAILED:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); for engineIndex := 1 to FSX_MAX_ENGINES do - Definition.AddVariable(Format('ENG ON FIRE:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable(Format('ENG ON FIRE:%d', [engineIndex]), FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); end; @@ -236,12 +236,12 @@ end; { TFSXGearFunctionWorker } -procedure TFSXGearFunctionWorker.RegisterVariables; +procedure TFSXGearFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); begin - Definition.AddVariable('IS GEAR RETRACTABLE', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); - Definition.AddVariable('GEAR TOTAL PCT EXTENDED', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); - Definition.AddVariable('GEAR DAMAGE BY SPEED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); - Definition.AddVariable('GEAR SPEED EXCEEDED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('IS GEAR RETRACTABLE', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('GEAR TOTAL PCT EXTENDED', FSX_UNIT_PERCENT, SIMCONNECT_DATAType_FLOAT64); + ADefinition.AddVariable('GEAR DAMAGE BY SPEED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); + ADefinition.AddVariable('GEAR SPEED EXCEEDED', FSX_UNIT_BOOL, SIMCONNECT_DATAType_INT32); end; @@ -349,9 +349,9 @@ end; { TFSXLightStatesFunctionWorker } -procedure TFSXLightStatesFunctionWorker.RegisterVariables; +procedure TFSXLightStatesFunctionWorker.RegisterVariables(ADefinition: IFSXSimConnectDefinition); begin - Definition.AddVariable('LIGHT ON STATES', FSX_UNIT_MASK, SIMCONNECT_DATATYPE_INT32); + ADefinition.AddVariable('LIGHT ON STATES', FSX_UNIT_MASK, SIMCONNECT_DATATYPE_INT32); end; diff --git a/G940LEDControl/Units/FSXLEDFunctionProvider.pas b/G940LEDControl/Units/FSXLEDFunctionProvider.pas index 19659e1..2a7d61b 100644 --- a/G940LEDControl/Units/FSXLEDFunctionProvider.pas +++ b/G940LEDControl/Units/FSXLEDFunctionProvider.pas @@ -18,7 +18,7 @@ type TFSXLEDFunctionProvider = class(TCustomLEDFunctionProvider, IFSXSimConnectObserver) private - FSimConnect: IFSXSimConnect; + FSimConnect: TInterfacedObject; FSimConnectLock: TCriticalSection; protected procedure RegisterFunctions; override; @@ -41,6 +41,8 @@ type FDisplayName: string; FUID: string; protected + function DoCreateWorker(ASettings: ILEDFunctionWorkerSettings): TCustomLEDFunctionWorker; override; + property Provider: TFSXLEDFunctionProvider read FProvider; protected function GetCategoryName: string; override; @@ -54,26 +56,30 @@ type TCustomFSXFunctionClass = class of TCustomFSXFunction; - TCustomFSXFunctionWorker = class(TCustomLEDFunctionWorker, IFSXSimConnectDataHandler) + TCustomFSXFunctionWorker = class(TCustomLEDFunctionWorker) private + FDataHandler: IFSXSimConnectDataHandler; + FDefinitionID: Cardinal; FSimConnect: IFSXSimConnect; - FDefinition: IFSXSimConnectDefinition; FCurrentStateLock: TCriticalSection; FCurrentState: ILEDStateWorker; protected - procedure RegisterVariables; virtual; abstract; + procedure RegisterStates(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); override; + procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); virtual; abstract; - procedure SetCurrentState(const AUID: string); + procedure SetCurrentState(const AUID: string; ANotifyObservers: Boolean = True); overload; virtual; + procedure SetCurrentState(AState: ILEDStateWorker; ANotifyObservers: Boolean = True); overload; virtual; + procedure SetSimConnect(const Value: IFSXSimConnect); virtual; - property Definition: IFSXSimConnectDefinition read FDefinition; - property SimConnect: IFSXSimConnect read FSimConnect; + property DataHandler: IFSXSimConnectDataHandler read FDataHandler; + property DefinitionID: Cardinal read FDefinitionID; + property SimConnect: IFSXSimConnect read FSimConnect write SetSimConnect; protected function GetCurrentState: ILEDStateWorker; override; - { IFSXSimConnectDataHandler } procedure HandleData(AData: Pointer); virtual; abstract; public - constructor Create(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; ASimConnect: IFSXSimConnect); + constructor Create(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); override; destructor Destroy; override; end; @@ -89,6 +95,20 @@ uses SimConnect; +type + TCustomFSXFunctionWorkerDataHandler = class(TInterfacedObject, IFSXSimConnectDataHandler) + private + FWorker: TCustomFSXFunctionWorker; + protected + { IFSXSimConnectDataHandler } + procedure HandleData(AData: Pointer); + + property Worker: TCustomFSXFunctionWorker read FWorker; + public + constructor Create(AWorker: TCustomFSXFunctionWorker); + end; + + { TFSXLEDFunctionProvider } constructor TFSXLEDFunctionProvider.Create; @@ -167,11 +187,13 @@ begin try if not Assigned(FSimConnect) then begin + { Keep an object reference so we don't increment the reference count. + We'll know when it's gone through the ObserveDestroy. } FSimConnect := TFSXSimConnectInterface.Create; - FSimConnect.Attach(Self); + (FSimConnect as IFSXSimConnect).Attach(Self); end; - Result := FSimConnect; + Result := (FSimConnect as IFSXSimConnect); finally FSimConnectLock.Release; end; @@ -189,6 +211,14 @@ begin end; +function TCustomFSXFunction.DoCreateWorker(ASettings: ILEDFunctionWorkerSettings): TCustomLEDFunctionWorker; +begin + Result := inherited DoCreateWorker(ASettings); + + (Result as TCustomFSXFunctionWorker).SimConnect := Provider.GetSimConnect; +end; + + function TCustomFSXFunction.GetCategoryName: string; begin Result := FSXCategory; @@ -208,18 +238,16 @@ end; { TCustomFSXFunctionWorker } -constructor TCustomFSXFunctionWorker.Create(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; ASimConnect: IFSXSimConnect); +constructor TCustomFSXFunctionWorker.Create(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); begin - inherited Create(AStates, ASettings); - FCurrentStateLock := TCriticalSection.Create; - FSimConnect := ASimConnect; - FDefinition := ASimConnect.CreateDefinition; - RegisterVariables; + { We can't pass ourselves as the Data Handler, as it would keep a reference to + this worker from the SimConnect interface. That'd mean the worker never + gets destroyed, and SimConnect never shuts down. Hence this proxy class. } + FDataHandler := TCustomFSXFunctionWorkerDataHandler.Create(Self); - // TODO pass self as callback for this definition - ASimConnect.AddDefinition(FDefinition, Self); + inherited Create(AStates, ASettings); end; @@ -227,7 +255,20 @@ destructor TCustomFSXFunctionWorker.Destroy; begin FreeAndNil(FCurrentStateLock); - inherited; + if DefinitionID <> 0 then + SimConnect.RemoveDefinition(DefinitionID, DataHandler); + + inherited Destroy; +end; + + +procedure TCustomFSXFunctionWorker.RegisterStates(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); +begin + inherited RegisterStates(AStates, ASettings); + + { Make sure we have a default state } + if States.Count > 0 then + SetCurrentState((States[0] as ILEDStateWorker), False); end; @@ -242,18 +283,22 @@ begin end; -procedure TCustomFSXFunctionWorker.SetCurrentState(const AUID: string); -var - newState: ILEDStateWorker; +procedure TCustomFSXFunctionWorker.SetCurrentState(const AUID: string; ANotifyObservers: Boolean); +begin + SetCurrentState(FindState(AUID), ANotifyObservers); +end; + +procedure TCustomFSXFunctionWorker.SetCurrentState(AState: ILEDStateWorker; ANotifyObservers: Boolean); begin FCurrentStateLock.Acquire; try - newState := FindState(AUID); - if newState <> FCurrentState then + if AState <> FCurrentState then begin - FCurrentState := newState; - NotifyObservers; + FCurrentState := AState; + + if ANotifyObservers then + NotifyObservers; end; finally FCurrentStateLock.Release; @@ -261,6 +306,38 @@ begin end; +procedure TCustomFSXFunctionWorker.SetSimConnect(const Value: IFSXSimConnect); +var + definition: IFSXSimConnectDefinition; + +begin + FSimConnect := Value; + + if Assigned(SimConnect) then + begin + definition := SimConnect.CreateDefinition; + RegisterVariables(definition); + + FDefinitionID := SimConnect.AddDefinition(definition, DataHandler); + end; +end; + + +{ TCustomFSXFunctionWorkerDataHandler } +constructor TCustomFSXFunctionWorkerDataHandler.Create(AWorker: TCustomFSXFunctionWorker); +begin + inherited Create; + + FWorker := AWorker; +end; + + +procedure TCustomFSXFunctionWorkerDataHandler.HandleData(AData: Pointer); +begin + Worker.HandleData(AData); +end; + + initialization TLEDFunctionRegistry.Register(TFSXLEDFunctionProvider.Create); diff --git a/G940LEDControl/Units/FSXResources.pas b/G940LEDControl/Units/FSXResources.pas index d1dad2a..b6f6300 100644 --- a/G940LEDControl/Units/FSXResources.pas +++ b/G940LEDControl/Units/FSXResources.pas @@ -2,6 +2,8 @@ unit FSXResources; interface const + FSXSimConnectAppName = 'G940 LED Control'; + FSXProviderUID = 'fsx'; FSXCategory = 'Flight Simulator X'; FSXCategoryLights = FSXCategory + ' - Lights'; diff --git a/G940LEDControl/Units/FSXSimConnectClient.pas b/G940LEDControl/Units/FSXSimConnectClient.pas index e55d7e5..3155883 100644 --- a/G940LEDControl/Units/FSXSimConnectClient.pas +++ b/G940LEDControl/Units/FSXSimConnectClient.pas @@ -23,8 +23,8 @@ type procedure Detach(AObserver: IFSXSimConnectObserver); function CreateDefinition: IFSXSimConnectDefinition; - procedure AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler); - procedure RemoveDefinition(ADataHandler: IFSXSimConnectDataHandler); + function AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer; + procedure RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); public constructor Create; destructor Destroy; override; @@ -33,36 +33,149 @@ type implementation uses + Generics.Collections, + System.Math, + System.SyncObjs, System.SysUtils, + Winapi.Windows, - SimConnect; + OtlComm, + OtlCommon, + SimConnect, + + FSXResources; const TM_ADDDEFINITION = 3001; TM_REMOVEDEFINITION = 3002; + TM_TRYSIMCONNECT = 3003; + + TIMER_TRYSIMCONNECT = 201; + + INTERVAL_TRYSIMCONNECT = 5000; type - TFSXSimConnectClient = class(TOmniWorker) + TFSXSimConnectDefinitionRef = class(TObject) + private + FDefinition: IFSXSimConnectDefinitionAccess; + FDataHandlers: TInterfaceList; + protected + property DataHandlers: TInterfaceList read FDataHandlers; + public + constructor Create(ADefinition: IFSXSimConnectDefinitionAccess); + destructor Destroy; override; + + procedure Attach(ADataHandler: IFSXSimConnectDataHandler); + procedure Detach(ADataHandler: IFSXSimConnectDataHandler); + + procedure HandleData(AData: Pointer); + + property Definition: IFSXSimConnectDefinitionAccess read FDefinition; end; - TFSXSimConnectDefinition = class(TInterfacedObject, IFSXSimConnectDefinition) + TFSXSimConnectDefinitionMap = TDictionary; + + TFSXSimConnectClient = class(TOmniWorker) + private + FDefinitions: TFSXSimConnectDefinitionMap; + FLastDefinitionID: Cardinal; + FSimConnectHandle: THandle; + FSimConnectDataEvent: TEvent; + 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; + + procedure HandleSimConnectDataEvent; + protected + function Initialize: Boolean; override; + procedure Cleanup; override; + + procedure TrySimConnect; + + procedure RegisterDefinitions; + procedure RegisterDefinition(ADefinitionID: Cardinal; ADefinition: IFSXSimConnectDefinitionAccess); + + function SameDefinition(ADefinition1, ADefinition2: IFSXSimConnectDefinitionAccess): Boolean; + + property Definitions: TFSXSimConnectDefinitionMap read FDefinitions; + property LastDefinitionID: Cardinal read FLastDefinitionID; + property SimConnectHandle: THandle read FSimConnectHandle; + property SimConnectDataEvent: TEvent read FSimConnectDataEvent; + 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; + + TFSXSimConnectDefinition = class(TInterfacedObject, IFSXSimConnectDefinition, IFSXSimConnectDefinitionAccess) private FSimConnect: IFSXSimConnect; + FVariables: TFSXSimConnectVariableList; protected property SimConnect: IFSXSimConnect read FSimConnect; + property Variables: TFSXSimConnectVariableList read FVariables; protected { IFSXSimConnectDefinition } - procedure AddVariable(AVariableName, AUnitsName: string; ADatumType: SIMCONNECT_DATAType; AEpsilon: Single = 0); - procedure Apply(ASimConnectHandle: THandle; ADefinitionID: Integer); + procedure AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single = 0); + + { IFSXSimConnectDefinitionAccess } + function GetVariableCount: Integer; + function GetVariable(AIndex: Integer): IFSXSimConnectVariable; public - constructor Create(ASimConnect: IFSXSimConnect); + 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; @@ -70,21 +183,30 @@ var worker: IOmniWorker; begin - worker := TFSXSimConnectClient.Create; - FClient := CreateTask(worker); + inherited Create; FObservers := TInterfaceList.Create; + + worker := TFSXSimConnectClient.Create; + FClient := CreateTask(worker).Run; end; destructor TFSXSimConnectInterface.Destroy; +var + observer: IInterface; + begin + for observer in Observers do + (observer as IFSXSimConnectObserver).ObserveDestroy(Self); + FreeAndNil(FObservers); + // TODO this doesn't get triggered yet. The connection is killed fine, but not because of us. Needs work. FClient.Terminate; FClient := nil; - inherited; + inherited Destroy; end; @@ -102,41 +224,386 @@ end; function TFSXSimConnectInterface.CreateDefinition: IFSXSimConnectDefinition; begin - Result := TFSXSimConnectDefinition.Create(Self); + Result := TFSXSimConnectDefinition.Create; end; -procedure TFSXSimConnectInterface.AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler); +function TFSXSimConnectInterface.AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer; +var + addDefinition: TAddDefinitionValue; + begin - Client.Comm.Send(TM_ADDDEFINITION, [ADefinition, ADataHandler]); - // TODO pass to thread; if definition already exists (same variables), link to existing definition to avoid too many SimConnect definition + addDefinition := TAddDefinitionValue.Create(ADefinition, ADataHandler); + Client.Comm.Send(TM_ADDDEFINITION, addDefinition); + + addDefinition.WaitFor(INFINITE); + Result := addDefinition.DefinitionID; end; -procedure TFSXSimConnectInterface.RemoveDefinition(ADataHandler: IFSXSimConnectDataHandler); +procedure TFSXSimConnectInterface.RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); +var + removeDefinition: TRemoveDefinitionValue; + begin - Client.Comm.Send(TM_REMOVEDEFINITION, ADataHandler); + removeDefinition := TRemoveDefinitionValue.Create(ADefinitionID, ADataHandler); + Client.Comm.Send(TM_REMOVEDEFINITION, removeDefinition); + + removeDefinition.WaitFor(INFINITE); end; { TFSXSimConnectDefinition } -constructor TFSXSimConnectDefinition.Create(ASimConnect: IFSXSimConnect); +constructor TFSXSimConnectDefinition.Create; begin + inherited Create; + FVariables := TFSXSimConnectVariableList.Create(True); end; -procedure TFSXSimConnectDefinition.AddVariable(AVariableName, AUnitsName: string; ADatumType: SIMCONNECT_DATAType; AEpsilon: Single); +destructor TFSXSimConnectDefinition.Destroy; begin + FreeAndNil(FVariables); + inherited Destroy; end; -procedure TFSXSimConnectDefinition.Apply(ASimConnectHandle: THandle; ADefinitionID: Integer); +procedure TFSXSimConnectDefinition.AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single); begin -// SimConnect_AddToDataDefinition(ASimConnectHandle, ADefinitionID, -// AnsiString(AVariableName), AnsiString(AUnitsName), ADatumType, AEpsilon, 0); + 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 } +function TFSXSimConnectClient.Initialize: Boolean; +begin + Result := inherited Initialize; + if not Result then + exit; + + FDefinitions := TFSXSimConnectDefinitionMap.Create; + FSimConnectDataEvent := TEvent.Create(nil, False, False, ''); + + Task.RegisterWaitObject(SimConnectDataEvent.Handle, HandleSimConnectDataEvent); + + TrySimConnect; +end; + + +procedure TFSXSimConnectClient.Cleanup; +begin + // TODO unregister definitions ? + if SimConnectHandle <> 0 then + SimConnect_Close(SimConnectHandle); + + FreeAndNil(FSimConnectDataEvent); + FreeAndNil(FDefinitions); + + inherited Cleanup; +end; + + +procedure TFSXSimConnectClient.TrySimConnect; +begin + if SimConnectHandle <> 0 then + exit; + + if InitSimConnect then + begin + if SimConnect_Open(FSimConnectHandle, FSXSimConnectAppName, 0, 0, SimConnectDataEvent.Handle, 0) = S_OK then + begin + Task.ClearTimer(TIMER_TRYSIMCONNECT); + RegisterDefinitions; + end; + end; + + if SimConnectHandle = 0 then + Task.SetTimer(TIMER_TRYSIMCONNECT, INTERVAL_TRYSIMCONNECT, TM_TRYSIMCONNECT); +end; + + +procedure TFSXSimConnectClient.HandleSimConnectDataEvent; +var + data: PSimConnectRecv; + dataSize: Cardinal; + simObjectData: PSimConnectRecvSimObjectData; + definitionRef: TFSXSimConnectDefinitionRef; + +begin + 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); + + if Definitions.ContainsKey(simObjectData^.dwDefineID) then + begin + definitionRef := Definitions[simObjectData^.dwDefineID]; + definitionRef.HandleData(@simObjectData^.dwData); + end; + end; + + SIMCONNECT_RECV_ID_QUIT: + begin + FSimConnectHandle := 0; + Task.SetTimer(TIMER_TRYSIMCONNECT, INTERVAL_TRYSIMCONNECT, TM_TRYSIMCONNECT); + end; + end; + end; +end; + + +procedure TFSXSimConnectClient.RegisterDefinitions; +var + definitionID: Cardinal; + +begin + if SimConnectHandle = 0 then + exit; + + for definitionID in Definitions.Keys do + RegisterDefinition(definitionID, Definitions[definitionID].Definition); +end; + + +procedure TFSXSimConnectClient.RegisterDefinition(ADefinitionID: Cardinal; ADefinition: IFSXSimConnectDefinitionAccess); +var + variableIndex: Integer; + variable: IFSXSimConnectVariable; + +begin + if SimConnectHandle = 0 then + exit; + + 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; + + +function TFSXSimConnectClient.SameDefinition(ADefinition1, ADefinition2: IFSXSimConnectDefinitionAccess): 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.TMAddDefinition(var Msg: TOmniMessage); +var + addDefinition: TAddDefinitionValue; + definitionID: Cardinal; + definitionRef: TFSXSimConnectDefinitionRef; + definitionAccess: IFSXSimConnectDefinitionAccess; + hasDefinition: Boolean; + +begin + addDefinition := Msg.MsgData; + definitionAccess := (addDefinition.Definition as IFSXSimConnectDefinitionAccess); + hasDefinition := False; + + { 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 + definitionRef.Attach(addDefinition.DataHandler); + addDefinition.DefinitionID := definitionID; + hasDefinition := True; + break; + end; + end; + + if not hasDefinition then + begin + { Add as new definition } + Inc(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; + +begin + removeDefinition := Msg.MsgData; + + // TODO actually remove the definition + + removeDefinition.Signal; +end; + + +procedure TFSXSimConnectClient.TMTrySimConnect(var Msg: TOmniMessage); +begin + TrySimConnect; +end; + + +{ TFSXSimConnectDefinitionRef } +constructor TFSXSimConnectDefinitionRef.Create(ADefinition: IFSXSimConnectDefinitionAccess); +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; + + +procedure TFSXSimConnectDefinitionRef.Attach(ADataHandler: IFSXSimConnectDataHandler); +begin + DataHandlers.Add(ADataHandler as IFSXSimConnectDataHandler); +end; + + +procedure TFSXSimConnectDefinitionRef.Detach(ADataHandler: IFSXSimConnectDataHandler); +begin + DataHandlers.Remove(ADataHandler as IFSXSimConnectDataHandler); +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. diff --git a/G940LEDControl/Units/FSXSimConnectIntf.pas b/G940LEDControl/Units/FSXSimConnectIntf.pas index 928a9d9..8b9bae5 100644 --- a/G940LEDControl/Units/FSXSimConnectIntf.pas +++ b/G940LEDControl/Units/FSXSimConnectIntf.pas @@ -28,18 +28,32 @@ type procedure Detach(AObserver: IFSXSimConnectObserver); function CreateDefinition: IFSXSimConnectDefinition; - procedure AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler); - procedure RemoveDefinition(ADataHandler: IFSXSimConnectDataHandler); + function AddDefinition(ADefinition: IFSXSimConnectDefinition; ADataHandler: IFSXSimConnectDataHandler): Integer; + procedure RemoveDefinition(ADefinitionID: Cardinal; ADataHandler: IFSXSimConnectDataHandler); end; IFSXSimConnectDefinition = interface ['{F1EAB3B1-0A3D-4B06-A75F-823E15C313B8}'] - procedure AddVariable(AVariableName, AUnitsName: string; ADatumType: SIMCONNECT_DATAType; AEpsilon: Single = 0); - procedure Apply(ASimConnectHandle: THandle; ADefinitionID: Integer); + procedure AddVariable(AVariableName, AUnitsName: string; ADataType: SIMCONNECT_DATAType; AEpsilon: Single = 0); end; + IFSXSimConnectVariable = interface + ['{A41AD003-77C0-4E34-91E3-B0BAADD08FCE}'] + function GetVariableName: string; + function GetUnitsName: string; + function GetDataType: SIMCONNECT_DATAType; + function GetEpsilon: Single; + end; + + + IFSXSimConnectDefinitionAccess = interface + ['{2592534C-0344-4442-8A5F-1AB34B96E1B5}'] + function GetVariableCount: Integer; + function GetVariable(AIndex: Integer): IFSXSimConnectVariable; + end; + const FSX_UNIT_PERCENT = 'percent'; diff --git a/G940LEDControl/Units/G940LEDStateConsumer.pas b/G940LEDControl/Units/G940LEDStateConsumer.pas index caf058c..9df311e 100644 --- a/G940LEDControl/Units/G940LEDStateConsumer.pas +++ b/G940LEDControl/Units/G940LEDStateConsumer.pas @@ -13,7 +13,9 @@ uses const TM_FINDTHROTTLEDEVICE = 2001; - TM_NOTIFY_DEVICESTATE = 2002; + TM_TESTTHROTTLEDEVICE = 2002; + + TM_NOTIFY_DEVICESTATE = 2003; type @@ -21,10 +23,13 @@ type private FDirectInput: IDirectInput8; FThrottleDevice: IDirectInputDevice8; + FTHrottleDeviceGUID: TGUID; protected - procedure MsgFindThrottleDevice(var msg: TOmniMessage); message TM_FINDTHROTTLEDEVICE; + procedure TMFindThrottleDevice(var Msg: TOmniMessage); message TM_FINDTHROTTLEDEVICE; + procedure TMTestThrottleDevice(var Msg: TOmniMessage); message TM_TESTTHROTTLEDEVICE; protected function Initialize: Boolean; override; + procedure Cleanup; override; procedure FindThrottleDevice; procedure FoundThrottleDevice(ADeviceGUID: TGUID); @@ -35,6 +40,7 @@ type property DirectInput: IDirectInput8 read FDirectInput; property ThrottleDevice: IDirectInputDevice8 read FThrottleDevice; + property ThrottleDeviceGUID: TGUID read FTHrottleDeviceGUID; end; @@ -63,23 +69,6 @@ const G940_BUTTONCOUNT = 8; - -(* -type - TRunInMainThreadSetLEDs = class(TOmniWaitableValue, IRunInMainThread) - private - FDevice: IDirectInputDevice8; - FRed: Byte; - FGreen: Byte; - protected - { IRunInMainThread } - procedure Execute; - public - constructor Create(ADevice: IDirectInputDevice8; ARed, AGreen: Byte); - end; -*) - - function EnumDevicesProc(var lpddi: TDIDeviceInstanceW; pvRef: Pointer): BOOL; stdcall; var vendorID: Word; @@ -127,81 +116,13 @@ begin end; -{ -procedure TG940LEDStateConsumer.ResetLEDState; +procedure TG940LEDStateConsumer.Cleanup; begin - FRed := 0; - FGreen := $FF; - - inherited; -end; - - -procedure TG940LEDStateConsumer.LEDStateChanged(ALEDIndex: Integer; AState: TLEDState); - - procedure SetBit(var AMask: Byte; ABit: Integer; ASet: Boolean); inline; - begin - if ASet then - AMask := AMask or (1 shl ABit) - else - AMask := AMask and not (1 shl ABit); - end; - -var - red: Boolean; - green: Boolean; - -begin - red := False; - green := False; - - case AState of - lsGreen: - green := True; - - lsAmber: - begin - red := True; - green := True; - end; - - lsRed: - red := True; - - lsWarning: - begin - red := True; - green := True; - - StartBlinkTimer; - end; - - lsError: - begin - red := True; - - StartBlinkTimer; - end; - end; - - SetBit(FRed, ALEDIndex, red); - SetBit(FGreen, ALEDIndex, green); - - inherited; -end; - -} - -{ -procedure TG940LEDStateConsumer.Changed; -begin - inherited; + inherited Cleanup; if Assigned(ThrottleDevice) then - { Logitech SDK will not change the color outside of the main thread - RunInMainThread(TRunInMainThreadSetLEDs.Create(ThrottleDevice, FRed, FGreen), Destroying); + SetLEDs(ThrottleDevice, 0, $FF); end; -} procedure TG940LEDStateConsumer.FindThrottleDevice; @@ -222,7 +143,10 @@ end; procedure TG940LEDStateConsumer.FoundThrottleDevice(ADeviceGUID: TGUID); begin if DirectInput.CreateDevice(ADeviceGUID, FThrottleDevice, nil) = S_OK then + begin + FTHrottleDeviceGUID := ADeviceGUID; SetDeviceState(DEVICESTATE_FOUND); + end; end; @@ -279,28 +203,22 @@ begin end; -procedure TG940LEDStateConsumer.MsgFindThrottleDevice(var msg: TOmniMessage); +procedure TG940LEDStateConsumer.TMFindThrottleDevice(var Msg: TOmniMessage); begin FindThrottleDevice; end; -{ TRunInMainThreadSetLEDs } -(* -constructor TRunInMainThreadSetLEDs.Create(ADevice: IDirectInputDevice8; ARed, AGreen: Byte); +procedure TG940LEDStateConsumer.TMTestThrottleDevice(var Msg: TOmniMessage); begin - inherited Create; - - FDevice := ADevice; - FRed := ARed; - FGreen := AGreen; + if Assigned(ThrottleDevice) then + begin + if DirectInput.GetDeviceStatus(ThrottleDeviceGUID) = DI_NOTATTACHED then + begin + FThrottleDevice := nil; + SetDeviceState(DEVICESTATE_NOTFOUND); + end; + end; end; - -procedure TRunInMainThreadSetLEDs.Execute; -begin - SetLEDs(FDevice, FRed, FGreen); -end; -*) - end. diff --git a/G940LEDControl/Units/LEDFunction.pas b/G940LEDControl/Units/LEDFunction.pas index 247ced4..98448ad 100644 --- a/G940LEDControl/Units/LEDFunction.pas +++ b/G940LEDControl/Units/LEDFunction.pas @@ -81,8 +81,8 @@ type function GetCurrentState: ILEDStateWorker; virtual; abstract; public - constructor Create; overload; - constructor Create(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); overload; + constructor Create; overload; virtual; + constructor Create(AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings); overload; virtual; destructor Destroy; override; end; @@ -138,7 +138,7 @@ destructor TCustomMultiStateLEDFunction.Destroy; begin FreeAndNil(FStates); - inherited; + inherited Destroy; end; @@ -229,6 +229,8 @@ var begin Result := nil; + if not Assigned(States) then + exit; for state in States do if (state as ICustomLEDState).GetUID = AUID then @@ -263,7 +265,7 @@ destructor TCustomLEDFunctionProvider.Destroy; begin FreeAndNil(FFunctions); - inherited; + inherited Destroy; end; diff --git a/G940LEDControl/Units/LEDFunctionRegistry.pas b/G940LEDControl/Units/LEDFunctionRegistry.pas index c328115..a4605f7 100644 --- a/G940LEDControl/Units/LEDFunctionRegistry.pas +++ b/G940LEDControl/Units/LEDFunctionRegistry.pas @@ -114,7 +114,7 @@ destructor TLEDFunctionRegistry.Destroy; begin FreeAndNil(FProviders); - inherited; + inherited Destroy; end; @@ -155,7 +155,7 @@ destructor TLEDFunctionProviderList.Destroy; begin FreeAndNil(FList); - inherited; + inherited Destroy; end; diff --git a/G940LEDControl/Units/LEDStateConsumer.pas b/G940LEDControl/Units/LEDStateConsumer.pas index a791bc5..09b998a 100644 --- a/G940LEDControl/Units/LEDStateConsumer.pas +++ b/G940LEDControl/Units/LEDStateConsumer.pas @@ -21,13 +21,7 @@ const type - IRunInMainThread = interface(IOmniWaitableValue) - ['{68B8F2F7-ED40-4078-9D99-503D7AFA068B}'] - procedure Execute; - end; - - - TLEDStateConsumer = class(TOmniWorker) + TLEDStateConsumer = class(TOmniWorker, ILEDFunctionObserver) private FButtonWorkers: TInterfaceList; FButtonColors: TInterfaceList; @@ -42,6 +36,9 @@ type property ButtonColors: TInterfaceList read FButtonColors; property HasTickTimer: Boolean read FHasTickTimer; protected + { ILEDFunctionObserver } + procedure ObserveUpdate(Sender: ILEDFunctionWorker); + procedure Changed; virtual; procedure Update; virtual; abstract; protected @@ -171,21 +168,49 @@ begin end; +procedure TLEDStateConsumer.ObserveUpdate(Sender: ILEDFunctionWorker); +begin + Changed; +end; + + procedure TLEDStateConsumer.TMLoadProfile(var Msg: TOmniMessage); var + oldWorkers: TInterfaceList; + oldWorker: IInterface; profile: TProfile; buttonIndex: Integer; + worker: ILEDFunctionWorker; begin profile := Msg.MsgData; - ButtonWorkers.Clear; - for buttonIndex := 0 to Pred(profile.ButtonCount) do - begin - if profile.HasButton(buttonIndex) then - ButtonWorkers.Add(CreateWorker(profile.Buttons[buttonIndex]) as ILEDFunctionWorker) - else - ButtonWorkers.Add(nil); + { Keep a copy of the old workers until all the new ones are initialized, + so we don't get unneccessary SimConnect reconnects. } + oldWorkers := TInterfaceList.Create; + try + for oldWorker in ButtonWorkers do + begin + if Assigned(oldWorker) then + oldWorkers.Add(oldWorker); + end; + + ButtonWorkers.Clear; + + for buttonIndex := 0 to Pred(profile.ButtonCount) do + begin + if profile.HasButton(buttonIndex) then + begin + worker := CreateWorker(profile.Buttons[buttonIndex]) as ILEDFunctionWorker; + ButtonWorkers.Add(worker); + + if Assigned(worker) then + worker.Attach(Self); + end else + ButtonWorkers.Add(nil); + end; + finally + FreeAndNil(oldWorkers); end; Changed; diff --git a/G940LEDControl/Units/Profile.pas b/G940LEDControl/Units/Profile.pas index 66eb916..a4a1c6a 100644 --- a/G940LEDControl/Units/Profile.pas +++ b/G940LEDControl/Units/Profile.pas @@ -188,7 +188,7 @@ destructor TProfile.Destroy; begin FreeAndNil(FButtons); - inherited; + inherited Destroy; end;