Skip to content

Joystick import export config#3656

Open
meee1 wants to merge 10 commits into
masterfrom
joystickimpexp
Open

Joystick import export config#3656
meee1 wants to merge 10 commits into
masterfrom
joystickimpexp

Conversation

@meee1
Copy link
Copy Markdown
Collaborator

@meee1 meee1 commented Jan 14, 2026

No description provided.

Copy link
Copy Markdown

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 thread ExtLibs/ArduPilot/Joystick/JoystickBase.cs Outdated
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 thread Joystick/JoystickSetup.Designer.cs Outdated
Comment thread Joystick/JoystickSetup.cs Outdated
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 thread Joystick/JoystickSetup.cs
Comment thread ExtLibs/ArduPilot/Joystick/JoystickBase.cs Outdated
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 thread Joystick/JoystickSetup.cs Outdated
Comment thread Joystick/JoystickSetup.cs Outdated
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
Copy Markdown

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
Copy Markdown

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 thread Joystick/JoystickSetup.cs
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.
Comment thread Joystick/JoystickSetup.cs Outdated
Comment thread Joystick/JoystickSetup.cs
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.
Comment thread Joystick/JoystickSetup.cs Outdated
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.
meee1 and others added 3 commits April 13, 2026 16:59
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>
HubMan17 added a commit to HubMan17/MissionPlanner that referenced this pull request Apr 20, 2026
Adds a profile manager on top of ExportConfig/ImportConfig from ArduPilot#3656.
JoystickSetup gets a profile row (dropdown, Create, Delete). Profiles
are stored as .joycfg files in Documents/Mission Planner/joystick_profiles/.
Existing joystick XMLs are migrated to a Default profile on first run.
The Default profile cannot be deleted from the UI. Switching reloads
the form in place; deleting the active profile auto-switches to the
first remaining one.

Hardens the existing Export/Import:
- ExportConfig writes to .tmp and then atomically replaces the target,
  so a killed process cannot leave a corrupt .joycfg.
- loadconfig logs XML deserialization errors via log4net instead of
  silently swallowing them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants