kicad-source/common/project/project_local_settings.cpp
Seth Hillbrand 427d7d2dad Move git password management to local password control
Don't keep passwords in plain text config files that may be checked into
version control.  Passwords stored in system-specific password managers

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18053
2025-01-20 17:33:55 -08:00

567 lines
23 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 CERN
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
* @author Jon Evans <jon@craftyjon.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <lset.h>
#include <project.h>
#include <project/project_local_settings.h>
#include <settings/layer_settings_utils.h>
#include <settings/json_settings_internals.h>
#include <settings/parameters.h>
const int projectLocalSettingsVersion = 5;
PROJECT_LOCAL_SETTINGS::PROJECT_LOCAL_SETTINGS( PROJECT* aProject, const wxString& aFilename ) :
JSON_SETTINGS( aFilename, SETTINGS_LOC::PROJECT, projectLocalSettingsVersion,
/* aCreateIfMissing = */ true, /* aCreateIfDefault = */ false,
/* aWriteFile = */ true ),
m_ActiveLayer( UNDEFINED_LAYER ),
m_ContrastModeDisplay( HIGH_CONTRAST_MODE::NORMAL ),
m_NetColorMode( NET_COLOR_MODE::RATSNEST ),
m_AutoTrackWidth( true ),
m_ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FILLED ),
m_TrackOpacity( 1.0 ),
m_ViaOpacity( 1.0 ),
m_PadOpacity( 1.0 ),
m_ZoneOpacity( 0.6 ),
m_ShapeOpacity( 1.0 ),
m_ImageOpacity( 0.6 ),
m_PcbSelectionFilter(),
m_project( aProject ),
m_wasMigrated( false )
{
// Keep old files around
m_deleteLegacyAfterMigration = false;
m_params.emplace_back( new PARAM_LAMBDA<std::string>( "board.visible_layers",
[&]() -> std::string
{
return m_VisibleLayers.FmtHex();
},
[&]( const std::string& aString )
{
m_VisibleLayers.ParseHex( aString );
},
LSET::AllLayersMask().FmtHex() ) );
m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "board.visible_items",
[&]() -> nlohmann::json
{
nlohmann::json ret = nlohmann::json::array();
for( GAL_LAYER_ID l : m_VisibleItems.Seq() )
{
if( std::optional<VISIBILITY_LAYER> vl = VisibilityLayerFromRenderLayer( l ) )
ret.push_back( VisibilityLayerToString( *vl ) );
}
return ret;
},
[&]( const nlohmann::json& aVal )
{
if( !aVal.is_array() || aVal.empty() )
{
m_VisibleItems = GAL_SET::DefaultVisible();
return;
}
m_VisibleItems &= ~UserVisbilityLayers();
GAL_SET visible;
for( const nlohmann::json& entry : aVal )
{
try
{
std::string vs = entry.get<std::string>();
if( std::optional<GAL_LAYER_ID> l = RenderLayerFromVisbilityString( vs ) )
visible.set( *l );
}
catch( ... )
{
// Non-integer or out of range entry in the array; ignore
}
}
m_VisibleItems |= UserVisbilityLayers() & visible;
},
{} ) );
m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "board.selection_filter",
[&]() -> nlohmann::json
{
nlohmann::json ret;
ret["lockedItems"] = m_PcbSelectionFilter.lockedItems;
ret["footprints"] = m_PcbSelectionFilter.footprints;
ret["text"] = m_PcbSelectionFilter.text;
ret["tracks"] = m_PcbSelectionFilter.tracks;
ret["vias"] = m_PcbSelectionFilter.vias;
ret["pads"] = m_PcbSelectionFilter.pads;
ret["graphics"] = m_PcbSelectionFilter.graphics;
ret["zones"] = m_PcbSelectionFilter.zones;
ret["keepouts"] = m_PcbSelectionFilter.keepouts;
ret["dimensions"] = m_PcbSelectionFilter.dimensions;
ret["otherItems"] = m_PcbSelectionFilter.otherItems;
return ret;
},
[&]( const nlohmann::json& aVal )
{
if( aVal.empty() || !aVal.is_object() )
return;
SetIfPresent( aVal, "lockedItems", m_PcbSelectionFilter.lockedItems );
SetIfPresent( aVal, "footprints", m_PcbSelectionFilter.footprints );
SetIfPresent( aVal, "text", m_PcbSelectionFilter.text );
SetIfPresent( aVal, "tracks", m_PcbSelectionFilter.tracks );
SetIfPresent( aVal, "vias", m_PcbSelectionFilter.vias );
SetIfPresent( aVal, "pads", m_PcbSelectionFilter.pads );
SetIfPresent( aVal, "graphics", m_PcbSelectionFilter.graphics );
SetIfPresent( aVal, "zones", m_PcbSelectionFilter.zones );
SetIfPresent( aVal, "keepouts", m_PcbSelectionFilter.keepouts );
SetIfPresent( aVal, "dimensions", m_PcbSelectionFilter.dimensions );
SetIfPresent( aVal, "otherItems", m_PcbSelectionFilter.otherItems );
},
{
{ "lockedItems", false },
{ "footprints", true },
{ "text", true },
{ "tracks", true },
{ "vias", true },
{ "pads", true },
{ "graphics", true },
{ "zones", true },
{ "keepouts", true },
{ "dimensions", true },
{ "otherItems", true }
} ) );
m_params.emplace_back( new PARAM_ENUM<PCB_LAYER_ID>( "board.active_layer",
&m_ActiveLayer, F_Cu, PCBNEW_LAYER_ID_START, F_Fab ) );
m_params.emplace_back( new PARAM<wxString>( "board.active_layer_preset",
&m_ActiveLayerPreset, "" ) );
m_params.emplace_back( new PARAM_ENUM<HIGH_CONTRAST_MODE>( "board.high_contrast_mode",
&m_ContrastModeDisplay, HIGH_CONTRAST_MODE::NORMAL,
HIGH_CONTRAST_MODE::NORMAL, HIGH_CONTRAST_MODE::HIDDEN ) );
m_params.emplace_back( new PARAM<double>( "board.opacity.tracks", &m_TrackOpacity, 1.0 ) );
m_params.emplace_back( new PARAM<double>( "board.opacity.vias", &m_ViaOpacity, 1.0 ) );
m_params.emplace_back( new PARAM<double>( "board.opacity.pads", &m_PadOpacity, 1.0 ) );
m_params.emplace_back( new PARAM<double>( "board.opacity.zones", &m_ZoneOpacity, 0.6 ) );
m_params.emplace_back( new PARAM<double>( "board.opacity.images", &m_ImageOpacity, 0.6 ) );
m_params.emplace_back( new PARAM<double>( "board.opacity.shapes", &m_ShapeOpacity, 1.0 ) );
m_params.emplace_back( new PARAM_LIST<wxString>( "board.hidden_nets", &m_HiddenNets, {} ) );
m_params.emplace_back( new PARAM_SET<wxString>( "board.hidden_netclasses",
&m_HiddenNetclasses, {} ) );
m_params.emplace_back( new PARAM_ENUM<NET_COLOR_MODE>( "board.net_color_mode",
&m_NetColorMode, NET_COLOR_MODE::RATSNEST, NET_COLOR_MODE::OFF,
NET_COLOR_MODE::ALL ) );
m_params.emplace_back( new PARAM<bool>( "board.auto_track_width",
&m_AutoTrackWidth, true ) );
m_params.emplace_back( new PARAM_ENUM<ZONE_DISPLAY_MODE>( "board.zone_display_mode",
&m_ZoneDisplayMode,
ZONE_DISPLAY_MODE::SHOW_FILLED, ZONE_DISPLAY_MODE::SHOW_FILLED,
ZONE_DISPLAY_MODE::SHOW_TRIANGULATION ) );
m_params.emplace_back( new PARAM<wxString>( "git.repo_username", &m_GitRepoUsername, "" ) );
m_params.emplace_back( new PARAM<wxString>( "git.repo_type", &m_GitRepoType, "" ) );
m_params.emplace_back( new PARAM<wxString>( "git.ssh_key", &m_GitSSHKey, "" ) );
m_params.emplace_back( new PARAM<wxString>( "net_inspector_panel.filter_text",
&m_NetInspectorPanel.filter_text, "" ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.filter_by_net_name",
&m_NetInspectorPanel.filter_by_net_name, true ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.filter_by_netclass",
&m_NetInspectorPanel.filter_by_netclass, true ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.group_by_netclass",
&m_NetInspectorPanel.group_by_netclass, false ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.group_by_constraint",
&m_NetInspectorPanel.group_by_constraint, false ) );
m_params.emplace_back( new PARAM_LIST<wxString>( "net_inspector_panel.custom_group_rules",
&m_NetInspectorPanel.custom_group_rules,
{} ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.show_zero_pad_nets",
&m_NetInspectorPanel.show_zero_pad_nets, false ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.show_unconnected_nets",
&m_NetInspectorPanel.show_unconnected_nets, false ) );
m_params.emplace_back( new PARAM<int>( "net_inspector_panel.sorting_column",
&m_NetInspectorPanel.sorting_column, -1 ) );
m_params.emplace_back( new PARAM<bool>( "net_inspector_panel.sort_ascending",
&m_NetInspectorPanel.sort_order_asc, true ) );
m_params.emplace_back( new PARAM_LIST<int>( "net_inspector_panel.col_order",
&m_NetInspectorPanel.col_order, {} ) );
m_params.emplace_back( new PARAM_LIST<int>( "net_inspector_panel.col_widths",
&m_NetInspectorPanel.col_widths, {} ) );
m_params.emplace_back( new PARAM_LIST<bool>( "net_inspector_panel.col_hidden",
&m_NetInspectorPanel.col_hidden, {} ) );
m_params.emplace_back( new PARAM_LIST<wxString>( "net_inspector_panel.expanded_rows",
&m_NetInspectorPanel.expanded_rows, {} ) );
m_params.emplace_back( new PARAM_LIST<wxString>( "open_jobsets", &m_OpenJobSets, {} ) );
m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "project.files",
[&]() -> nlohmann::json
{
nlohmann::json ret = nlohmann::json::array();
for( PROJECT_FILE_STATE& fileState : m_files )
{
nlohmann::json file;
file["name"] = fileState.fileName;
file["open"] = fileState.open;
nlohmann::json window;
window["maximized"] = fileState.window.maximized;
window["size_x"] = fileState.window.size_x;
window["size_y"] = fileState.window.size_y;
window["pos_x"] = fileState.window.pos_x;
window["pos_y"] = fileState.window.pos_y;
window["display"] = fileState.window.display;
file["window"] = window;
ret.push_back( file );
}
return ret;
},
[&]( const nlohmann::json& aVal )
{
if( !aVal.is_array() || aVal.empty() )
return;
m_files.clear();
for( const nlohmann::json& file : aVal )
{
PROJECT_FILE_STATE fileState;
try
{
SetIfPresent( file, "name", fileState.fileName );
SetIfPresent( file, "open", fileState.open );
SetIfPresent( file, "window.size_x", fileState.window.size_x );
SetIfPresent( file, "window.size_y", fileState.window.size_y );
SetIfPresent( file, "window.pos_x", fileState.window.pos_x );
SetIfPresent( file, "window.pos_y", fileState.window.pos_y );
SetIfPresent( file, "window.maximized", fileState.window.maximized );
SetIfPresent( file, "window.display", fileState.window.display );
m_files.push_back( fileState );
}
catch( ... )
{
// Non-integer or out of range entry in the array; ignore
}
}
},
{
} ) );
m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "schematic.selection_filter",
[&]() -> nlohmann::json
{
nlohmann::json ret;
ret["lockedItems"] = m_SchSelectionFilter.lockedItems;
ret["symbols"] = m_SchSelectionFilter.symbols;
ret["text"] = m_SchSelectionFilter.text;
ret["wires"] = m_SchSelectionFilter.wires;
ret["labels"] = m_SchSelectionFilter.labels;
ret["pins"] = m_SchSelectionFilter.pins;
ret["graphics"] = m_SchSelectionFilter.graphics;
ret["images"] = m_SchSelectionFilter.images;
ret["otherItems"] = m_SchSelectionFilter.otherItems;
return ret;
},
[&]( const nlohmann::json& aVal )
{
if( aVal.empty() || !aVal.is_object() )
return;
SetIfPresent( aVal, "lockedItems", m_SchSelectionFilter.lockedItems );
SetIfPresent( aVal, "symbols", m_SchSelectionFilter.symbols );
SetIfPresent( aVal, "text", m_SchSelectionFilter.text );
SetIfPresent( aVal, "wires", m_SchSelectionFilter.wires );
SetIfPresent( aVal, "labels", m_SchSelectionFilter.labels );
SetIfPresent( aVal, "pins", m_SchSelectionFilter.pins );
SetIfPresent( aVal, "graphics", m_SchSelectionFilter.graphics );
SetIfPresent( aVal, "images", m_SchSelectionFilter.images );
SetIfPresent( aVal, "otherItems", m_SchSelectionFilter.otherItems );
},
{
{ "lockedItems", false },
{ "symbols", true },
{ "text", true },
{ "wires", true },
{ "labels", true },
{ "pins", true },
{ "graphics", true },
{ "images", true },
{ "otherItems", true }
} ) );
registerMigration( 1, 2,
[&]()
{
/**
* Schema version 1 to 2:
* LAYER_PADS and LAYER_ZONES added to visibility controls
*/
std::string ptr( "board.visible_items" );
if( Contains( ptr ) )
{
if( At( ptr ).is_array() )
{
At( ptr ).push_back( LAYER_PADS - GAL_LAYER_ID_START );
At( ptr ).push_back( LAYER_ZONES - GAL_LAYER_ID_START );
}
else
{
At( "board" ).erase( "visible_items" );
}
m_wasMigrated = true;
}
return true;
} );
registerMigration( 2, 3,
[&]()
{
/**
* Schema version 2 to 3:
* Fix issue with object visibility not migrating from legacy, which required
* remapping of GAL_LAYER_ID to match the legacy bitmask ordering.
*/
/// Stores a mapping from old to new enum offset
const std::map<int, int> offsets = {
{ 22, 34 }, // LAYER_PAD_HOLEWALLS
{ 23, 22 }, // LAYER_VIA_HOLES
{ 24, 35 }, // LAYER_VIA_HOLEWALLS
{ 25, 23 }, // LAYER_DRC_ERROR
{ 26, 36 }, // LAYER_DRC_WARNING
{ 27, 37 }, // LAYER_DRC_EXCLUSION
{ 28, 38 }, // LAYER_MARKER_SHADOWS
{ 29, 24 }, // LAYER_DRAWINGSHEET
{ 30, 25 }, // LAYER_GP_OVERLAY
{ 31, 26 }, // LAYER_SELECT_OVERLAY
{ 32, 27 }, // LAYER_PCB_BACKGROUND
{ 33, 28 }, // LAYER_CURSOR
{ 34, 29 }, // LAYER_AUX_ITEM
{ 35, 30 }, // LAYER_DRAW_BITMAPS
{ 39, 32 }, // LAYER_PADS
{ 40, 33 }, // LAYER_ZONES
};
std::string ptr( "board.visible_items" );
if( Contains( ptr ) && At( ptr ).is_array() )
{
nlohmann::json visible = nlohmann::json::array();
for( const nlohmann::json& val : At( ptr ) )
{
try
{
int layer = val.get<int>();
if( offsets.count( layer ) )
visible.push_back( offsets.at( layer ) );
else
visible.push_back( layer );
}
catch( ... )
{
// skip invalid value
}
}
At( "board" )["visible_items"] = visible;
m_wasMigrated = true;
}
return true;
} );
registerMigration( 3, 4,
[&]()
{
// Schema version 3 to 4: LAYER_SHAPES added to visibility controls
std::string ptr( "board.visible_items" );
if( Contains( ptr ) )
{
if( At( ptr ).is_array() )
At( ptr ).push_back( LAYER_SHAPES - GAL_LAYER_ID_START );
else
At( "board" ).erase( "visible_items" );
m_wasMigrated = true;
}
return true;
} );
registerMigration( 4, 5,
[&]()
{
// Schema version 5: use named render layers
std::string ptr( "board.visible_items" );
if( Contains( ptr ) && At( ptr ).is_array() )
{
std::vector<std::string> newLayers;
for( nlohmann::json& entry : At( ptr ) )
{
if( !entry.is_number_integer() )
continue;
if( std::optional<VISIBILITY_LAYER> vl =
VisibilityLayerFromRenderLayer( GAL_LAYER_ID_START + entry.get<int>() ) )
{
newLayers.emplace_back( VisibilityLayerToString( *vl ) );
}
}
At( ptr ) = newLayers;
m_wasMigrated = true;
}
return true;
} );
}
bool PROJECT_LOCAL_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
{
/**
* The normal legacy migration code won't be used for this because the only legacy
* information stored here was stored in board files, so we do that migration when loading
* the board.
*/
return true;
}
bool PROJECT_LOCAL_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
{
wxASSERT( m_project );
Set( "meta.filename",
m_project->GetProjectName() + "." + FILEEXT::ProjectLocalSettingsFileExtension );
// Even if parameters were not modified, we should resave after migration
bool force = aForce || m_wasMigrated;
// If we're actually going ahead and doing the save, the flag that keeps code from doing the
// save should be cleared at this point.
m_wasMigrated = false;
return JSON_SETTINGS::SaveToFile( aDirectory, force );
}
bool PROJECT_LOCAL_SETTINGS::SaveAs( const wxString& aDirectory, const wxString& aFile )
{
Set( "meta.filename", aFile + "." + FILEEXT::ProjectLocalSettingsFileExtension );
SetFilename( aFile );
// If we're actually going ahead and doing the save, the flag that keeps code from doing the
// save should be cleared at this point.
m_wasMigrated = false;
return JSON_SETTINGS::SaveToFile( aDirectory, true );
}
const PROJECT_FILE_STATE* PROJECT_LOCAL_SETTINGS::GetFileState( const wxString& aFileName )
{
auto it = std::find_if( m_files.begin(), m_files.end(),
[&aFileName]( const PROJECT_FILE_STATE &a )
{
return a.fileName == aFileName;
} );
if( it != m_files.end() )
{
return &( *it );
}
return nullptr;
}
void PROJECT_LOCAL_SETTINGS::SaveFileState( const wxString& aFileName,
const WINDOW_SETTINGS* aWindowCfg, bool aOpen )
{
auto it = std::find_if( m_files.begin(), m_files.end(),
[&aFileName]( const PROJECT_FILE_STATE& a )
{
return a.fileName == aFileName;
} );
if( it == m_files.end() )
{
PROJECT_FILE_STATE fileState;
fileState.fileName = aFileName;
fileState.open = false;
fileState.window.maximized = false;
fileState.window.size_x = -1;
fileState.window.size_y = -1;
fileState.window.pos_x = -1;
fileState.window.pos_y = -1;
fileState.window.display = 0;
m_files.push_back( fileState );
it = m_files.end() - 1;
}
( *it ).window = aWindowCfg->state;
( *it ).open = aOpen;
}
void PROJECT_LOCAL_SETTINGS::ClearFileState()
{
m_files.clear();
}