Skip to content

Joystick import export config#3656

Open
meee1 wants to merge 7 commits intomasterfrom
joystickimpexp
Open

Joystick import export config#3656
meee1 wants to merge 7 commits intomasterfrom
joystickimpexp

Conversation

@meee1
Copy link
Collaborator

@meee1 meee1 commented Jan 14, 2026

No description provided.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment on lines +48 to +49
this.but_export = new MissionPlanner.Controls.MyButton();
this.but_import = new MissionPlanner.Controls.MyButton();
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button naming is inconsistent with the existing convention in this file. Existing buttons use uppercase prefix "BUT_" (BUT_enable, BUT_save), but the new buttons use lowercase "but_" (but_export, but_import). For consistency, these should be renamed to BUT_export and BUT_import to match the established naming pattern.

Copilot uses AI. Check for mistakes.
Comment on lines +544 to +546
MainV2.joystick.saveconfig();
MainV2.joystick.ExportConfig(sfd.FileName);
}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The but_export_Click method does not check if MainV2.joystick is null before calling saveconfig() and ExportConfig(). If the joystick is not initialized, this will result in a NullReferenceException. Add a null check similar to the one in BUT_save_Click to ensure a joystick is configured before attempting to export.

Copilot uses AI. Check for mistakes.
Comment on lines +1310 to +1359
// compress all joystick config files from user data directory
string userDataDir = Settings.GetUserDataDirectory();

// find all joystick config files (buttons and axis)
var buttonFiles = Directory.GetFiles(userDataDir, "joystickbutton*.xml", SearchOption.TopDirectoryOnly);
var axisFiles = Directory.GetFiles(userDataDir, "joystickaxis*.xml", SearchOption.TopDirectoryOnly);

var allFiles = buttonFiles.Concat(axisFiles).ToList();

if (allFiles.Count == 0)
{
throw new FileNotFoundException("No joystick configuration files found in " + userDataDir);
}

if (File.Exists(fileName))
File.Delete(fileName);

// create archive with all config files
using (var archive = ZipFile.Open(fileName, ZipArchiveMode.Create))
{
foreach (var file in allFiles)
{
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}

public void ImportConfig(string fileName)
{
// decompress all joystick config files to user data directory
string userDataDir = Settings.GetUserDataDirectory();

if (!File.Exists(fileName))
{
throw new FileNotFoundException("Archive file not found: " + fileName);
}

// extract all files from archive to user data directory
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
}
}
}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ExportConfig and ImportConfig methods lack error handling for file I/O operations. If operations like File.Delete, ZipFile.Open, or ZipFile.OpenRead fail, the exceptions will propagate unhandled to the UI layer. Consider wrapping these operations in try-catch blocks and providing user-friendly error messages, or at minimum document that calling code should handle these exceptions.

Suggested change
// compress all joystick config files from user data directory
string userDataDir = Settings.GetUserDataDirectory();
// find all joystick config files (buttons and axis)
var buttonFiles = Directory.GetFiles(userDataDir, "joystickbutton*.xml", SearchOption.TopDirectoryOnly);
var axisFiles = Directory.GetFiles(userDataDir, "joystickaxis*.xml", SearchOption.TopDirectoryOnly);
var allFiles = buttonFiles.Concat(axisFiles).ToList();
if (allFiles.Count == 0)
{
throw new FileNotFoundException("No joystick configuration files found in " + userDataDir);
}
if (File.Exists(fileName))
File.Delete(fileName);
// create archive with all config files
using (var archive = ZipFile.Open(fileName, ZipArchiveMode.Create))
{
foreach (var file in allFiles)
{
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}
public void ImportConfig(string fileName)
{
// decompress all joystick config files to user data directory
string userDataDir = Settings.GetUserDataDirectory();
if (!File.Exists(fileName))
{
throw new FileNotFoundException("Archive file not found: " + fileName);
}
// extract all files from archive to user data directory
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
}
}
}
try
{
// compress all joystick config files from user data directory
string userDataDir = Settings.GetUserDataDirectory();
// find all joystick config files (buttons and axis)
var buttonFiles = Directory.GetFiles(userDataDir, "joystickbutton*.xml", SearchOption.TopDirectoryOnly);
var axisFiles = Directory.GetFiles(userDataDir, "joystickaxis*.xml", SearchOption.TopDirectoryOnly);
var allFiles = buttonFiles.Concat(axisFiles).ToList();
if (allFiles.Count == 0)
{
throw new FileNotFoundException("No joystick configuration files found in " + userDataDir);
}
if (File.Exists(fileName))
File.Delete(fileName);
// create archive with all config files
using (var archive = ZipFile.Open(fileName, ZipArchiveMode.Create))
{
foreach (var file in allFiles)
{
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}
catch (FileNotFoundException)
{
// Preserve explicit FileNotFoundException messages for callers
throw;
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is InvalidDataException)
{
throw new IOException($"Failed to export joystick configuration to '{fileName}': {ex.Message}", ex);
}
}
public void ImportConfig(string fileName)
{
try
{
// decompress all joystick config files to user data directory
string userDataDir = Settings.GetUserDataDirectory();
if (!File.Exists(fileName))
{
throw new FileNotFoundException("Archive file not found: " + fileName);
}
// extract all files from archive to user data directory
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
}
}
}
}
catch (FileNotFoundException)
{
// Preserve explicit FileNotFoundException messages for callers
throw;
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is InvalidDataException)
{
throw new IOException($"Failed to import joystick configuration from '{fileName}': {ex.Message}", ex);
}
Copilot uses AI. Check for mistakes.
Comment on lines +557 to +564
MainV2.joystick.ImportConfig(ofd.FileName);
MainV2.joystick.loadconfig();
CustomMessageBox.Show("Please reopen joystick for changes to take effect");
this.BeginInvoke((Action)delegate ()
{
this.Close();
((Form)this.Parent).Close();
});
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The but_import_Click method lacks error handling for exceptions that might be thrown by ImportConfig() or loadconfig(). If these operations fail (e.g., corrupted archive, invalid file format, file access denied), the user will see an unhandled exception. Wrap the operations in a try-catch block and display user-friendly error messages using CustomMessageBox.Show.

