From 1c73b2f4e962fc35fc511dd3232b5cc55b79f015 Mon Sep 17 00:00:00 2001 From: I013442 Date: Fri, 30 Oct 2026 13:21:58 +0100 Subject: [PATCH 1/5] fix(windows): install for current user without elevation Signed-off-by: I013442 --- .../backend_filesystem_program_settings.py | 3 +- windows/ardupilot_methodic_configurator.iss | 102 +++++++++--------- windows/modpath.iss | 5 +- 3 files changed, 57 insertions(+), 53 deletions(-) diff --git a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py index 603fe1dfc..b22302ba0 100644 --- a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py +++ b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py @@ -505,7 +505,8 @@ def get_templates_base_dir() -> str: package_path = importlib_files("ardupilot_methodic_configurator") logging_debug("current script directory1: %s", package_path) if platform_system() == "Windows": - package_path = Path(ProgramSettings._site_config_dir()) + # AI-EDIT (2026-10-30): Match the user-only Windows installer template destination. + package_path = Path(ProgramSettings._user_config_dir()) logging_debug("current script directory2: %s", package_path) logging_debug(_("site_directory: %s"), package_path) diff --git a/windows/ardupilot_methodic_configurator.iss b/windows/ardupilot_methodic_configurator.iss index 3aac983a7..71875b60a 100644 --- a/windows/ardupilot_methodic_configurator.iss +++ b/windows/ardupilot_methodic_configurator.iss @@ -21,7 +21,9 @@ AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} -DefaultDirName={commonpf}\{#MyAppName} +; AI-EDIT (2026-10-30): Install for the current user without elevation. +PrivilegesRequired=lowest +DefaultDirName={userpf}\{#MyAppName} ; DefaultDirName={code:GetDefaultDirName} DefaultGroupName={#MyAppName} LicenseFile=..\LICENSE.md @@ -31,52 +33,53 @@ SolidCompression=yes ChangesEnvironment=yes [InstallDelete] -; Remove main application files -Type: filesandordirs; Name: {commonpf}\{#MyAppName} +; AI-EDIT (2026-10-30): Leave the machine-wide application directory untouched. +; AI-EDIT (2026-10-30): Clean up obsolete templates only in the current user's data. ; Remove ArduCopter template files -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.3.8-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.4.4-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.1-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.2-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.3-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.0-DEV-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.x-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.x-params" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.4.x" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.5.x" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.6.x" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.7.x" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.8.x" -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\X11_plus"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Marmotte5v2"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Chimera7"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ReadyToSkyZD550"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\FETtec-5"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Tarot_X4"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\TarotFY680Hexacopter"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\GazeboIrisWithTargetFollow"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X11+"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X13"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500_V2"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X650_LTE"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Demo32Motor_PeterHall"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ESM450X-Des"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.3.8-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.4.4-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.1-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.2-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.3-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.0-DEV-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.x-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.x-params" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.4.x" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.5.x" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.6.x" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.7.x" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.8.x" +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\X11_plus"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Marmotte5v2"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Chimera7"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ReadyToSkyZD550"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\FETtec-5"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Tarot_X4"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\TarotFY680Hexacopter"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\GazeboIrisWithTargetFollow"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X11+"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X13"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500_V2"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X650_LTE"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Demo32Motor_PeterHall"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ESM450X-Des"; ; Remove ArduPlane template files -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\empty_4.7.x"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\normal_plane"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\empty_4.7.x"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\normal_plane"; ; Remove Heli template files -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\Allister"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\OMP_M4"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\empty_4.7.x"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\Allister"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\OMP_M4"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\empty_4.7.x"; ; Remove Rover template files -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\AION_R1"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\Carisma_SCA-1E"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\empty_4.7.x"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\AION_R1"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\Carisma_SCA-1E"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\empty_4.7.x"; +; AI-EDIT (2026-10-30): Outdated template metadata is also user-scoped. ; Remove Outdated files Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\ArduCopter_configuration_steps.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\ArduPlane_configuration_steps.json"; @@ -84,8 +87,8 @@ Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\con Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\configuration_steps_ArduPlane.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\configuration_steps_Heli.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\configuration_steps_Rover.json"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\version.txt"; -Type: filesandordirs; Name: "{commonappdata}\.ardupilot_methodic_configurator\git_hash.txt"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\version.txt"; +Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\git_hash.txt"; [Languages] Name: "en"; MessagesFile: "compiler:Default.isl" @@ -98,13 +101,14 @@ Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" Name: "modifypath"; Description: "&Add application directory to environmental path for current user. For command line use"; Flags: unchecked checkablealone -Name: "modifypath\system"; Description: Add application directory to environmental path for all users. For command line use; Flags: unchecked +; AI-EDIT (2026-10-30): Do not offer a machine-wide PATH task. [Files] Source: "..\ardupilot_methodic_configurator\dist\ardupilot_methodic_configurator\ardupilot_methodic_configurator.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\ardupilot_methodic_configurator\dist\ardupilot_methodic_configurator\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files -Source: "..\ardupilot_methodic_configurator\vehicle_templates\*.*"; DestDir: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates"; Flags: ignoreversion recursesubdirs createallsubdirs +; AI-EDIT (2026-10-30): Install templates in the current user's configuration directory. +Source: "..\ardupilot_methodic_configurator\vehicle_templates\*.*"; DestDir: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\git_hash.txt"; DestDir: "{app}\_internal\ardupilot_methodic_configurator"; Flags: ignoreversion Source: "..\windows\ardupilot_methodic_configurator.ico"; DestDir: "{app}"; Flags: ignoreversion Source: "..\ardupilot_methodic_configurator\images\*.png"; DestDir: "{app}\_internal\ardupilot_methodic_configurator\images"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -122,9 +126,10 @@ Source: "..\ardupilot_methodic_configurator_command_line_completion.psm1"; DestD Name: "{userappdata}\.ardupilot_methodic_configurator\vehicles"; Flags: uninsneveruninstall [Icons] -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; Tasks: desktopicon; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" +; AI-EDIT (2026-10-30): Desktop and template shortcuts belong to the current user. +Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; Tasks: desktopicon; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" Name: "{group}\Documentation"; Filename: "https://github.com/ArduPilot/MethodicConfigurator/blob/master/USERMANUAL.md" -Name: "{group}\Vehicle Templates"; Filename: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_templates" +Name: "{group}\Vehicle Templates"; Filename: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates" Name: "{group}\ArduPilot Methodic Configurator Forum"; Filename: "https://discuss.ardupilot.org/t/new-ardupilot-methodic-configurator-gui/115038/" Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" @@ -153,11 +158,8 @@ end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssPostInstall then begin - if IsTaskSelected(ModPathName + '\system') then begin - ModPathType := 'system'; - end else begin - ModPathType := 'user'; - end; + // AI-EDIT (2026-10-30): Modify only the current user's PATH. + ModPathType := 'user'; if IsTaskSelected(ModPathName) then ModPath(); diff --git a/windows/modpath.iss b/windows/modpath.iss index a886a20fd..dbe6044aa 100644 --- a/windows/modpath.iss +++ b/windows/modpath.iss @@ -192,8 +192,9 @@ begin appid := '{#emit SetupSetting("AppId")}'; if appid = '' then appid := '{#emit SetupSetting("AppName")}'; regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1'); - RegQueryStringValue(HKLM, regpath, 'Inno Setup: Selected Tasks', regstring); - if regstring = '' then RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); + // AI-EDIT (2026-10-30): Read this user's tasks and remove only their PATH entry. + ModPathType := 'user'; + RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); // check each task; if matches modpath taskname, trigger patch removal if regstring <> '' then begin From 50e5e2e252c718aa14b3fd0e3bbf9879784910da Mon Sep 17 00:00:00 2001 From: I013442 Date: Fri, 30 Oct 2026 13:53:25 +0100 Subject: [PATCH 2/5] chore(windows): remove AI edit annotations Signed-off-by: I013442 --- .../backend_filesystem_program_settings.py | 1 - windows/ardupilot_methodic_configurator.iss | 8 -------- windows/modpath.iss | 1 - 3 files changed, 10 deletions(-) diff --git a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py index b22302ba0..d0173bc91 100644 --- a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py +++ b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py @@ -505,7 +505,6 @@ def get_templates_base_dir() -> str: package_path = importlib_files("ardupilot_methodic_configurator") logging_debug("current script directory1: %s", package_path) if platform_system() == "Windows": - # AI-EDIT (2026-10-30): Match the user-only Windows installer template destination. package_path = Path(ProgramSettings._user_config_dir()) logging_debug("current script directory2: %s", package_path) diff --git a/windows/ardupilot_methodic_configurator.iss b/windows/ardupilot_methodic_configurator.iss index 71875b60a..eeac588a6 100644 --- a/windows/ardupilot_methodic_configurator.iss +++ b/windows/ardupilot_methodic_configurator.iss @@ -21,7 +21,6 @@ AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} -; AI-EDIT (2026-10-30): Install for the current user without elevation. PrivilegesRequired=lowest DefaultDirName={userpf}\{#MyAppName} ; DefaultDirName={code:GetDefaultDirName} @@ -33,9 +32,7 @@ SolidCompression=yes ChangesEnvironment=yes [InstallDelete] -; AI-EDIT (2026-10-30): Leave the machine-wide application directory untouched. -; AI-EDIT (2026-10-30): Clean up obsolete templates only in the current user's data. ; Remove ArduCopter template files Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.3.8-params" Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.4.4-params" @@ -79,7 +76,6 @@ Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehi Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\AION_R1"; Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\Carisma_SCA-1E"; Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\empty_4.7.x"; -; AI-EDIT (2026-10-30): Outdated template metadata is also user-scoped. ; Remove Outdated files Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\ArduCopter_configuration_steps.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\ArduPlane_configuration_steps.json"; @@ -101,13 +97,11 @@ Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" Name: "modifypath"; Description: "&Add application directory to environmental path for current user. For command line use"; Flags: unchecked checkablealone -; AI-EDIT (2026-10-30): Do not offer a machine-wide PATH task. [Files] Source: "..\ardupilot_methodic_configurator\dist\ardupilot_methodic_configurator\ardupilot_methodic_configurator.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\ardupilot_methodic_configurator\dist\ardupilot_methodic_configurator\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files -; AI-EDIT (2026-10-30): Install templates in the current user's configuration directory. Source: "..\ardupilot_methodic_configurator\vehicle_templates\*.*"; DestDir: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\git_hash.txt"; DestDir: "{app}\_internal\ardupilot_methodic_configurator"; Flags: ignoreversion Source: "..\windows\ardupilot_methodic_configurator.ico"; DestDir: "{app}"; Flags: ignoreversion @@ -126,7 +120,6 @@ Source: "..\ardupilot_methodic_configurator_command_line_completion.psm1"; DestD Name: "{userappdata}\.ardupilot_methodic_configurator\vehicles"; Flags: uninsneveruninstall [Icons] -; AI-EDIT (2026-10-30): Desktop and template shortcuts belong to the current user. Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; Tasks: desktopicon; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" Name: "{group}\Documentation"; Filename: "https://github.com/ArduPilot/MethodicConfigurator/blob/master/USERMANUAL.md" Name: "{group}\Vehicle Templates"; Filename: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates" @@ -158,7 +151,6 @@ end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssPostInstall then begin - // AI-EDIT (2026-10-30): Modify only the current user's PATH. ModPathType := 'user'; if IsTaskSelected(ModPathName) then diff --git a/windows/modpath.iss b/windows/modpath.iss index dbe6044aa..c46fcdb01 100644 --- a/windows/modpath.iss +++ b/windows/modpath.iss @@ -192,7 +192,6 @@ begin appid := '{#emit SetupSetting("AppId")}'; if appid = '' then appid := '{#emit SetupSetting("AppName")}'; regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1'); - // AI-EDIT (2026-10-30): Read this user's tasks and remove only their PATH entry. ModPathType := 'user'; RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); From 2f1c83bc05878fa900d02358c86461e148210609 Mon Sep 17 00:00:00 2001 From: I013442 Date: Fri, 30 Oct 2026 14:06:25 +0100 Subject: [PATCH 3/5] feat(windows): default to user installation with optional all-users mode Signed-off-by: I013442 --- .../backend_filesystem_program_settings.py | 2 + windows/ardupilot_methodic_configurator.iss | 93 ++++++++++--------- windows/modpath.iss | 11 ++- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py index d0173bc91..38c19ae92 100644 --- a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py +++ b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py @@ -506,6 +506,8 @@ def get_templates_base_dir() -> str: logging_debug("current script directory1: %s", package_path) if platform_system() == "Windows": package_path = Path(ProgramSettings._user_config_dir()) + if not (package_path / "vehicle_templates").is_dir(): + package_path = Path(ProgramSettings._site_config_dir()) logging_debug("current script directory2: %s", package_path) logging_debug(_("site_directory: %s"), package_path) diff --git a/windows/ardupilot_methodic_configurator.iss b/windows/ardupilot_methodic_configurator.iss index eeac588a6..7bef353a7 100644 --- a/windows/ardupilot_methodic_configurator.iss +++ b/windows/ardupilot_methodic_configurator.iss @@ -22,7 +22,9 @@ AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} PrivilegesRequired=lowest -DefaultDirName={userpf}\{#MyAppName} +PrivilegesRequiredOverridesAllowed=dialog +UsePreviousPrivileges=no +DefaultDirName={autopf}\{#MyAppName} ; DefaultDirName={code:GetDefaultDirName} DefaultGroupName={#MyAppName} LicenseFile=..\LICENSE.md @@ -34,48 +36,48 @@ ChangesEnvironment=yes [InstallDelete] ; Remove ArduCopter template files -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.3.8-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.4.4-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.1-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.2-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.3-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.0-DEV-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.x-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.x-params" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.4.x" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.5.x" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.6.x" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.7.x" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.8.x" -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\X11_plus"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Marmotte5v2"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Chimera7"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ReadyToSkyZD550"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\FETtec-5"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Tarot_X4"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\TarotFY680Hexacopter"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\GazeboIrisWithTargetFollow"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X11+"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X13"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500_V2"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X650_LTE"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Demo32Motor_PeterHall"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ESM450X-Des"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.3.8-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.4.4-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.1-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.2-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.3-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.0-DEV-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.5.x-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.6.x-params" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.4.x" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.5.x" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.6.x" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.7.x" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\empty_4.8.x" +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\X11_plus"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Marmotte5v2"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Chimera7"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ReadyToSkyZD550"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\FETtec-5"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Tarot_X4"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\TarotFY680Hexacopter"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\GazeboIrisWithTargetFollow"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X11+"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Hoverit_X13"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X500_V2"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Holybro_X650_LTE"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\Demo32Motor_PeterHall"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\ESM450X-Des"; ; Remove ArduPlane template files -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\empty_4.7.x"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\normal_plane"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\empty_4.7.x"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduPlane\normal_plane"; ; Remove Heli template files -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\Allister"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\OMP_M4"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\empty_4.7.x"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\Allister"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\OMP_M4"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\Heli\empty_4.7.x"; ; Remove Rover template files -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\AION_R1"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\Carisma_SCA-1E"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\empty_4.7.x"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\AION_R1"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\Carisma_SCA-1E"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\Rover\empty_4.7.x"; ; Remove Outdated files Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\ArduCopter_configuration_steps.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\ArduPlane_configuration_steps.json"; @@ -83,8 +85,8 @@ Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\con Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\configuration_steps_ArduPlane.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\configuration_steps_Heli.json"; Type: filesandordirs; Name: "{app}\_internal\ardupilot_methodic_configurator\configuration_steps_Rover.json"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\version.txt"; -Type: filesandordirs; Name: "{userappdata}\.ardupilot_methodic_configurator\git_hash.txt"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\version.txt"; +Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\git_hash.txt"; [Languages] Name: "en"; MessagesFile: "compiler:Default.isl" @@ -96,13 +98,13 @@ Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" -Name: "modifypath"; Description: "&Add application directory to environmental path for current user. For command line use"; Flags: unchecked checkablealone +Name: "modifypath"; Description: "&Add application directory to PATH for command line use"; Flags: unchecked checkablealone [Files] Source: "..\ardupilot_methodic_configurator\dist\ardupilot_methodic_configurator\ardupilot_methodic_configurator.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "..\ardupilot_methodic_configurator\dist\ardupilot_methodic_configurator\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files -Source: "..\ardupilot_methodic_configurator\vehicle_templates\*.*"; DestDir: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "..\ardupilot_methodic_configurator\vehicle_templates\*.*"; DestDir: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "..\git_hash.txt"; DestDir: "{app}\_internal\ardupilot_methodic_configurator"; Flags: ignoreversion Source: "..\windows\ardupilot_methodic_configurator.ico"; DestDir: "{app}"; Flags: ignoreversion Source: "..\ardupilot_methodic_configurator\images\*.png"; DestDir: "{app}\_internal\ardupilot_methodic_configurator\images"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -120,9 +122,9 @@ Source: "..\ardupilot_methodic_configurator_command_line_completion.psm1"; DestD Name: "{userappdata}\.ardupilot_methodic_configurator\vehicles"; Flags: uninsneveruninstall [Icons] -Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; Tasks: desktopicon; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; Tasks: desktopicon; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" Name: "{group}\Documentation"; Filename: "https://github.com/ArduPilot/MethodicConfigurator/blob/master/USERMANUAL.md" -Name: "{group}\Vehicle Templates"; Filename: "{userappdata}\.ardupilot_methodic_configurator\vehicle_templates" +Name: "{group}\Vehicle Templates"; Filename: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates" Name: "{group}\ArduPilot Methodic Configurator Forum"; Filename: "https://discuss.ardupilot.org/t/new-ardupilot-methodic-configurator-gui/115038/" Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; WorkingDir: "{userappdata}\.ardupilot_methodic_configurator"; IconFilename: "{app}\ardupilot_methodic_configurator.ico"; Parameters: "--language {language}" @@ -151,7 +153,10 @@ end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssPostInstall then begin - ModPathType := 'user'; + if IsAdminInstallMode then + ModPathType := 'system' + else + ModPathType := 'user'; if IsTaskSelected(ModPathName) then ModPath(); diff --git a/windows/modpath.iss b/windows/modpath.iss index c46fcdb01..5a748ad64 100644 --- a/windows/modpath.iss +++ b/windows/modpath.iss @@ -183,6 +183,8 @@ var i: Integer; taskname: String; regpath: String; + regroot: Integer; + installedPath: String; regstring: String; appid: String; begin @@ -192,8 +194,15 @@ begin appid := '{#emit SetupSetting("AppId")}'; if appid = '' then appid := '{#emit SetupSetting("AppName")}'; regpath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\'+appid+'_is1'); + regroot := HKCU; ModPathType := 'user'; - RegQueryStringValue(HKCU, regpath, 'Inno Setup: Selected Tasks', regstring); + // Match the installation directory rather than the uninstaller's elevation. + if RegQueryStringValue(HKLM, regpath, 'Inno Setup: App Path', installedPath) and + (CompareText(installedPath, ExpandConstant('{app}')) = 0) then begin + regroot := HKLM; + ModPathType := 'system'; + end; + RegQueryStringValue(regroot, regpath, 'Inno Setup: Selected Tasks', regstring); // check each task; if matches modpath taskname, trigger patch removal if regstring <> '' then begin From 5d3cfe8e6d8352a6e93454cdf407c577d4dfdbaf Mon Sep 17 00:00:00 2001 From: "Dr.-Ing. Amilcar do Carmo Lucas" Date: Fri, 30 Oct 2026 20:48:17 +0100 Subject: [PATCH 4/5] fix(windows): preserve install scope and restore template lookup Keep existing all-users installations in their original scope during upgrades by removing UsePreviousPrivileges=no. Fresh installations continue to default to per-user mode and offer an all-users option. Require system_vehicle_components_template.json to be a regular file before selecting the per-user templates directory. Fall back to shared templates when uninstall leaves an empty directory or personal presets behind. Restore stale application-file cleanup in the default installation directory for the selected scope. When uninstalling an all-users installation with the PATH task selected, also remove matching entries from the current user's PATH left by older installers. Replace the machine-dependent Windows lookup test with isolated cases covering absent, empty, leftover-only, invalid-marker, and installed template directories. Add a scope-switch regression that verifies shared templates are selected after per-user uninstall while personal presets remain intact. Update the Windows build workflow's Inno Setup download and cache key from 6.7.1 to 6.7.4. Validation: - 291 settings, component-template, and vehicle-project tests passed. - ty, Ruff, Pylint, and pre-commit checks passed for the application, test, and installer changes. - Windows compilation and upgrade/uninstall testing remain unverified. Signed-off-by: Dr.-Ing. Amilcar do Carmo Lucas --- .github/workflows/build_windows_macos.yml | 4 +- .../backend_filesystem_program_settings.py | 3 +- ...est_backend_filesystem_program_settings.py | 92 +++++++++++++++---- windows/ardupilot_methodic_configurator.iss | 3 +- windows/modpath.iss | 8 +- 5 files changed, 87 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build_windows_macos.yml b/.github/workflows/build_windows_macos.yml index 412e4a5da..54f4eb8b8 100644 --- a/.github/workflows/build_windows_macos.yml +++ b/.github/workflows/build_windows_macos.yml @@ -60,11 +60,11 @@ jobs: uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: installer.exe - key: innosetup-6.7.1-exe + key: innosetup-6.7.4-exe - name: Download Inno Setup installer if: steps.cache-innosetup.outputs.cache-hit != 'true' - run: curl -L -o installer.exe https://github.com/jrsoftware/issrc/releases/download/is-6_7_1/innosetup-6.7.1.exe + run: curl -L -o installer.exe https://github.com/jrsoftware/issrc/releases/download/is-6_7_4/innosetup-6.7.4.exe - name: Install Inno Setup run: | diff --git a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py index 38c19ae92..3a95fdbfb 100644 --- a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py +++ b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py @@ -506,7 +506,8 @@ def get_templates_base_dir() -> str: logging_debug("current script directory1: %s", package_path) if platform_system() == "Windows": package_path = Path(ProgramSettings._user_config_dir()) - if not (package_path / "vehicle_templates").is_dir(): + # Uninstall can leave personal presets behind; require an installer-owned file. + if not (package_path / "vehicle_templates" / "system_vehicle_components_template.json").is_file(): package_path = Path(ProgramSettings._site_config_dir()) logging_debug("current script directory2: %s", package_path) diff --git a/tests/test_backend_filesystem_program_settings.py b/tests/test_backend_filesystem_program_settings.py index 53e959b11..09eacd6dd 100755 --- a/tests/test_backend_filesystem_program_settings.py +++ b/tests/test_backend_filesystem_program_settings.py @@ -13,6 +13,7 @@ import json import platform from os import path as os_path +from pathlib import Path from unittest.mock import MagicMock, mock_open, patch import pytest @@ -1321,29 +1322,84 @@ def test_get_templates_base_dir_uses_script_dir_on_macos(self) -> None: assert result == "/mock/package/path/vehicle_templates" mock_importlib.assert_called_once_with("ardupilot_methodic_configurator") - def test_get_templates_base_dir_uses_site_config_on_windows(self) -> None: + @pytest.mark.parametrize("user_templates_state", ["absent", "empty", "user-only", "marker-directory", "installed"]) + def test_get_templates_base_dir_selects_installed_templates_on_windows( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, user_templates_state: str + ) -> None: """ - Templates base directory uses site config directory on Windows. + Select an installed template set without relying on the machine's configuration. - GIVEN: A Windows platform environment + GIVEN: An all-users template installation and possibly a per-user template directory WHEN: The templates base directory is requested - THEN: The site configuration directory should be used as the base - """ - # Arrange: Mock Windows platform and site config + THEN: Per-user templates take precedence only when their installer-owned file exists + """ + monkeypatch.delenv("AMC_COMPONENT_TEMPLATES_DIR_TEST", raising=False) + user_config = tmp_path / "user" + site_config = tmp_path / "site" + user_templates = user_config / "vehicle_templates" + site_templates = site_config / "vehicle_templates" + site_templates.mkdir(parents=True) + (site_templates / "system_vehicle_components_template.json").write_text("{}", encoding="utf-8") + if user_templates_state != "absent": + user_templates.mkdir(parents=True) + if user_templates_state == "user-only": + (user_templates / "user_vehicle_components_template.json").write_text("{}", encoding="utf-8") + elif user_templates_state == "marker-directory": + (user_templates / "system_vehicle_components_template.json").mkdir() + elif user_templates_state == "installed": + (user_templates / "system_vehicle_components_template.json").write_text("{}", encoding="utf-8") + with ( - patch("ardupilot_methodic_configurator.backend_filesystem_program_settings.platform_system") as mock_platform, patch( - "ardupilot_methodic_configurator.backend_filesystem_program_settings.ProgramSettings._site_config_dir" - ) as mock_site_config, + "ardupilot_methodic_configurator.backend_filesystem_program_settings.platform_system", return_value="Windows" + ), + patch.object(ProgramSettings, "_user_config_dir", return_value=str(user_config)) as mock_user_config, + patch.object(ProgramSettings, "_site_config_dir", return_value=str(site_config)) as mock_site_config, ): - mock_platform.return_value = "Windows" - mock_site_config.return_value = "C:\\Program Files\\App" - - # Act: Get templates base directory result = ProgramSettings.get_templates_base_dir() - # Assert: Windows path logic is used correctly - mock_site_config.assert_called_once() - # Use os.path.join to handle platform-specific path separators - expected_path = os_path.join("C:\\Program Files\\App", "vehicle_templates") - assert result == expected_path + mock_user_config.assert_called_once_with() + if user_templates_state == "installed": + assert result == str(user_templates) + mock_site_config.assert_not_called() + else: + assert result == str(site_templates) + mock_site_config.assert_called_once_with() + + def test_scope_switch_finds_shared_templates_after_per_user_uninstall( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """ + Find all-users templates when uninstall leaves personal presets behind. + + GIVEN: Per-user templates, personal presets, and an all-users installation + WHEN: Uninstall removes the per-user installer's template file + THEN: Lookup switches to the shared installation and leaves personal presets intact + """ + monkeypatch.delenv("AMC_COMPONENT_TEMPLATES_DIR_TEST", raising=False) + user_config = tmp_path / "user" + site_config = tmp_path / "site" + user_templates = user_config / "vehicle_templates" + site_templates = site_config / "vehicle_templates" + user_templates.mkdir(parents=True) + site_templates.mkdir(parents=True) + user_system_file = user_templates / "system_vehicle_components_template.json" + user_system_file.write_text("{}", encoding="utf-8") + (site_templates / user_system_file.name).write_text("{}", encoding="utf-8") + personal_presets = user_templates / "user_vehicle_components_template.json" + personal_presets.write_text('{"Flight Controller": [{"name": "Personal preset"}]}', encoding="utf-8") + with ( + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.platform_system", return_value="Windows" + ), + patch.object(ProgramSettings, "_user_config_dir", return_value=str(user_config)), + patch.object(ProgramSettings, "_site_config_dir", return_value=str(site_config)), + ): + assert ProgramSettings.get_templates_base_dir() == str(user_templates) + + user_system_file.unlink() + + assert ProgramSettings.get_templates_base_dir() == str(site_templates) + assert json.loads(personal_presets.read_text(encoding="utf-8")) == { + "Flight Controller": [{"name": "Personal preset"}] + } diff --git a/windows/ardupilot_methodic_configurator.iss b/windows/ardupilot_methodic_configurator.iss index 7bef353a7..be3f4c578 100644 --- a/windows/ardupilot_methodic_configurator.iss +++ b/windows/ardupilot_methodic_configurator.iss @@ -23,7 +23,6 @@ AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} PrivilegesRequired=lowest PrivilegesRequiredOverridesAllowed=dialog -UsePreviousPrivileges=no DefaultDirName={autopf}\{#MyAppName} ; DefaultDirName={code:GetDefaultDirName} DefaultGroupName={#MyAppName} @@ -34,6 +33,8 @@ SolidCompression=yes ChangesEnvironment=yes [InstallDelete] +; Remove stale application files from the default directory for this scope. +Type: filesandordirs; Name: "{autopf}\{#MyAppName}" ; Remove ArduCopter template files Type: filesandordirs; Name: "{autoappdata}\.ardupilot_methodic_configurator\vehicle_templates\ArduCopter\diatone_taycan_mxc\4.3.8-params" diff --git a/windows/modpath.iss b/windows/modpath.iss index 5a748ad64..a0cd8a6a4 100644 --- a/windows/modpath.iss +++ b/windows/modpath.iss @@ -210,8 +210,14 @@ begin MPExplode(aSelectedTasks, regstring, ','); if GetArrayLength(aSelectedTasks) > 0 then begin for i := 0 to GetArrayLength(aSelectedTasks)-1 do begin - if comparetext(aSelectedTasks[i], taskname) = 0 then + if comparetext(aSelectedTasks[i], taskname) = 0 then begin ModPath(); + // Older all-users installers added the directory to the user PATH. + if regroot = HKLM then begin + ModPathType := 'user'; + ModPath(); + end; + end; end; end; end; From 3c07cddde7fd0aade6781533ec323f11552f8eef Mon Sep 17 00:00:00 2001 From: "Dr.-Ing. Amilcar do Carmo Lucas" Date: Sun, 1 Nov 2026 20:12:56 +0100 Subject: [PATCH 5/5] fix(windows): preserve component presets across install scopes Look for personal component presets in the other Windows installation scope when the selected scope has no preset file. This keeps presets available after switching between per-user and all-users installations. Keep an existing preset file in the selected scope authoritative, including when it is empty or invalid. When presets are recovered from the other scope, retain them when saving to the selected scope. Document the PowerShell completion module paths for both the default per-user installation and an all-users installation. Add tests for scope switches in both directions, subsequent saves, selected-file precedence, and non-Windows and test-directory behavior. Validation: - 142 filesystem tests passed. - Pre-commit, Pylint, Ruff, ty, Pyright, and Markdown lint passed. Signed-off-by: Dr.-Ing. Amilcar do Carmo Lucas --- INSTALL.md | 12 +- .../backend_filesystem_program_settings.py | 16 +++ .../backend_filesystem_vehicle_components.py | 6 + ...est_backend_filesystem_program_settings.py | 116 ++++++++++++++++++ 4 files changed, 148 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 9ff0055ad..957eabb35 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -184,12 +184,20 @@ For PowerShell (MS Windows) autocompletion, run this command in PowerShell: notepad $PROFILE ``` -And add this line to the file: +For the default per-user installation, add this line to the file: ```powershell -Import-Module "C:\Program Files (x86)\ardupilot_methodic_configurator\ardupilot_methodic_configurator_command_line_completion.psm1" +Import-Module "$env:LOCALAPPDATA\Programs\ardupilot_methodic_configurator\ardupilot_methodic_configurator_command_line_completion.psm1" ``` +For an all-users installation on 64-bit Windows, use: + +```powershell +Import-Module "${env:ProgramFiles(x86)}\ardupilot_methodic_configurator\ardupilot_methodic_configurator_command_line_completion.psm1" +``` + +If you selected a different installation directory, use the module path in that directory instead. + ## Verify Installer Security and Integrity **Why verify?** The ArduPilot Methodic Configurator installer is cryptographically signed and includes SLSA Level 3 provenance attestations. diff --git a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py index 3a95fdbfb..d876f2911 100644 --- a/ardupilot_methodic_configurator/backend_filesystem_program_settings.py +++ b/ardupilot_methodic_configurator/backend_filesystem_program_settings.py @@ -514,6 +514,22 @@ def get_templates_base_dir() -> str: logging_debug(_("site_directory: %s"), package_path) return str(package_path / "vehicle_templates") + @staticmethod + def get_other_windows_templates_base_dir(templates_dir: str) -> str | None: + """Find the other installation scope without creating its configuration directory.""" + if platform_system() != "Windows" or os.environ.get("AMC_COMPONENT_TEMPLATES_DIR_TEST"): + return None + user_dir = ( + Path(user_config_dir(".ardupilot_methodic_configurator", appauthor=False, roaming=True)) / "vehicle_templates" + ) + site_dir = Path(site_config_dir(".ardupilot_methodic_configurator", appauthor=False)) / "vehicle_templates" + selected = os_path.normcase(os_path.abspath(templates_dir)) + if selected == os_path.normcase(os_path.abspath(user_dir)): + return str(site_dir) + if selected == os_path.normcase(os_path.abspath(site_dir)): + return str(user_dir) + return None + @staticmethod def get_vehicles_default_dir() -> Path: settings_directory = Path(ProgramSettings._user_config_dir()) diff --git a/ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py b/ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py index c92e68095..ad52f5eb5 100644 --- a/ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py +++ b/ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py @@ -123,6 +123,12 @@ def _load_user_templates(self) -> dict[str, list[dict]]: templates_filename = "user_vehicle_components_template.json" templates_dir = ProgramSettings.get_templates_base_dir() filepath = os_path.join(templates_dir, templates_filename) + if not os_path.exists(filepath): + other_templates_dir = ProgramSettings.get_other_windows_templates_base_dir(templates_dir) + if other_templates_dir: + other_filepath = os_path.join(other_templates_dir, templates_filename) + if os_path.isfile(other_filepath): + filepath = other_filepath templates = {} try: diff --git a/tests/test_backend_filesystem_program_settings.py b/tests/test_backend_filesystem_program_settings.py index 09eacd6dd..58d80a52f 100755 --- a/tests/test_backend_filesystem_program_settings.py +++ b/tests/test_backend_filesystem_program_settings.py @@ -19,6 +19,7 @@ import pytest from ardupilot_methodic_configurator.backend_filesystem_program_settings import ProgramSettings, normalize_path +from ardupilot_methodic_configurator.backend_filesystem_vehicle_components import VehicleComponents # pylint: disable=too-many-lines,protected-access,redefined-outer-name,too-few-public-methods @@ -1403,3 +1404,118 @@ def test_scope_switch_finds_shared_templates_after_per_user_uninstall( assert json.loads(personal_presets.read_text(encoding="utf-8")) == { "Flight Controller": [{"name": "Personal preset"}] } + + +class TestComponentPresetScope: + """Preserve personal component presets when Windows installation scope changes.""" + + @pytest.mark.parametrize("old_scope", ["user", "site"]) + def test_saved_presets_survive_scope_switch_and_subsequent_save( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, old_scope: str + ) -> None: + """ + Load existing presets after switching either way, then retain them on the next save. + + GIVEN a personal preset saved in an installation scope that is later uninstalled + WHEN another scope is installed and the user loads and saves component templates + THEN the old preset remains available and the new scope owns the updated file + """ + monkeypatch.delenv("AMC_COMPONENT_TEMPLATES_DIR_TEST", raising=False) + user_config, site_config = tmp_path / "user", tmp_path / "site" + user_templates, site_templates = user_config / "vehicle_templates", site_config / "vehicle_templates" + user_templates.mkdir(parents=True) + site_templates.mkdir(parents=True) + marker = "system_vehicle_components_template.json" + (site_templates / marker).write_text("{}", encoding="utf-8") + if old_scope == "user": + (user_templates / marker).write_text("{}", encoding="utf-8") + old_templates = user_templates if old_scope == "user" else site_templates + new_templates = site_templates if old_scope == "user" else user_templates + preset = {"Flight Controller": [{"name": "Personal preset", "data": {"Manufacturer": "Example"}}]} + added = {"Battery": [{"name": "New preset", "data": {"Cells": 4}}]} + + with ( + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.platform_system", return_value="Windows" + ), + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.user_config_dir", + return_value=str(user_config), + ), + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.site_config_dir", + return_value=str(site_config), + ), + ): + components = VehicleComponents() + assert not components.save_component_templates_to_file(preset)[0] + assert ProgramSettings.get_templates_base_dir() == str(old_templates) + old_file = old_templates / "user_vehicle_components_template.json" + assert json.loads(old_file.read_text(encoding="utf-8")) == preset + + (old_templates / marker).unlink() + (new_templates / marker).write_text("{}", encoding="utf-8") + assert ProgramSettings.get_templates_base_dir() == str(new_templates) + assert components.load_component_templates()["Flight Controller"] == [ + {**preset["Flight Controller"][0], "is_user_modified": True} + ] + assert not components.save_component_templates_to_file(added)[0] + assert json.loads((new_templates / old_file.name).read_text(encoding="utf-8")) == {**preset, **added} + assert json.loads(old_file.read_text(encoding="utf-8")) == preset + + old_file.write_text('{"Ignored": [{"name": "Old"}]}', encoding="utf-8") + assert components.load_component_templates() == { + "Flight Controller": [{**preset["Flight Controller"][0], "is_user_modified": True}], + "Battery": [{**added["Battery"][0], "is_user_modified": True}], + } + + @pytest.mark.parametrize("selected_content", ["{}", "{invalid json"]) + def test_selected_scope_file_remains_authoritative( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, selected_content: str + ) -> None: + """An empty or invalid selected file does not restore presets from the other scope.""" + monkeypatch.delenv("AMC_COMPONENT_TEMPLATES_DIR_TEST", raising=False) + user_config, site_config = tmp_path / "user", tmp_path / "site" + user_templates, site_templates = user_config / "vehicle_templates", site_config / "vehicle_templates" + user_templates.mkdir(parents=True) + site_templates.mkdir(parents=True) + (user_templates / "system_vehicle_components_template.json").write_text("{}", encoding="utf-8") + (user_templates / "user_vehicle_components_template.json").write_text(selected_content, encoding="utf-8") + (site_templates / "user_vehicle_components_template.json").write_text( + '{"Flight Controller": [{"name": "Old preset"}]}', encoding="utf-8" + ) + with ( + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.platform_system", return_value="Windows" + ), + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.user_config_dir", + return_value=str(user_config), + ), + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.site_config_dir", + return_value=str(site_config), + ), + ): + assert VehicleComponents().load_component_templates() == {} + + @pytest.mark.parametrize(("platform_name", "override"), [("Linux", False), ("Windows", True)]) + def test_alternate_scope_is_disabled_outside_normal_windows_installs( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, platform_name: str, override: bool + ) -> None: + """Test overrides and non-Windows installations never read another scope's presets.""" + if override: + monkeypatch.setenv("AMC_COMPONENT_TEMPLATES_DIR_TEST", str(tmp_path)) + else: + monkeypatch.delenv("AMC_COMPONENT_TEMPLATES_DIR_TEST", raising=False) + with ( + patch( + "ardupilot_methodic_configurator.backend_filesystem_program_settings.platform_system", + return_value=platform_name, + ), + patch("ardupilot_methodic_configurator.backend_filesystem_program_settings.user_config_dir") as user_dir, + patch("ardupilot_methodic_configurator.backend_filesystem_program_settings.site_config_dir") as site_dir, + ): + assert ProgramSettings.get_other_windows_templates_base_dir(str(tmp_path)) is None + user_dir.assert_not_called() + site_dir.assert_not_called()