@@ -432,6 +432,85 @@ def check_firewall() -> Tuple[bool, str]:
432432 return False , f"Could not check firewall: { str (e )} "
433433
434434
435+ class ExportLuaInstaller :
436+ """Helper class for Export.lua installation"""
437+
438+ @staticmethod
439+ def extract_version (lua_file_path : Path ) -> Optional [str ]:
440+ """Extract STREAMER_VERSION from Export.lua"""
441+ try :
442+ if not lua_file_path .exists ():
443+ return None
444+
445+ with open (lua_file_path , 'r' , encoding = 'utf-8' ) as f :
446+ for line in f :
447+ # Look for: local STREAMER_VERSION = "1.0.13"
448+ if 'STREAMER_VERSION' in line and '=' in line :
449+ # Extract version string
450+ parts = line .split ('=' )
451+ if len (parts ) >= 2 :
452+ version_part = parts [1 ].strip ()
453+ # Remove quotes and comments
454+ version = version_part .strip ('"' ).strip ("'" ).split ('--' )[0 ].strip ()
455+ return version
456+ return None
457+ except Exception as e :
458+ logger .error (f"Error extracting version from { lua_file_path } : { e } " )
459+ return None
460+
461+ @staticmethod
462+ def copy_export_lua (source_path : Path , dest_dir : Path , parent_widget = None ) -> Tuple [bool , str ]:
463+ """
464+ Copy Export.lua to DCS Scripts folder
465+ Returns (success, message)
466+ """
467+ try :
468+ # Ensure destination directory exists
469+ dest_dir .mkdir (parents = True , exist_ok = True )
470+ dest_file = dest_dir / "Export.lua"
471+
472+ # Get source version
473+ source_version = ExportLuaInstaller .extract_version (source_path )
474+ source_version_str = source_version if source_version else "Unknown"
475+
476+ # Check if destination file exists
477+ if dest_file .exists ():
478+ # Get existing version
479+ existing_version = ExportLuaInstaller .extract_version (dest_file )
480+ existing_version_str = existing_version if existing_version else "Unknown"
481+
482+ # Ask user for confirmation
483+ if parent_widget :
484+ reply = QMessageBox .question (
485+ parent_widget ,
486+ "Export.lua Already Exists" ,
487+ f"Export.lua already exists in the DCS Scripts folder.\n \n "
488+ f"<b>Current Version:</b> { existing_version_str } \n "
489+ f"<b>New Version:</b> { source_version_str } \n \n "
490+ f"Do you want to overwrite the existing file?" ,
491+ QMessageBox .StandardButton .Yes | QMessageBox .StandardButton .No
492+ )
493+
494+ if reply == QMessageBox .StandardButton .No :
495+ return False , "Installation cancelled by user"
496+
497+ # Create backup of existing file
498+ if dest_file .exists ():
499+ backup_file = dest_file .with_suffix ('.lua.backup' )
500+ shutil .copy2 (dest_file , backup_file )
501+ logger .info (f"Created backup: { backup_file } " )
502+
503+ # Copy the file
504+ shutil .copy2 (source_path , dest_file )
505+ logger .info (f"Copied Export.lua from { source_path } to { dest_file } " )
506+
507+ return True , f"Export.lua v{ source_version_str } installed successfully"
508+
509+ except Exception as e :
510+ logger .error (f"Error copying Export.lua: { e } " )
511+ return False , f"Failed to copy Export.lua: { str (e )} "
512+
513+
435514class SetupWizard (QWizard ):
436515 """First-time setup wizard"""
437516
@@ -444,6 +523,7 @@ def __init__(self, parent=None):
444523 # Add pages
445524 self .addPage (WelcomePage ())
446525 self .addPage (DCSDetectionPage ())
526+ self .addPage (ExportLuaInstallPage ())
447527 self .addPage (DependencyInstallPage ())
448528 self .addPage (ServerConfigPage ())
449529 self .addPage (CompletionPage ())
@@ -467,6 +547,7 @@ def __init__(self):
467547 "<p>This setup wizard will help you:</p>"
468548 "<ul>"
469549 "<li>Detect your DCS World installation</li>"
550+ "<li>Install Export.lua to DCS Scripts folder</li>"
470551 "<li>Install required Python dependencies</li>"
471552 "<li>Configure server settings</li>"
472553 "<li>Add your first authorized device</li>"
@@ -676,6 +757,179 @@ def __init__(self):
676757 self .registerField ("pow_difficulty" , self .pow_difficulty_spin )
677758
678759
760+ class ExportLuaInstallPage (QWizardPage ):
761+ """Export.lua installation page"""
762+
763+ def __init__ (self ):
764+ super ().__init__ ()
765+ self .setTitle ("Install Export.lua" )
766+ self .setSubTitle ("Copy Export.lua to DCS Scripts folder" )
767+
768+ layout = QVBoxLayout ()
769+
770+ # Info section
771+ info_label = QLabel (
772+ "<p>The Export.lua file is required for DCS to send flight data to the DataPad server.</p>"
773+ "<p>This step will copy Export.lua from the installation folder to your DCS Scripts folder.</p>"
774+ )
775+ info_label .setWordWrap (True )
776+ layout .addWidget (info_label )
777+
778+ # Source file info
779+ source_group = QGroupBox ("Source File" )
780+ source_layout = QFormLayout ()
781+
782+ self .source_path_label = QLabel ("Searching..." )
783+ source_layout .addRow ("Export.lua Location:" , self .source_path_label )
784+
785+ self .source_version_label = QLabel ("Unknown" )
786+ source_layout .addRow ("Version:" , self .source_version_label )
787+
788+ source_group .setLayout (source_layout )
789+ layout .addWidget (source_group )
790+
791+ # Destination info
792+ dest_group = QGroupBox ("Destination" )
793+ dest_layout = QFormLayout ()
794+
795+ self .dest_path_label = QLabel ("Will be determined from DCS installation" )
796+ dest_layout .addRow ("Scripts Folder:" , self .dest_path_label )
797+
798+ self .existing_version_label = QLabel ("Not installed" )
799+ dest_layout .addRow ("Current Version:" , self .existing_version_label )
800+
801+ dest_group .setLayout (dest_layout )
802+ layout .addWidget (dest_group )
803+
804+ # Install button and status
805+ button_layout = QHBoxLayout ()
806+
807+ self .install_btn = QPushButton ("📄 Install Export.lua" )
808+ self .install_btn .setMinimumHeight (40 )
809+ self .install_btn .clicked .connect (self .install_export_lua )
810+ button_layout .addWidget (self .install_btn )
811+
812+ self .skip_btn = QPushButton ("⏭ Skip Installation" )
813+ self .skip_btn .setMinimumHeight (40 )
814+ self .skip_btn .clicked .connect (self .skip_installation )
815+ button_layout .addWidget (self .skip_btn )
816+
817+ layout .addLayout (button_layout )
818+
819+ # Status label
820+ self .status_label = QLabel ("" )
821+ self .status_label .setWordWrap (True )
822+ layout .addWidget (self .status_label )
823+
824+ layout .addStretch ()
825+
826+ self .setLayout (layout )
827+
828+ # Track installation status
829+ self .installed = False
830+
831+ def initializePage (self ):
832+ """Initialize page when shown"""
833+ self .detect_export_lua ()
834+ self .update_destination_info ()
835+
836+ def detect_export_lua (self ):
837+ """Detect Export.lua in current directory"""
838+ # Look for Export.lua in current directory
839+ current_dir = Path .cwd ()
840+ export_lua_path = current_dir / "Export.lua"
841+
842+ if export_lua_path .exists ():
843+ self .source_path_label .setText (str (export_lua_path ))
844+ version = ExportLuaInstaller .extract_version (export_lua_path )
845+ self .source_version_label .setText (version if version else "Unknown" )
846+ self .source_version_label .setStyleSheet ("color: green; font-weight: bold;" )
847+ else :
848+ self .source_path_label .setText ("❌ Not found in current directory" )
849+ self .source_path_label .setStyleSheet ("color: red;" )
850+ self .source_version_label .setText ("N/A" )
851+ self .install_btn .setEnabled (False )
852+ self .status_label .setText (
853+ "⚠ Warning: Export.lua not found in the current directory.\n "
854+ "Please make sure Export.lua is in the same folder as this installer."
855+ )
856+ self .status_label .setStyleSheet ("color: orange; font-weight: bold;" )
857+
858+ def update_destination_info (self ):
859+ """Update destination path and existing version info"""
860+ # Get saved games path from wizard
861+ wizard = self .wizard ()
862+ if hasattr (wizard , 'field' ):
863+ dcs_path = wizard .field ("dcs_path" )
864+ if dcs_path :
865+ # Try to detect saved games from DCS installation
866+ saved_games_path = DCSPathDetector .detect_saved_games ()
867+ if saved_games_path :
868+ scripts_folder = Path (saved_games_path ) / "Scripts"
869+ self .dest_path_label .setText (str (scripts_folder ))
870+
871+ # Check if Export.lua already exists
872+ existing_export = scripts_folder / "Export.lua"
873+ if existing_export .exists ():
874+ existing_version = ExportLuaInstaller .extract_version (existing_export )
875+ self .existing_version_label .setText (existing_version if existing_version else "Unknown" )
876+ self .existing_version_label .setStyleSheet ("color: blue; font-weight: bold;" )
877+ else :
878+ self .existing_version_label .setText ("Not installed" )
879+ self .existing_version_label .setStyleSheet ("color: gray;" )
880+ return
881+
882+ self .dest_path_label .setText ("⚠ DCS Saved Games folder not detected" )
883+ self .dest_path_label .setStyleSheet ("color: orange;" )
884+
885+ def install_export_lua (self ):
886+ """Install Export.lua to DCS Scripts folder"""
887+ try :
888+ # Get paths
889+ source_path = Path .cwd () / "Export.lua"
890+ if not source_path .exists ():
891+ self .status_label .setText ("❌ Error: Export.lua not found in current directory" )
892+ self .status_label .setStyleSheet ("color: red; font-weight: bold;" )
893+ return
894+
895+ saved_games_path = DCSPathDetector .detect_saved_games ()
896+ if not saved_games_path :
897+ self .status_label .setText ("❌ Error: Could not detect DCS Saved Games folder" )
898+ self .status_label .setStyleSheet ("color: red; font-weight: bold;" )
899+ return
900+
901+ scripts_folder = Path (saved_games_path ) / "Scripts"
902+
903+ # Perform installation
904+ success , message = ExportLuaInstaller .copy_export_lua (
905+ source_path ,
906+ scripts_folder ,
907+ parent_widget = self
908+ )
909+
910+ if success :
911+ self .status_label .setText (f"✅ { message } " )
912+ self .status_label .setStyleSheet ("color: green; font-weight: bold;" )
913+ self .installed = True
914+ self .install_btn .setEnabled (False )
915+ self .install_btn .setText ("✅ Installed" )
916+ # Update existing version display
917+ self .update_destination_info ()
918+ else :
919+ self .status_label .setText (f"ℹ { message } " )
920+ self .status_label .setStyleSheet ("color: gray; font-weight: bold;" )
921+
922+ except Exception as e :
923+ logger .error (f"Error in install_export_lua: { e } " )
924+ self .status_label .setText (f"❌ Error: { str (e )} " )
925+ self .status_label .setStyleSheet ("color: red; font-weight: bold;" )
926+
927+ def skip_installation (self ):
928+ """Skip Export.lua installation"""
929+ self .status_label .setText ("ℹ Installation skipped - you can install Export.lua manually later" )
930+ self .status_label .setStyleSheet ("color: gray;" )
931+
932+
679933class CompletionPage (QWizardPage ):
680934 """Setup completion page"""
681935
@@ -784,12 +1038,6 @@ def create_menu_bar(self):
7841038 # File menu
7851039 file_menu = menu_bar .addMenu ("&File" )
7861040
787- run_wizard_action = QAction ("Run Setup &Wizard" , self )
788- run_wizard_action .triggered .connect (self .show_setup_wizard )
789- file_menu .addAction (run_wizard_action )
790-
791- file_menu .addSeparator ()
792-
7931041 exit_action = QAction ("E&xit" , self )
7941042 exit_action .triggered .connect (self .close )
7951043 file_menu .addAction (exit_action )
@@ -811,6 +1059,13 @@ def create_menu_bar(self):
8111059 restart_action .triggered .connect (self .restart_server )
8121060 server_menu .addAction (restart_action )
8131061
1062+ # Setup Wizard menu
1063+ wizard_menu = menu_bar .addMenu ("Setup &Wizard" )
1064+
1065+ run_wizard_action = QAction ("Run Setup &Wizard" , self )
1066+ run_wizard_action .triggered .connect (self .show_setup_wizard )
1067+ wizard_menu .addAction (run_wizard_action )
1068+
8141069 # Help menu
8151070 help_menu = menu_bar .addMenu ("&Help" )
8161071
@@ -898,10 +1153,6 @@ def create_device_management_tab(self):
8981153 remove_btn .clicked .connect (self .remove_selected_device )
8991154 toolbar .addWidget (remove_btn )
9001155
901- qr_btn = QPushButton ("📷 Scan QR Code" )
902- qr_btn .clicked .connect (self .scan_qr_code )
903- toolbar .addWidget (qr_btn )
904-
9051156 generate_token_btn = QPushButton ("🎫 Generate Token" )
9061157 generate_token_btn .clicked .connect (self .generate_registration_token )
9071158 toolbar .addWidget (generate_token_btn )
@@ -1542,15 +1793,6 @@ def remove_selected_device(self):
15421793 else :
15431794 QMessageBox .warning (self , "Error" , "Failed to remove device." )
15441795
1545- def scan_qr_code (self ):
1546- """Scan QR code for device registration (placeholder)"""
1547- QMessageBox .information (
1548- self ,
1549- "QR Code Scanner" ,
1550- "QR Code scanning will be implemented in the next version.\n \n "
1551- "For now, please use 'Generate Token' to create a registration token."
1552- )
1553-
15541796 def generate_registration_token (self ):
15551797 """Generate a registration token and display QR code"""
15561798 try :
0 commit comments