-
Notifications
You must be signed in to change notification settings - Fork 8.3k
/
CascadiaSettings.cpp
1215 lines (1088 loc) · 46.4 KB
/
CascadiaSettings.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include <argb.h>
#include <conattrs.hpp>
#include <io.h>
#include <fcntl.h>
#include "CascadiaSettings.h"
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
#include "Utils.h"
#include "LibraryResources.h"
#include "PowershellCoreProfileGenerator.h"
#include "WslDistroGenerator.h"
#include "AzureCloudShellGenerator.h"
#include "CascadiaSettings.g.cpp"
using namespace ::Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Control;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace winrt::Windows::Foundation::Collections;
using namespace Microsoft::Console;
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_PATH{ L"ms-appx:///ProfileIcons/" };
static constexpr std::wstring_view PACKAGED_PROFILE_ICON_EXTENSION{ L".png" };
static constexpr std::wstring_view DEFAULT_LINUX_ICON_GUID{ L"{9acb9455-ca41-5af7-950f-6bca1bc9722f}" };
// make sure this matches defaults.json.
static constexpr std::wstring_view DEFAULT_WINDOWS_POWERSHELL_GUID{ L"{61c54bbd-c2c6-5271-96e7-009a87ff44bf}" };
CascadiaSettings::CascadiaSettings() :
CascadiaSettings(true)
{
}
// Constructor Description:
// - Creates a new settings object. If addDynamicProfiles is true, we'll
// automatically add the built-in profile generators to our list of profile
// generators. Set this to `false` for unit testing.
// Arguments:
// - addDynamicProfiles: if true, we'll add the built-in DPGs.
CascadiaSettings::CascadiaSettings(const bool addDynamicProfiles) :
_globals{ winrt::make_self<implementation::GlobalAppSettings>() },
_allProfiles{ winrt::single_threaded_observable_vector<Model::Profile>() },
_activeProfiles{ winrt::single_threaded_observable_vector<Model::Profile>() },
_warnings{ winrt::single_threaded_vector<SettingsLoadWarnings>() },
_deserializationErrorMessage{ L"" },
_defaultTerminals{ winrt::single_threaded_observable_vector<Model::DefaultTerminal>() },
_currentDefaultTerminal{ nullptr }
{
if (addDynamicProfiles)
{
_profileGenerators.emplace_back(std::make_unique<PowershellCoreProfileGenerator>());
_profileGenerators.emplace_back(std::make_unique<WslDistroGenerator>());
_profileGenerators.emplace_back(std::make_unique<AzureCloudShellGenerator>());
}
}
CascadiaSettings::CascadiaSettings(winrt::hstring json) :
CascadiaSettings(false)
{
const auto jsonString{ til::u16u8(json) };
_ParseJsonString(jsonString, false);
LayerJson(_userSettings);
_ValidateSettings();
}
winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings::Copy() const
{
// dynamic profile generators added by default
auto settings{ winrt::make_self<CascadiaSettings>() };
settings->_globals = _globals->Copy();
for (auto warning : _warnings)
{
settings->_warnings.Append(warning);
}
settings->_loadError = _loadError;
settings->_deserializationErrorMessage = _deserializationErrorMessage;
settings->_userSettingsString = _userSettingsString;
settings->_userSettings = _userSettings;
settings->_defaultSettings = _defaultSettings;
settings->_defaultTerminals = _defaultTerminals;
settings->_currentDefaultTerminal = _currentDefaultTerminal;
_CopyProfileInheritanceTree(settings);
return *settings;
}
// Method Description:
// - Copies the inheritance tree for profiles and hooks them up to a clone CascadiaSettings
// Arguments:
// - cloneSettings: the CascadiaSettings we're copying the inheritance tree to
// Return Value:
// - <none>
void CascadiaSettings::_CopyProfileInheritanceTree(winrt::com_ptr<CascadiaSettings>& cloneSettings) const
{
// Our profiles inheritance graph doesn't have a formal root.
// However, if we create a dummy Profile, and set _profiles as the parent,
// we now have a root. So we'll do just that, then copy the inheritance graph
// from the dummyRoot.
auto dummyRootSource{ winrt::make_self<Profile>() };
for (const auto& profile : _allProfiles)
{
winrt::com_ptr<Profile> profileImpl;
profileImpl.copy_from(winrt::get_self<Profile>(profile));
Profile::InsertParentHelper(dummyRootSource, profileImpl);
}
auto dummyRootClone{ winrt::make_self<Profile>() };
std::unordered_map<void*, winrt::com_ptr<Profile>> visited{};
if (_userDefaultProfileSettings)
{
// profile.defaults must be saved to CascadiaSettings
// So let's do that manually first, and add that to visited
cloneSettings->_userDefaultProfileSettings = Profile::CopySettings(_userDefaultProfileSettings);
visited[_userDefaultProfileSettings.get()] = cloneSettings->_userDefaultProfileSettings;
}
Profile::CloneInheritanceGraph(dummyRootSource, dummyRootClone, visited);
// All of the parents of the dummy root clone are _profiles.
// Get the parents and add them to the settings clone.
const auto cloneParents{ dummyRootClone->Parents() };
for (const auto& profile : cloneParents)
{
cloneSettings->_allProfiles.Append(*profile);
if (!profile->Hidden())
{
cloneSettings->_activeProfiles.Append(*profile);
}
}
}
// Method Description:
// - Finds a profile that matches the given GUID. If there is no profile in this
// settings object that matches, returns nullptr.
// Arguments:
// - profileGuid: the GUID of the profile to return.
// Return Value:
// - a non-ownership pointer to the profile matching the given guid, or nullptr
// if there is no match.
winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::FindProfile(winrt::guid profileGuid) const noexcept
{
const winrt::guid guid{ profileGuid };
for (const auto& profile : _allProfiles)
{
try
{
if (profile.Guid() == guid)
{
return profile;
}
}
CATCH_LOG();
}
return nullptr;
}
// Method Description:
// - Returns an iterable collection of all of our Profiles.
// Arguments:
// - <none>
// Return Value:
// - an iterable collection of all of our Profiles.
IObservableVector<winrt::Microsoft::Terminal::Settings::Model::Profile> CascadiaSettings::AllProfiles() const noexcept
{
return _allProfiles;
}
// Method Description:
// - Returns an iterable collection of all of our non-hidden Profiles.
// Arguments:
// - <none>
// Return Value:
// - an iterable collection of all of our Profiles.
IObservableVector<winrt::Microsoft::Terminal::Settings::Model::Profile> CascadiaSettings::ActiveProfiles() const noexcept
{
return _activeProfiles;
}
// Method Description:
// - Returns the globally configured keybindings
// Arguments:
// - <none>
// Return Value:
// - the globally configured keybindings
winrt::Microsoft::Terminal::Settings::Model::ActionMap CascadiaSettings::ActionMap() const noexcept
{
return _globals->ActionMap();
}
// Method Description:
// - Get a reference to our global settings
// Arguments:
// - <none>
// Return Value:
// - a reference to our global settings
winrt::Microsoft::Terminal::Settings::Model::GlobalAppSettings CascadiaSettings::GlobalSettings() const
{
return *_globals;
}
// Method Description:
// - Get a reference to our profiles.defaults object
// Arguments:
// - <none>
// Return Value:
// - a reference to our profile.defaults object
winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::ProfileDefaults() const
{
return *_userDefaultProfileSettings;
}
// Method Description:
// - Create a new profile based off the default profile settings.
// Arguments:
// - <none>
// Return Value:
// - a reference to the new profile
winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::CreateNewProfile()
{
if (_allProfiles.Size() == std::numeric_limits<uint32_t>::max())
{
// Shouldn't really happen
return nullptr;
}
winrt::hstring newName{};
for (uint32_t candidateIndex = 0; candidateIndex < _allProfiles.Size() + 1; candidateIndex++)
{
// There is a theoretical unsigned integer wraparound, which is OK
newName = fmt::format(L"Profile {}", _allProfiles.Size() + 1 + candidateIndex);
if (std::none_of(begin(_allProfiles), end(_allProfiles), [&](auto&& profile) { return profile.Name() == newName; }))
{
break;
}
}
auto newProfile{ _userDefaultProfileSettings->CreateChild() };
newProfile->Name(newName);
_allProfiles.Append(*newProfile);
return *newProfile;
}
// Method Description:
// - Duplicate a new profile based off another profile's settings
// - This differs from Profile::Copy because it also copies over settings
// that were not defined in the json (for example, settings that were
// defined in one of the parents)
// - This will not duplicate settings that were defined in profiles.defaults
// however, because we do not want the json blob generated from the new profile
// to contain those settings
// Arguments:
// - source: the Profile object we are duplicating (must not be null)
// Return Value:
// - a reference to the new profile
winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::DuplicateProfile(Model::Profile source)
{
THROW_HR_IF_NULL(E_INVALIDARG, source);
winrt::com_ptr<Profile> duplicated;
if (_userDefaultProfileSettings)
{
duplicated = _userDefaultProfileSettings->CreateChild();
}
else
{
duplicated = winrt::make_self<Profile>();
}
_allProfiles.Append(*duplicated);
if (!source.Hidden())
{
_activeProfiles.Append(*duplicated);
}
winrt::hstring newName{ fmt::format(L"{} ({})", source.Name(), RS_(L"CopySuffix")) };
// Check if this name already exists and if so, append a number
for (uint32_t candidateIndex = 0; candidateIndex < _allProfiles.Size() + 1; ++candidateIndex)
{
if (std::none_of(begin(_allProfiles), end(_allProfiles), [&](auto&& profile) { return profile.Name() == newName; }))
{
break;
}
// There is a theoretical unsigned integer wraparound, which is OK
newName = fmt::format(L"{} ({} {})", source.Name(), RS_(L"CopySuffix"), candidateIndex + 2);
}
duplicated->Name(winrt::hstring(newName));
#define DUPLICATE_SETTING_MACRO(settingName) \
if (source.Has##settingName() || \
(source.##settingName##OverrideSource() != nullptr && source.##settingName##OverrideSource().Origin() != OriginTag::ProfilesDefaults)) \
{ \
duplicated->##settingName(source.##settingName()); \
}
#define DUPLICATE_APPEARANCE_SETTING_MACRO(settingName) \
if (source.DefaultAppearance().Has##settingName() || \
(source.DefaultAppearance().##settingName##OverrideSource() != nullptr && source.DefaultAppearance().##settingName##OverrideSource().SourceProfile().Origin() != OriginTag::ProfilesDefaults)) \
{ \
duplicated->DefaultAppearance().##settingName(source.DefaultAppearance().##settingName()); \
}
DUPLICATE_SETTING_MACRO(Hidden);
DUPLICATE_SETTING_MACRO(Icon);
DUPLICATE_SETTING_MACRO(CloseOnExit);
DUPLICATE_SETTING_MACRO(TabTitle);
DUPLICATE_SETTING_MACRO(TabColor);
DUPLICATE_SETTING_MACRO(SuppressApplicationTitle);
DUPLICATE_SETTING_MACRO(UseAcrylic);
DUPLICATE_SETTING_MACRO(AcrylicOpacity);
DUPLICATE_SETTING_MACRO(ScrollState);
DUPLICATE_SETTING_MACRO(FontFace);
DUPLICATE_SETTING_MACRO(FontSize);
DUPLICATE_SETTING_MACRO(FontWeight);
DUPLICATE_SETTING_MACRO(Padding);
DUPLICATE_SETTING_MACRO(Commandline);
DUPLICATE_SETTING_MACRO(StartingDirectory);
DUPLICATE_SETTING_MACRO(AntialiasingMode);
DUPLICATE_SETTING_MACRO(ForceFullRepaintRendering);
DUPLICATE_SETTING_MACRO(SoftwareRendering);
DUPLICATE_SETTING_MACRO(HistorySize);
DUPLICATE_SETTING_MACRO(SnapOnInput);
DUPLICATE_SETTING_MACRO(AltGrAliasing);
DUPLICATE_SETTING_MACRO(BellStyle);
DUPLICATE_APPEARANCE_SETTING_MACRO(ColorSchemeName);
DUPLICATE_APPEARANCE_SETTING_MACRO(Foreground);
DUPLICATE_APPEARANCE_SETTING_MACRO(Background);
DUPLICATE_APPEARANCE_SETTING_MACRO(SelectionBackground);
DUPLICATE_APPEARANCE_SETTING_MACRO(CursorColor);
DUPLICATE_APPEARANCE_SETTING_MACRO(PixelShaderPath);
DUPLICATE_APPEARANCE_SETTING_MACRO(BackgroundImagePath);
DUPLICATE_APPEARANCE_SETTING_MACRO(BackgroundImageOpacity);
DUPLICATE_APPEARANCE_SETTING_MACRO(BackgroundImageStretchMode);
DUPLICATE_APPEARANCE_SETTING_MACRO(BackgroundImageAlignment);
DUPLICATE_APPEARANCE_SETTING_MACRO(RetroTerminalEffect);
DUPLICATE_APPEARANCE_SETTING_MACRO(CursorShape);
DUPLICATE_APPEARANCE_SETTING_MACRO(CursorHeight);
// UnfocusedAppearance is treated as a single setting,
// but requires a little more legwork to duplicate properly
if (source.HasUnfocusedAppearance() ||
(source.UnfocusedAppearanceOverrideSource() != nullptr && source.UnfocusedAppearanceOverrideSource().Origin() != OriginTag::ProfilesDefaults))
{
// First, get a com_ptr to the source's unfocused appearance
// We need this to be able to call CopyAppearance (it is alright to simply call CopyAppearance here
// instead of needing a separate function like DuplicateAppearance since UnfocusedAppearance is treated
// as a single setting)
winrt::com_ptr<AppearanceConfig> sourceUnfocusedAppearanceImpl;
sourceUnfocusedAppearanceImpl.copy_from(winrt::get_self<AppearanceConfig>(source.UnfocusedAppearance()));
// Get a weak ref to the duplicate profile so we can provide a source profile to the new UnfocusedAppearance
// we are about to create
const auto weakRefToDuplicated = weak_ref<Model::Profile>(*duplicated);
auto duplicatedUnfocusedAppearanceImpl = AppearanceConfig::CopyAppearance(sourceUnfocusedAppearanceImpl, weakRefToDuplicated);
// Make sure to add the default appearance of the duplicated profile as a parent to the duplicate's UnfocusedAppearance
winrt::com_ptr<AppearanceConfig> duplicatedDefaultAppearanceImpl;
duplicatedDefaultAppearanceImpl.copy_from(winrt::get_self<AppearanceConfig>(duplicated->DefaultAppearance()));
duplicatedUnfocusedAppearanceImpl->InsertParent(duplicatedDefaultAppearanceImpl);
// Finally, set the duplicate's UnfocusedAppearance
duplicated->UnfocusedAppearance(*duplicatedUnfocusedAppearanceImpl);
}
if (source.HasConnectionType())
{
duplicated->ConnectionType(source.ConnectionType());
}
return *duplicated;
}
// Method Description:
// - Gets our list of warnings we found during loading. These are things that we
// knew were bad when we called `_ValidateSettings` last.
// Return Value:
// - a reference to our list of warnings.
IVectorView<winrt::Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> CascadiaSettings::Warnings()
{
return _warnings.GetView();
}
void CascadiaSettings::ClearWarnings()
{
_warnings.Clear();
}
void CascadiaSettings::AppendWarning(SettingsLoadWarnings warning)
{
_warnings.Append(warning);
}
winrt::Windows::Foundation::IReference<winrt::Microsoft::Terminal::Settings::Model::SettingsLoadErrors> CascadiaSettings::GetLoadingError()
{
return _loadError;
}
winrt::hstring CascadiaSettings::GetSerializationErrorMessage()
{
return _deserializationErrorMessage;
}
// Method Description:
// - Attempts to validate this settings structure. If there are critical errors
// found, they'll be thrown as a SettingsLoadError. Non-critical errors, such
// as not finding the default profile, will only result in an error. We'll add
// all these warnings to our list of warnings, and the application can chose
// to display these to the user.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_ValidateSettings()
{
// Make sure to check that profiles exists at all first and foremost:
_ValidateProfilesExist();
// Re-order profiles so that all profiles from the user's settings appear
// before profiles that _weren't_ in the user profiles.
_ReorderProfilesToMatchUserSettingsOrder();
// Remove hidden profiles _after_ re-ordering. The re-ordering uses the raw
// json, and will get confused if the profile isn't in the list.
_UpdateActiveProfiles();
// Then do some validation on the profiles. The order of these does not
// terribly matter.
_ValidateNoDuplicateProfiles();
// Resolve the default profile before we validate that it exists.
_ResolveDefaultProfile();
_ValidateDefaultProfileExists();
// Ensure that all the profile's color scheme names are
// actually the names of schemes we've parsed. If the scheme doesn't exist,
// just use the hardcoded defaults
_ValidateAllSchemesExist();
// Ensure all profile's with specified images resources have valid file path.
// This validates icons and background images.
_ValidateMediaResources();
// TODO:GH#2548 ensure there's at least one key bound. Display a warning if
// there's _NO_ keys bound to any actions. That's highly irregular, and
// likely an indication of an error somehow.
// GH#3522 - With variable args to keybindings, it's possible that a user
// set a keybinding without all the required args for an action. Display a
// warning if an action didn't have a required arg.
// This will also catch other keybinding warnings, like from GH#4239
_ValidateKeybindings();
_ValidateColorSchemesInCommands();
_ValidateNoGlobalsKey();
}
// Method Description:
// - Checks if the settings contain profiles at all. As we'll need to have some
// profiles at all, we'll throw an error if there aren't any profiles.
void CascadiaSettings::_ValidateProfilesExist()
{
const bool hasProfiles = _allProfiles.Size() > 0;
if (!hasProfiles)
{
// Throw an exception. This is an invalid state, and we want the app to
// be able to gracefully use the default settings.
// We can't add the warning to the list of warnings here, because this
// object is not going to be returned at any point.
throw SettingsException(Microsoft::Terminal::Settings::Model::SettingsLoadErrors::NoProfiles);
}
}
// Method Description:
// - Resolves the "defaultProfile", which can be a profile name, to a GUID
// and stores it back to the globals.
void CascadiaSettings::_ResolveDefaultProfile()
{
const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
if (!unparsedDefaultProfile.empty())
{
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
auto defaultProfileGuid{ til::coalesce_value(maybeParsedDefaultProfile, winrt::guid{}) };
GlobalSettings().DefaultProfile(defaultProfileGuid);
}
}
// Method Description:
// - Checks if the "defaultProfile" is set to one of the profiles we
// actually have. If the value is unset, or the value is set to something that
// doesn't exist in the list of profiles, we'll arbitrarily pick the first
// profile to use temporarily as the default.
// - Appends a SettingsLoadWarnings::MissingDefaultProfile to our list of
// warnings if we failed to find the default.
void CascadiaSettings::_ValidateDefaultProfileExists()
{
const winrt::guid defaultProfileGuid{ GlobalSettings().DefaultProfile() };
const bool nullDefaultProfile = defaultProfileGuid == winrt::guid{};
bool defaultProfileNotInProfiles = true;
for (const auto& profile : _allProfiles)
{
if (profile.Guid() == defaultProfileGuid)
{
defaultProfileNotInProfiles = false;
break;
}
}
if (nullDefaultProfile || defaultProfileNotInProfiles)
{
_warnings.Append(Microsoft::Terminal::Settings::Model::SettingsLoadWarnings::MissingDefaultProfile);
// Use the first profile as the new default
// _temporarily_ set the default profile to the first profile. Because
// we're adding a warning, this settings change won't be re-serialized.
GlobalSettings().DefaultProfile(_allProfiles.GetAt(0).Guid());
}
}
// Method Description:
// - Checks to make sure there aren't any duplicate profiles in the list of
// profiles. If so, we'll remove the subsequent entries (temporarily), as they
// won't be accessible anyways.
// - Appends a SettingsLoadWarnings::DuplicateProfile to our list of warnings if
// we find any such duplicate.
void CascadiaSettings::_ValidateNoDuplicateProfiles()
{
bool foundDupe = false;
std::vector<uint32_t> indicesToDelete;
std::set<winrt::guid> uniqueGuids;
// Try collecting all the unique guids. If we ever encounter a guid that's
// already in the set, then we need to delete that profile.
for (uint32_t i = 0; i < _allProfiles.Size(); i++)
{
if (!uniqueGuids.insert(_allProfiles.GetAt(i).Guid()).second)
{
foundDupe = true;
indicesToDelete.push_back(i);
}
}
// Remove all the duplicates we've marked
// Walk backwards, so we don't accidentally shift any of the elements
for (auto iter = indicesToDelete.rbegin(); iter != indicesToDelete.rend(); iter++)
{
_allProfiles.RemoveAt(*iter);
}
if (foundDupe)
{
_warnings.Append(Microsoft::Terminal::Settings::Model::SettingsLoadWarnings::DuplicateProfile);
}
}
// Method Description:
// - Re-orders the list of profiles to match what the user would expect them to
// be. Orders profiles to be in the ordering { [profiles from user settings],
// [default profiles that weren't in the user profiles]}.
// - Does not set any warnings.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_ReorderProfilesToMatchUserSettingsOrder()
{
std::set<winrt::guid> uniqueGuids;
std::deque<winrt::guid> guidOrder;
auto collectGuids = [&](const auto& json) {
for (auto profileJson : _GetProfilesJsonObject(json))
{
if (profileJson.isObject())
{
auto guid = implementation::Profile::GetGuidOrGenerateForJson(profileJson);
if (uniqueGuids.insert(guid).second)
{
guidOrder.push_back(guid);
}
}
}
};
// Push all the userSettings profiles' GUIDS into the set
collectGuids(_userSettings);
// Push all the defaultSettings profiles' GUIDS into the set
collectGuids(_defaultSettings);
std::equal_to<winrt::guid> equals;
// Re-order the list of profiles to match that ordering
// for (gIndex=0 -> uniqueGuids.size)
// pIndex = the pIndex of the profile with guid==guids[gIndex]
// profiles.swap(pIndex <-> gIndex)
// This is O(N^2), which is kinda rough. I'm sure there's a better way
for (uint32_t gIndex = 0; gIndex < guidOrder.size(); gIndex++)
{
const auto guid = guidOrder.at(gIndex);
for (uint32_t pIndex = gIndex; pIndex < _allProfiles.Size(); pIndex++)
{
auto profileGuid = _allProfiles.GetAt(pIndex).Guid();
if (equals(profileGuid, guid))
{
auto prof1 = _allProfiles.GetAt(pIndex);
_allProfiles.SetAt(pIndex, _allProfiles.GetAt(gIndex));
_allProfiles.SetAt(gIndex, prof1);
break;
}
}
}
}
// Method Description:
// - Updates the list of active profiles from the list of all profiles
// - If there are no active profiles (all profiles are hidden), throw a SettingsException
// - Does not set any warnings.
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_UpdateActiveProfiles()
{
_activeProfiles.Clear();
for (auto const& profile : _allProfiles)
{
if (!profile.Hidden())
{
_activeProfiles.Append(profile);
}
}
// Ensure that we still have some profiles here. If we don't, then throw an
// exception, so the app can use the defaults.
const bool hasProfiles = _activeProfiles.Size() > 0;
if (!hasProfiles)
{
// Throw an exception. This is an invalid state, and we want the app to
// be able to gracefully use the default settings.
throw SettingsException(SettingsLoadErrors::AllProfilesHidden);
}
}
// Method Description:
// - Ensures that every profile has a valid "color scheme" set. If any profile
// has a colorScheme set to a value which is _not_ the name of an actual color
// scheme, we'll set the color table of the profile to something reasonable.
// Arguments:
// - <none>
// Return Value:
// - <none>
// - Appends a SettingsLoadWarnings::UnknownColorScheme to our list of warnings if
// we find any such duplicate.
void CascadiaSettings::_ValidateAllSchemesExist()
{
bool foundInvalidScheme = false;
for (auto profile : _allProfiles)
{
const auto schemeName = profile.DefaultAppearance().ColorSchemeName();
if (!_globals->ColorSchemes().HasKey(schemeName))
{
// Clear the user set color scheme. We'll just fallback instead.
profile.DefaultAppearance().ClearColorSchemeName();
foundInvalidScheme = true;
}
if (profile.UnfocusedAppearance())
{
const auto unfocusedSchemeName = profile.UnfocusedAppearance().ColorSchemeName();
if (!_globals->ColorSchemes().HasKey(unfocusedSchemeName))
{
profile.UnfocusedAppearance().ClearColorSchemeName();
foundInvalidScheme = true;
}
}
}
if (foundInvalidScheme)
{
_warnings.Append(SettingsLoadWarnings::UnknownColorScheme);
}
}
// Method Description:
// - Ensures that all specified images resources (icons and background images) are valid URIs.
// This does not verify that the icon or background image files are encoded as an image.
// Arguments:
// - <none>
// Return Value:
// - <none>
// - Appends a SettingsLoadWarnings::InvalidBackgroundImage to our list of warnings if
// we find any invalid background images.
// - Appends a SettingsLoadWarnings::InvalidIconImage to our list of warnings if
// we find any invalid icon images.
void CascadiaSettings::_ValidateMediaResources()
{
bool invalidBackground{ false };
bool invalidIcon{ false };
for (auto profile : _allProfiles)
{
if (!profile.DefaultAppearance().BackgroundImagePath().empty())
{
// Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable.
// This covers file paths on the machine, app data, URLs, and other resource paths.
try
{
winrt::Windows::Foundation::Uri imagePath{ profile.DefaultAppearance().ExpandedBackgroundImagePath() };
}
catch (...)
{
// reset background image path
profile.DefaultAppearance().BackgroundImagePath(L"");
invalidBackground = true;
}
}
if (profile.UnfocusedAppearance())
{
if (!profile.UnfocusedAppearance().BackgroundImagePath().empty())
{
// Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable.
// This covers file paths on the machine, app data, URLs, and other resource paths.
try
{
winrt::Windows::Foundation::Uri imagePath{ profile.UnfocusedAppearance().ExpandedBackgroundImagePath() };
}
catch (...)
{
// reset background image path
profile.UnfocusedAppearance().BackgroundImagePath(L"");
invalidBackground = true;
}
}
}
if (!profile.Icon().empty())
{
const auto iconPath{ wil::ExpandEnvironmentStringsW<std::wstring>(profile.Icon().c_str()) };
try
{
winrt::Windows::Foundation::Uri imagePath{ iconPath };
}
catch (...)
{
// Anything longer than 2 wchar_t's _isn't_ an emoji or symbol,
// so treat it as an invalid path.
if (iconPath.size() > 2)
{
// reset icon path
profile.Icon(L"");
invalidIcon = true;
}
}
}
}
if (invalidBackground)
{
_warnings.Append(SettingsLoadWarnings::InvalidBackgroundImage);
}
if (invalidIcon)
{
_warnings.Append(SettingsLoadWarnings::InvalidIcon);
}
}
// Method Description:
// - Helper to get the GUID of a profile, given an optional index and a possible
// "profile" value to override that.
// - First, we'll try looking up the profile for the given index. This will
// either get us the GUID of the Nth profile, or the GUID of the default
// profile.
// - Then, if there was a Profile set in the NewTerminalArgs, we'll use that to
// try and look the profile up by either GUID or name.
// Arguments:
// - index: if provided, the index in the list of profiles to get the GUID for.
// If omitted, instead use the default profile's GUID
// - newTerminalArgs: An object that may contain a profile name or GUID to
// actually use. If the Profile value is not a guid, we'll treat it as a name,
// and attempt to look the profile up by name instead.
// Return Value:
// - the GUID of the profile corresponding to this combination of index and NewTerminalArgs
winrt::guid CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs& newTerminalArgs) const
{
std::optional<winrt::guid> profileByIndex, profileByName;
if (newTerminalArgs)
{
if (newTerminalArgs.ProfileIndex() != nullptr)
{
profileByIndex = _GetProfileGuidByIndex(newTerminalArgs.ProfileIndex().Value());
}
profileByName = _GetProfileGuidByName(newTerminalArgs.Profile());
}
return til::coalesce_value(profileByName, profileByIndex, _globals->DefaultProfile());
}
// Method Description:
// - Helper to get the GUID of a profile given a name that could be a guid or an actual name.
// Arguments:
// - name: a guid string _or_ the name of a profile
// Return Value:
// - the GUID of the profile corresponding to this name
std::optional<winrt::guid> CascadiaSettings::_GetProfileGuidByName(const winrt::hstring name) const
try
{
// First, try and parse the "name" as a GUID. If it's a
// GUID, and the GUID of one of our profiles, then use that as the
// profile GUID instead. If it's not, then try looking it up as a
// name of a profile. If it's still not that, then just ignore it.
if (!name.empty())
{
// Do a quick heuristic check - is the profile 38 chars long (the
// length of a GUID string), and does it start with '{'? Because if
// it doesn't, it's _definitely_ not a GUID.
if (name.size() == 38 && name[0] == L'{')
{
const auto newGUID{ Utils::GuidFromString(static_cast<std::wstring>(name)) };
if (FindProfile(newGUID))
{
return newGUID;
}
}
// Here, we were unable to use the profile string as a GUID to
// lookup a profile. Instead, try using the string to look the
// Profile up by name.
for (auto profile : _allProfiles)
{
if (profile.Name() == name)
{
return profile.Guid();
}
}
}
return std::nullopt;
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
return std::nullopt;
}
// Method Description:
// - Helper to find the profile GUID for a the profile at the given index in the
// list of profiles. If no index is provided, this instead returns the default
// profile's guid. This is used by the NewTabProfile<N> ShortcutActions to
// create a tab for the Nth profile in the list of profiles.
// Arguments:
// - index: if provided, the index in the list of profiles to get the GUID for.
// If omitted, instead return the default profile's GUID
// Return Value:
// - the Nth profile's GUID, or the default profile's GUID
std::optional<winrt::guid> CascadiaSettings::_GetProfileGuidByIndex(std::optional<int> index) const
{
if (index)
{
const auto realIndex{ index.value() };
// If we don't have that many profiles, then do nothing.
if (realIndex >= 0 &&
realIndex < gsl::narrow_cast<decltype(realIndex)>(_activeProfiles.Size()))
{
const auto& selectedProfile = _activeProfiles.GetAt(realIndex);
return selectedProfile.Guid();
}
}
return std::nullopt;
}
// Method Description:
// - If there were any warnings we generated while parsing the user's
// keybindings, add them to the list of warnings here. If there were warnings
// generated in this way, we'll add a AtLeastOneKeybindingWarning, which will
// act as a header for the other warnings
// Arguments:
// - <none>
// Return Value:
// - <none>
void CascadiaSettings::_ValidateKeybindings()
{
auto keybindingWarnings = _globals->KeybindingsWarnings();
if (!keybindingWarnings.empty())
{
_warnings.Append(SettingsLoadWarnings::AtLeastOneKeybindingWarning);
for (auto warning : keybindingWarnings)
{
_warnings.Append(warning);
}
}
}
// Method Description:
// - Ensures that every "setColorScheme" command has a valid "color scheme" set.
// Arguments:
// - <none>
// Return Value:
// - <none>
// - Appends a SettingsLoadWarnings::InvalidColorSchemeInCmd to our list of warnings if
// we find any command with an invalid color scheme.
void CascadiaSettings::_ValidateColorSchemesInCommands()
{
bool foundInvalidScheme{ false };
for (const auto& nameAndCmd : _globals->ActionMap().NameMap())
{
if (_HasInvalidColorScheme(nameAndCmd.Value()))
{
foundInvalidScheme = true;
break;
}
}
if (foundInvalidScheme)
{
_warnings.Append(SettingsLoadWarnings::InvalidColorSchemeInCmd);
}
}
bool CascadiaSettings::_HasInvalidColorScheme(const Model::Command& command)
{
bool invalid{ false };
if (command.HasNestedCommands())
{
for (const auto& nested : command.NestedCommands())
{
if (_HasInvalidColorScheme(nested.Value()))
{
invalid = true;
break;
}
}
}
else if (const auto& actionAndArgs = command.ActionAndArgs())
{
if (const auto& realArgs = actionAndArgs.Args().try_as<Model::SetColorSchemeArgs>())
{
auto cmdImpl{ winrt::get_self<Command>(command) };
// no need to validate iterable commands on color schemes
// they will be expanded to commands with a valid scheme name
if (cmdImpl->IterateOn() != ExpandCommandType::ColorSchemes &&
!_globals->ColorSchemes().HasKey(realArgs.SchemeName()))
{
invalid = true;
}
}
}
return invalid;
}
// Method Description:
// - Checks for the presence of the legacy "globals" key in the user's
// settings.json. If this key is present, then they've probably got a pre-0.11
// settings file that won't work as expected anymore. We should warn them
// about that.
// Arguments:
// - <none>
// Return Value:
// - <none>
// - Appends a SettingsLoadWarnings::LegacyGlobalsProperty to our list of warnings if
// we find any invalid background images.
void CascadiaSettings::_ValidateNoGlobalsKey()
{
// use isMember here. If you use [], you're actually injecting "globals": null.
if (_userSettings.isMember("globals"))
{
_warnings.Append(SettingsLoadWarnings::LegacyGlobalsProperty);
}
}
// Method Description
// - Replaces known tokens DEFAULT_PROFILE, PRODUCT and VERSION in the settings template
// with their expected values. DEFAULT_PROFILE is updated to match PowerShell Core's GUID
// if such a profile is detected. If it isn't, it'll be set to Windows PowerShell's GUID.
// Arguments:
// - settingsTemplate: a settings template
// Return value:
// - The new settings string.
std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::string_view settingsTemplate) const
{
// We're using replace_needle_in_haystack_inplace here, because it's more
// efficient to iteratively modify a single string in-place than it is to
// keep copying over the contents and modifying a copy (which
// replace_needle_in_haystack would do).
std::string finalSettings{ settingsTemplate };