Suggested change
MainV2.joystick.ImportConfig(ofd.FileName);
MainV2.joystick.loadconfig();
CustomMessageBox.Show("Please reopen joystick for changes to take effect");
this.BeginInvoke((Action)delegate ()
{
this.Close();
((Form)this.Parent).Close();
});
try
{
MainV2.joystick.ImportConfig(ofd.FileName);
MainV2.joystick.loadconfig();
CustomMessageBox.Show("Please reopen joystick for changes to take effect");
this.BeginInvoke((Action)delegate ()
{
this.Close();
((Form)this.Parent).Close();
});
}
catch (Exception ex)
{
CustomMessageBox.Show("Failed to import joystick configuration.\n\nError: " + ex.Message, "Import Error");
}
Copilot uses AI. Check for mistakes.
meee1 and others added 7 commits January 15, 2026 11:32
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • Joystick/JoystickSetup.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +538 to +559
private void but_export_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Joystick config files (*.joycfg)|*.joycfg|All files (*.*)|*.*";
if (sfd.ShowDialog() == DialogResult.OK)
{
if (MainV2.joystick == null)
{
CustomMessageBox.Show("No joystick configuration is available to export.", "Export Joystick Config", MessageBoxButtons.OK);
return;
}

try
{
MainV2.joystick.saveconfig();
MainV2.joystick.ExportConfig(sfd.FileName);
}
catch (Exception ex)
{
CustomMessageBox.Show("Failed to export joystick configuration:\n" + ex.Message, "Export Joystick Config", MessageBoxButtons.OK);
}
}
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider providing a more informative success message to the user after a successful export, including the file path where the configuration was saved.

Copilot uses AI. Check for mistakes.


namespace MissionPlanner.Joystick
using Microsoft.CodeAnalysis.CSharp.Syntax;
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import statement appears to be unused. The code doesn't use any types from Microsoft.CodeAnalysis.CSharp.Syntax namespace. Consider removing it to avoid unnecessary dependencies.

Suggested change
using Microsoft.CodeAnalysis.CSharp.Syntax;
Copilot uses AI. Check for mistakes.
Comment on lines +552 to +553
MainV2.joystick.saveconfig();
MainV2.joystick.ExportConfig(sfd.FileName);
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MainV2.joystick object should be checked for null before calling saveconfig. If MainV2.joystick is null at line 544, the code will throw a NullReferenceException at line 552.

Copilot uses AI. Check for mistakes.
{
if (MainV2.joystick == null)
{
CustomMessageBox.Show("No joystick is currently initialized. Please initialize a joystick before importing a configuration.");
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message title should be "Import Joystick Config" rather than "Export Joystick Config" since this is in the import button click handler.

Suggested change
CustomMessageBox.Show("No joystick is currently initialized. Please initialize a joystick before importing a configuration.");
CustomMessageBox.Show("No joystick is currently initialized. Please initialize a joystick before importing a configuration.", "Import Joystick Config", MessageBoxButtons.OK);
Copilot uses AI. Check for mistakes.
Comment on lines +1349 to +1355
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name validation is insufficient. The method should validate that the entry name doesn't contain directory traversal sequences (like ".." or absolute paths) before extracting to prevent potential security vulnerabilities where a malicious archive could write files outside the intended directory.

Suggested change
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
// normalize base directory once for traversal checks
var fullUserDataDir = Path.GetFullPath(userDataDir);
if (!fullUserDataDir.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
!fullUserDataDir.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
{
fullUserDataDir += Path.DirectorySeparatorChar;
}
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
var entryName = entry.Name;
// basic validation to prevent directory traversal and invalid paths
if (string.IsNullOrEmpty(entryName))
continue;
if (Path.IsPathRooted(entryName))
continue;
if (entryName.Contains(".."))
continue;
if (entryName.Contains(Path.DirectorySeparatorChar.ToString()) ||
entryName.Contains(Path.AltDirectorySeparatorChar.ToString()))
continue;
string outputPath = Path.Combine(userDataDir, entryName);
string fullOutputPath = Path.GetFullPath(outputPath);
// ensure the resolved path is still under the intended directory
if (!fullOutputPath.StartsWith(fullUserDataDir, StringComparison.OrdinalIgnoreCase))
continue;
entry.ExtractToFile(fullOutputPath, true);
Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants