Squashed 'third_party/allwpilib/' changes from e473a00f97..e4b91005cf

e4b91005cf [examples] Update SwerveModule constructor doc (NFC) (#4042)
a260bfd83b [examples] Remove "this" keyword from SwerveModule (#4043)
18e262a100 [examples] Fix multiple doc typos in SwerveControllerCommand example (NFC) (#4044)
4bd1f526ab [wpilibc] Prevent StopMotor from terminating robot during MotorSafety check (#4038)
27847d7eb2 [sim] Expose GUI control functions via HAL_RegisterExtension (#4034)
b2a8d3f0f3 [wpilibc] Add mechanism to reset MotorSafety list (#4037)
49adac9564 [wpilib] Check for signedness in ArcadeDriveIK() (#4028)
a19d1133b1 [wpiutil] libuv: Fix sign compare warnings in gcc 11.2 (#4031)
dde91717e4 [build] cmake: Add ability to customize target warnings (#4032)
e9050afd67 [sim] Update sim match time to match real robot (#4024)
165d2837cf [wpilib] Preferences: Set Persistent in Init methods (#4025)
ac7549edca [glass] Fix snprintf truncation warning (#4029)
4d96bc72e0 [wpilibj] Fix typos in error messages for non-null assertions (#4014)
3411eee20f [hal] Replace hardcoded sim array sizes with constants (#4015)
74de97eeca [wpilibc] Add mechanism to reset various global structures (#4007)
4e3cc25012 [examples] Fix periodic function rate comment (NFC) (#4013)
90c1db393e [sim] Add exported functions to control the sim GUI (#3995)
2f43274aa4 [wpilibj] MechanismRoot2d: Add flush to setPosition (#4011)
aeca09db09 [glass] Support remapping of Enter key (#3994)
c107f22c67 [sim] Sim GUI: don't force-show Timing and Other Devices (#4001)
68fe51e8da [wpigui] Update PFD to latest, fix kdialog multiselect (#4005)
8d08d67cf1 [wpigui] PFD: Add console warning if file chooser unavailable (#4003)
4f1782f66e [wpilibc] Only call HAL_Report when initializing SmartDashboard (#4006)
3f77725cd3 Remove uses of iostream (#4004)
5635f33a32 [glass] Increase plot depth to 20K points (#3993)
bca4b7111b [glass] Fix PlotSeries::SetSource() (#3991)
6a6366b0d6 [commands] Add until() as alias for withInterrupt() (#3981)
16bf2c70c5 [wpilib] Fix joystick out of range error messages (#3988)
4b3edb742c [wpilib] Fix ADIS16448 IMU default constructor not working in Java (#3989)
fcf23fc9e9 [hal] Fix potential gamedata out of bounds read (#3983)
af5ef510c5 [wpilibc] Fix REV PH pulse duration units (#3982)
05401e2b81 [wpilib] Write REV PH firmware version to roboRIO to display on driver station (#3977)
9fde0110b6 Update to 2022 v4.0 image (#3944)
b03f8ddb2e [examples] fix incorrect variable in Arm Simulation Pref (#3980)
a26df2a022 [examples] Update ArmSimulation example to use Preferences (#3976)
d68d6674e8 [examples] Armbot: rename kCos to kG (#3975)
a8f0f6bb90 [wpilibj] Fix ADIS16448 getRate to return rate instead of angle (#3974)
dd9c92d5bf [build] Remove debug info from examples (#3971)
84df14dd70 [rtns] Fix icons (#3972)
560094ad92 [examples] Correct Mecanum example axes (#3955)
7ea1be9c01 [wpilibc] Fix typo in hardware version for REV PDH (#3969)
700f13bffd [wpilibj] Make methods public for Java REV PDH (#3970)
b6aa7c1aa9 [wpilibj] Make methods public for Java REVPH (#3968)
eb4d183e48 [wpimath] Fix clang-tidy bugprone-integer-division warning (#3966)
77e4e81e1e [wpilib] Add Field widget to BuiltInWidgets in shuffleboard (#3961)
88f5cb6eb0 [build] Publish PDBs with C++ tools (#3960)
efae552f3e [wpimath] Remove DifferentialDriveKinematics include from odometry (#3958)
46b277421a [glass] Update Speed Controller Type name for 2022 WPILib (#3952)
42908126b9 [wpilib] Add DCMotorSim (#3910)
a467392cbd [wpiutil] StackTrace: Add ability to override default implementation (#3951)
78d0bcf49d [templates] Add SimulationInit()/SimulationPeriodic() to robot templates (#3943)
02a0ced9b0 [wpilib] MecanumDrive: update docs for axis to match implementation (NFC) (#3942)
4ccfe1c9f2 [wpilib] Added docs clarification on units for drive class WheelSpeeds (NFC) (#3939)
830c0c5c2f [wpilib] MechanismLigament2d: Add getters for color and line weight (#3947)
5548a37465 [wpilib] PowerDistribution: Add module type getter (#3948)
2f9a600de2 [hal] Fix PCM one shot (#3949)
559db11a20 [myRobot] Skip deploying debug libraries for myRobot deploys (#3950)
76c78e295b [examples] Reorder SwerveModules in SwerveControllerCommand example odometry update (#3934)
debbd5ff4b [wpilib] Improve PowerDistribution docs (NFC) (#3925)
841174f302 [commands] Change command vendordep JSON version number to 1.0.0 (#3938)
8c55844f91 [wpilib] Remove comment about Mecanum right side inverted (NFC) (#3929)
0b990bf0f5 [hal] Fix PCM sticky faults clear function crashing (#3932)
104d7e2abc [hal] Don't throw exceptions in PCM JNI (#3933)
5ba69e1af1 [examples] Updated type in Java SwerveModule (#3928)
f3a0b5c7d7 [wpimath] Fix Java SimpleMotorFeedforward Docs (NFC) (#3926)
7f4265facc [wpimath] Add LinearFilter::FiniteDifference() (#3900)
63d1fb3bed [wpiutil] Modify fmt to not throw on write failure (#3919)
36af6d25a5 [wpimath] Fix input vector in pose estimator docs (NFC) (#3923)
8f387f7255 [wpilibj] Switch ControlWord mutex to actual reentrant mutex (#3922)
792e735e08 [wpimath] Move TrajectoryGenerator::SetErrorHandler definition to .cpp (#3920)
3b76de83eb [commands] Fix ProfiledPIDCommand use-after-free (#3904)
ad9f738cfa [fieldimages] Fix maven publishing (#3897)
49455199e5 [examples] Use left/rightGroup.Get() for simulator inputs to fix inversions (#3908)
64426502ea [wpimath] Fix arm -> flywheel typo (NFC) (#3911)
8cc112d196 [wpiutil] Fix wpi::array for move-only types (#3917)
e78cd49861 [build] Upgrade Java formatter plugins (#3894)
cfb4f756d6 [build] Upgrade to shadow 7.1.2 (#3893)
ba0908216c [wpimath] Fix crash in KF latency compensator (#3888)
a3a0334fad [build] cmake: Move fieldImages to WITH_GUI (#3885)
cf7460c3a8 [fieldImages] Add 2022 field (#3883)
db0fbb6448 [wpimath] Fix LQR matrix constructor overload for Q, R, and N (#3884)
8ac45f20bb [commands] Update Command documentation (NFC) (#3881)
b3707cca0b [wpiutil] Upgrade to fmt 8.1.1 (#3879)
a69ee3ece9 [wpimath] Const-qualify Twist2d scalar multiply (#3882)
750d9a30c9 [examples] Fix Eigen out of range error when running example (#3877)
41c5b2b5ac [rtns] Add cmake build (#3866)
6cf3f9b28e [build] Upgrade to Gradle 7.3.3 (#3878)
269cf03472 [examples] Add communication examples (e.g. arduino) (#2500)
5ccfc4adbd [oldcommands] Deprecate PIDWrappers, since they use deprecated interfaces (#3868)
b6f44f98be [hal] Add warning about onboard I2C (#3871)
0dca57e9ec [templates] romieducational: Invert drivetrain and disable motor safety (#3869)
22c4da152e [wpilib] Add GetRate() to ADIS classes (#3864)
05d66f862d [templates] Change the template ordering to put command based first (#3863)
b09f5b2cf2 [wpilibc] Add virtual dtor for LinearSystemSim (#3861)
a2510aaa0e [wpilib] Make ADIS IMU classes unit-safe (#3860)
947f589916 [wpilibc] Rename ADIS_16470_IMU.cpp to match class name (#3859)
bbd8980a20 [myRobot] Fix cameraserver library order (#3858)
831052f118 [wpilib] Add simulation support to ADIS classes (#3857)
c137569f91 [wpilib] Throw exception if the REV Pneumatic Hub firmware version is older than 22.0.0 (#3853)
dae61226fa Fix Maven Artifacts readme (#3856)
3ad4594a88 Update Maven artifacts readme for 2022 (#3855)
112acb9a62 [wpilibc] Move ADIS IMU constants to inside class (#3852)
ecee224e81 [wpilib] Allow SendableCameraWrappers to take arbitrary URLs (#3850)
a3645dea34 LICENSE: Bump year range to include 2022 (#3854)
7c09f44898 [wpilib] Use PSI for compressor config and sensor reading (#3847)
f401ea9aae [wpigui] Remove wpiutil linkage (#3851)
bf8517f1e6 [wpimath] TimeInterpolatableBufferTest: Fix lint warnings (#3849)
528087e308 [hal] Use enums with fixed underlying type in clang C (#3297)
1f59ff72f9 [wpilib] Add ADIS IMUs (#3777)
315be873c4 [wpimath] Add TimeInterpolatableBuffer (#2695)
b8d019cdb4 [wpilib] Rename NormalizeWheelSpeeds to DesaturateWheelSpeeds (#3791)
102f23bbdb [wpilibj] DriverStation: Set thread interrupted state (#3846)
b85c24a79c [wpilib] Add warning about onboard I2C (#3842)
eee29daaf9 [newCommands] Trigger: Allow override of debounce type (#3845)
aa9dfabde2 [wpimath] Move debouncer to filters (#3838)
5999a26fba [wpiutil] Add GetSystemTime() (#3840)
1e82595ffb [examples] Fix arcade inversions (#3841)
e373fa476b [wpiutil] Add disableMockTime to JNI (#3839)
dceb5364f4 [examples] Ensure right side motors are inverted (#3836)
baacbc8e24 [wpilib] Tachometer: Add function to return RPS (#3833)
84b15f0883 [templates] Add Java Romi Educational template (#3837)
c0da9d2d35 [examples] Invert Right Motor in Romi Java examples (#3828)
0fe0be2733 [build] Change project year to intellisense (#3835)
eafa947338 [wpimath] Make copies of trajectory constraint arguments (#3832)
9d13ae8d01 [wpilib] Add notes for Servo get that it only returns cmd (NFC) (#3820)
2a64e4bae5 [wpimath] Give drivetrain a more realistic width in TrajectoryJsonTest.java (#3822)
c3fd20db59 [wpilib] Fix trajectory sampling in DifferentialDriveSim test (#3821)
6f91f37cd0 [examples] Fix SwerveControllerCommand order of Module States (#3815)
5158730b81 [wpigui] Upgrade to imgui 1.86, GLFW 3.3.6 (#3817)
2ad2d2ca96 [wpiutil] MulticastServiceResolver: Fix C array returning functions (#3816)
b5fd29774f [wpilibj] Trigger: implement BooleanSupplier interface (#3811)
9f8f330e96 [wpilib] Fix Mecanum and SwerveControllerCommand when desired rotation passed (#3808)
1ad3b1b333 [hal] Don't copy byte to where null terminator goes (#3807)
dfc24425c3 [build] Fix gazebo gradle IDE warnings (#3806)
c02577bb51 [glass] Configure delay loading for windows camera server support (#3803)
c9e6a96a61 [wpilib] Document range of Servo angle (NFC) (#3796)
9778626f34 [wpilib, hal] Add support for getting faults and versions from power distribution (#3794)
34b2d0dae1 [wpilib, hal] High Level REV PH changes (#3792)
59a7528fd6 [cscore] Fix crash when usbcamera is deleted before message pump thread fully starts (#3804)
11d9859ef1 [build] Update plugins to remove log4j vulnerabilities (#3805)
e44ed752ad [glass] Fix CollapsingHeader in Encoder, PCM, and DeviceTree (#3797)
52b2dd5b89 [build] Bump native utils to remove log4j (#3802)
c46636f218 [wpilib] Improve new counter classes documentation (NFC) (#3801)
dc531462e1 [build] Update to gradle 7.3.2 (#3800)
92ba98621c [wpimath] Add helper variable templates for units type traits (#3790)
d41d051f1b [wpilibc] Fix Mecanum & Swerve ControllerCommand lambda capture (#3795)
c5ae0effac OtherVersions.md: Add one missing case of useLocal (#3788)
b3974c6ed3 [wpimath] Upgrade to Drake v0.37.0 (#3786)
589a00e379 [wpilibc] Start DriverStation thread from RobotBase (#3785)
8d9836ca02 [wpilib] Improve curvature drive documentation (NFC) (#3783)
8b5bf8632e [myRobot] Add wpimath and wpiutil JNI (#3784)
1846114491 [examples] Update references from characterization to SysId (NFC) (#3782)
2c461c794e [build] Update to gradle 7.3 (#3778)
109363daa4 [hal] Add remaining driver functions for REVPH (#3776)
41d26bee8d [hal] Refactor REV PDH (#3775)
7269a170fb Upgrade maven deps to latest versions and fix new linter errors (#3772)
441f2ed9b0 [build] actions: use fixed image versions instead latest (#3761)
15275433d4 [examples] Fix duplicate port allocations in C++ SwerveBot/SwerveDrivePoseEstimator/RomiReference (#3773)
1ac02d2f58 [examples] Fix drive Joystick axes in several examples (#3769)
8ee6257e92 [wpilib] DifferentialDrivetrainSim.KitbotMotor: Add NEO and Falcon 500 (#3762)
d81ef2bc5c [wpilib] Fix deadlocks in Mechanism2d et al. (#3770)
acb64dff97 [wpimath] Make RamseteController::Calculate() more concise (#3763)
3f6cf76a8c [hal] Refactor REV PH CAN frames (#3756)
3ef2dab465 [wpilib] DutyCycleEncoder: add setting of duty cycle range (#3759)
a5a56dd067 Readme: Add Visual Studio 2022 (#3760)
04957a6d30 [wpimath] Fix units of RamseteController's b and zeta (#3757)
5da54888f8 [glass] Upgrade imgui to 0.85, implot to HEAD, glfw to 3.3.5 (#3754)
6c93365b0f [wpiutil] MulticastService cleanup (#3750)
1c4a8bfb66 [cscore] Cleanup Windows USB camera impl (#3751)
d51a1d3b3d [rtns] Fix icon (#3749)
aced2e7da6 Add roboRIO Team Number Setter tool (#3744)
fa1ceca83a [wpilibj] Use DS cache for iterative robot control word cache (#3748)
0ea05d34e6 [build] Update to gradle 7.2 (#3746)
09db4f672b [build] Update to native utils 2022.6.1 (#3745)
4ba80a3a8c [wpigui] Don't recursively render frames in size callback (#3743)
ae208d2b17 [wpiutil] StringExtras: Add substr() (#3742)
6f51cb3b98 [wpiutil] MulticastResolver: make event manual reset, change to multiple read (#3736)
f6159ee1a2 [glass] Fix Drive widget handling of negative rotation (#3739)
7f401ae895 [build] Update NI libraries to 2022.2.3 (#3738)
0587b7043a [glass] Use JSON files for storage instead of imgui ini
0bbf51d566 [wpigui] Change maximized to bool
92c6eae6b0 [wpigui] PFD: Add explicit to constructors
141354cd79 [wpigui] Add hooks for custom load/save settings
f6e9fc7d71 [wpiutil] Handle multicast service collision on linux (#3734)
d8418be7d1 [glass, outlineviewer] Return 0 from WinMain (#3735)
82066946e5 [wpiutil] Add mDNS resolver and announcer (#3733)
4b1defc8d8 [wpilib] Remove automatic PD type from module type enum (#3732)
da90c1cd2c [wpilib] Add bang-bang controller (#3676)
3aa54fa027 [wpilib] Add new counter implementations (#2447)
b156db400d [hal, wpilib] Incorporate pneumatic control type into wpilibc/j (#3728)
9aba2b7583 [oldCommands] Add wrappers for WPILib objects to work with old PID Controller (#3710)
a9931223f0 [hal] Add REV PH faults (#3729)
aacf9442e4 [wpimath] Fix units typo in LinearSystemId source comment (#3730)
7db10ecf00 [wpilibc] Make SPI destructor virtual since SPI contains virtual functions (#3727)
a0a5b2aea5 [wpimath] Upgrade to EJML 0.41 (#3726)
eb835598a4 [hal] Add HAL functions for compressor config modes on REV PH (#3724)
f0ab6df5b6 [wpimath] Upgrade to Drake v0.36.0 (#3722)
075144faa3 [docs] Parse files without extensions with Doxygen (#3721)
32468a40cb [hal] Remove use of getDmaDescriptor from autospi (#3717)
38611e9dd7 [hal] Fix REVPH Analog Pressure channel selection (#3716)
4d78def31e [wpilib] Add DeadbandElimination forwarding to PWMMotorController (#3714)
3be0c1217a [wpilibcExamples] Make GearsBot use idiomatic C++ (#3711)
42d3a50aa2 [hal] Check error codes during serial port initialization (#3712)
52f1464029 Add project with field images and their json config files (#3668)
68ce62e2e9 [hal] Add autodetect for power modules (#3706)
3dd41c0d37 [wpilib] Don't print PD errors for LiveWindow reads (#3708)
7699a1f827 [hal] Fix sim not working with automatic PD type and default module (#3707)

Change-Id: I6b0b8fa8b2d2a24071240f624db9ec6d127f6648
git-subtree-dir: third_party/allwpilib
git-subtree-split: e4b91005cf69161f1cb3d934f6526232e6b9169e
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/simulation/gz_msgs/build.gradle b/simulation/gz_msgs/build.gradle
index ccc933e..31f3df4 100644
--- a/simulation/gz_msgs/build.gradle
+++ b/simulation/gz_msgs/build.gradle
@@ -24,6 +24,8 @@
 } catch(Exception ex) {
 }
 
+ext.skip_gz_msgs = false
+
 if (project.hasProperty("forceGazebo")) {
     if (!protobuf_version?.trim()) {
         println "Protobuf is not available. (pkg-config --modversion protobuf failed)"
@@ -38,9 +40,11 @@
     task.onlyIf { !project.hasProperty('skip_gz_msgs') }
 }
 
-dependencies {
-    implementation "com.google.protobuf:protobuf-java:${protobuf_version}"
-    implementation "com.google.protobuf:protoc:${protobuf_version}"
+if (!ext.skip_gz_msgs) {
+    dependencies {
+        implementation "com.google.protobuf:protobuf-java:${protobuf_version}"
+        implementation "com.google.protobuf:protoc:${protobuf_version}"
+    }
 }
 
 /* There is a nice gradle plugin for protobuf, and the protoc tool
diff --git a/simulation/halsim_ds_socket/src/main/native/cpp/DSCommPacket.cpp b/simulation/halsim_ds_socket/src/main/native/cpp/DSCommPacket.cpp
index 1aff988..653565c 100644
--- a/simulation/halsim_ds_socket/src/main/native/cpp/DSCommPacket.cpp
+++ b/simulation/halsim_ds_socket/src/main/native/cpp/DSCommPacket.cpp
@@ -310,6 +310,10 @@
 void DSCommPacket::SendUDPToHALSim(void) {
   SendJoysticks();
 
+  if (!m_control_word.enabled) {
+    m_match_time = -1;
+  }
+
   HALSIM_SetDriverStationMatchTime(m_match_time);
   HALSIM_SetDriverStationEnabled(m_control_word.enabled);
   HALSIM_SetDriverStationAutonomous(m_control_word.autonomous);
diff --git a/simulation/halsim_ds_socket/src/main/native/include/DSCommPacket.h b/simulation/halsim_ds_socket/src/main/native/include/DSCommPacket.h
index 1285711..5b8b45d 100644
--- a/simulation/halsim_ds_socket/src/main/native/include/DSCommPacket.h
+++ b/simulation/halsim_ds_socket/src/main/native/include/DSCommPacket.h
@@ -66,7 +66,7 @@
   HAL_AllianceStationID m_alliance_station;
   HAL_MatchInfo matchInfo;
   std::array<DSCommJoystickPacket, HAL_kMaxJoysticks> m_joystick_packets;
-  double m_match_time;
+  double m_match_time = -1;
 };
 
 }  // namespace halsim
diff --git a/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp b/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp
index 23a5238..d6409cd 100644
--- a/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp
@@ -100,7 +100,7 @@
 }
 
 void AddressableLEDGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "Addressable LEDs", [] { return AddressableLEDsExists(); },
       [] { return std::make_unique<AddressableLEDsModel>(); },
       [](glass::Window* win, glass::Model* model) {
diff --git a/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp
index 5a3b2d8..e100d5d 100644
--- a/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp
@@ -109,7 +109,7 @@
 }
 
 void AnalogInputSimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "Analog Inputs", AnalogInputsAnyInitialized,
       [] { return std::make_unique<AnalogInputsSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
diff --git a/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp
index 22c06dd..150e4bf 100644
--- a/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp
@@ -232,14 +232,14 @@
 }
 
 void DIOSimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "DIO", DIOAnyInitialized, [] { return std::make_unique<DIOsSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
         win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
         win->SetDefaultPos(470, 20);
         return glass::MakeFunctionView([=] {
           glass::DisplayDIOs(static_cast<DIOsSimModel*>(model),
-                             HALSimGui::halProvider.AreOutputsEnabled());
+                             HALSimGui::halProvider->AreOutputsEnabled());
         });
       });
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp
index 42ec8a3..866b14a 100644
--- a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp
@@ -4,9 +4,11 @@
 
 #include "DriverStationGui.h"
 
+#include <glass/Context.h>
+#include <glass/Storage.h>
 #include <glass/other/FMS.h>
 #include <glass/support/ExtraGuiWidgets.h>
-#include <glass/support/IniSaverInfo.h>
+#include <glass/support/NameSetting.h>
 
 #include <algorithm>
 #include <atomic>
@@ -93,7 +95,7 @@
 
 class KeyboardJoystick : public SystemJoystick {
  public:
-  explicit KeyboardJoystick(int index);
+  KeyboardJoystick(glass::Storage& storage, int index);
 
   void SettingsDisplay() override;
   void Update() override;
@@ -107,9 +109,6 @@
   void ClearKey(int key);
   virtual const char* GetKeyName(int key) const = 0;
 
-  void ReadIni(std::string_view name, std::string_view value);
-  void WriteIni(ImGuiTextBuffer* out_buf) const;
-
  protected:
   void EditKey(const char* label, int* key);
 
@@ -121,44 +120,57 @@
 
   HALJoystickData m_data;
 
+  int& m_axisCount;
+  int& m_buttonCount;
+  int& m_povCount;
+
   struct AxisConfig {
-    int incKey = -1;
-    int decKey = -1;
-    float keyRate = 0.05f;
-    float decayRate = 0.05f;
-    float maxAbsValue = 1.0f;
+    explicit AxisConfig(glass::Storage& storage);
+
+    int& incKey;
+    int& decKey;
+    float& keyRate;
+    float& decayRate;
+    float& maxAbsValue;
   };
-  AxisConfig m_axisConfig[HAL_kMaxJoystickAxes];
+
+  std::vector<std::unique_ptr<glass::Storage>>& m_axisStorage;
+  std::vector<AxisConfig> m_axisConfig;
 
   static constexpr int kMaxButtonCount = 32;
-  int m_buttonKey[kMaxButtonCount];
+  std::vector<int>& m_buttonKey;
 
   struct PovConfig {
-    int key0 = -1;
-    int key45 = -1;
-    int key90 = -1;
-    int key135 = -1;
-    int key180 = -1;
-    int key225 = -1;
-    int key270 = -1;
-    int key315 = -1;
+    explicit PovConfig(glass::Storage& storage);
+
+    int& key0;
+    int& key45;
+    int& key90;
+    int& key135;
+    int& key180;
+    int& key225;
+    int& key270;
+    int& key315;
   };
 
-  PovConfig m_povConfig[HAL_kMaxJoystickPOVs];
+  std::vector<std::unique_ptr<glass::Storage>>& m_povStorage;
+  std::vector<PovConfig> m_povConfig;
 };
 
 class GlfwKeyboardJoystick : public KeyboardJoystick {
  public:
-  explicit GlfwKeyboardJoystick(int index, bool noDefaults = false);
+  GlfwKeyboardJoystick(glass::Storage& storage, int index);
 
   const char* GetKeyName(int key) const override;
 };
 
 struct RobotJoystick {
-  glass::NameInfo name;
-  std::string guid;
+  explicit RobotJoystick(glass::Storage& storage);
+
+  glass::NameSetting name;
+  std::string& guid;
   const SystemJoystick* sys = nullptr;
-  bool useGamepad = false;
+  bool& useGamepad;  // = false;
 
   HALJoystickData data;
 
@@ -235,8 +247,6 @@
   }
   void SetMatchTime(double val) override {
     HALSIM_SetDriverStationMatchTime(val);
-    int32_t status = 0;
-    m_startMatchTime = HAL_GetFPGATime(&status) * 1.0e-6 - val;
   }
   void SetEStop(bool val) override { HALSIM_SetDriverStationEStop(val); }
   void SetEnabled(bool val) override { HALSIM_SetDriverStationEnabled(val); }
@@ -254,8 +264,6 @@
 
   bool IsReadOnly() override;
 
-  bool m_matchTimeEnabled = true;
-
  private:
   glass::DataSource m_fmsAttached{"FMS:FMSAttached"};
   glass::DataSource m_dsAttached{"FMS:DSAttached"};
@@ -265,8 +273,7 @@
   glass::DataSource m_enabled{"FMS:RobotEnabled"};
   glass::DataSource m_test{"FMS:TestMode"};
   glass::DataSource m_autonomous{"FMS:AutonomousMode"};
-  double m_startMatchTime = 0.0;
-  double m_prevTime = 0.0;
+  double m_startMatchTime = -1.0;
 };
 
 }  // namespace
@@ -277,23 +284,24 @@
 static std::vector<std::unique_ptr<GlfwKeyboardJoystick>> gKeyboardJoysticks;
 
 // robot joysticks
-static RobotJoystick gRobotJoysticks[HAL_kMaxJoysticks];
+static std::vector<RobotJoystick> gRobotJoysticks;
 static std::unique_ptr<JoystickModel> gJoystickSources[HAL_kMaxJoysticks];
 
 // FMS
 static std::unique_ptr<FMSSimModel> gFMSModel;
 
 // Window management
-DSManager DriverStationGui::dsManager{"DSManager"};
+std::unique_ptr<DSManager> DriverStationGui::dsManager;
 
-static bool gDisableDS = false;
-static bool gZeroDisconnectedJoysticks = true;
-static bool gUseEnableDisableHotkeys = false;
-static bool gUseEstopHotkey = false;
-static std::atomic<bool>* gDSSocketConnected = nullptr;
+static bool* gpDisableDS = nullptr;
+static bool* gpZeroDisconnectedJoysticks = nullptr;
+static bool* gpUseEnableDisableHotkeys = nullptr;
+static bool* gpUseEstopHotkey = nullptr;
+static std::atomic<bool>* gpDSSocketConnected = nullptr;
 
 static inline bool IsDSDisabled() {
-  return gDisableDS || (gDSSocketConnected && *gDSSocketConnected);
+  return (gpDisableDS != nullptr && *gpDisableDS) ||
+         (gpDSSocketConnected && *gpDSSocketConnected);
 }
 
 JoystickModel::JoystickModel(int index) : m_index{index} {
@@ -357,133 +365,6 @@
   }
 }
 
-// read/write joystick mapping to ini file
-static void* JoystickReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
-                              const char* name) {
-  int num = wpi::parse_integer<int>(name, 10).value_or(-1);
-  if (num < 0 || num >= HAL_kMaxJoysticks) {
-    return nullptr;
-  }
-  return &gRobotJoysticks[num];
-}
-
-static void JoystickReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
-                             void* entry, const char* line) {
-  RobotJoystick* joy = static_cast<RobotJoystick*>(entry);
-  // format: guid=guid or useGamepad=0/1
-  auto [name, value] = wpi::split(line, '=');
-  name = wpi::trim(name);
-  value = wpi::trim(value);
-  if (name == "guid") {
-    joy->guid = value;
-  } else if (name == "useGamepad") {
-    if (auto num = wpi::parse_integer<int>(value, 10)) {
-      joy->useGamepad = num.value();
-    }
-  } else {
-    joy->name.ReadIni(name, value);
-  }
-}
-
-static void JoystickWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
-                             ImGuiTextBuffer* out_buf) {
-  for (int i = 0; i < HAL_kMaxJoysticks; ++i) {
-    auto& joy = gRobotJoysticks[i];
-    if (!joy.name.HasName() && !joy.sys) {
-      continue;
-    }
-    out_buf->appendf("[Joystick][%d]\nuseGamepad=%d\n", i,
-                     joy.useGamepad ? 1 : 0);
-    if (joy.name.HasName()) {
-      joy.name.WriteIni(out_buf);
-    }
-    if (joy.sys) {
-      const char* guid = joy.sys->GetGUID();
-      if (guid) {
-        out_buf->appendf("guid=%s\n", guid);
-      }
-    }
-    out_buf->append("\n");
-  }
-}
-
-// read/write keyboard joystick mapping to ini file
-static void* KeyboardJoystickReadOpen(ImGuiContext* ctx,
-                                      ImGuiSettingsHandler* handler,
-                                      const char* name) {
-  int num = wpi::parse_integer<int>(name, 10).value_or(-1);
-  if (num < 0 || num >= static_cast<int>(gKeyboardJoysticks.size())) {
-    return nullptr;
-  }
-  auto joy = gKeyboardJoysticks[num].get();
-  *joy = GlfwKeyboardJoystick(num, true);
-  return joy;
-}
-
-static void KeyboardJoystickReadLine(ImGuiContext* ctx,
-                                     ImGuiSettingsHandler* handler, void* entry,
-                                     const char* line) {
-  auto joy = static_cast<KeyboardJoystick*>(entry);
-  // format: guid=guid or useGamepad=0/1
-  auto [name, value] = wpi::split(line, '=');
-  joy->ReadIni(wpi::trim(name), wpi::trim(value));
-}
-
-static void KeyboardJoystickWriteAll(ImGuiContext* ctx,
-                                     ImGuiSettingsHandler* handler,
-                                     ImGuiTextBuffer* out_buf) {
-  for (unsigned int i = 0; i < gKeyboardJoysticks.size(); ++i) {
-    out_buf->appendf("[KeyboardJoystick][%u]\n", i);
-    gKeyboardJoysticks[i]->WriteIni(out_buf);
-    out_buf->append("\n");
-  }
-}
-
-// read/write DS settings to ini file
-static void* DriverStationReadOpen(ImGuiContext* ctx,
-                                   ImGuiSettingsHandler* handler,
-                                   const char* name) {
-  if (name == std::string_view{"Main"}) {
-    return &gDisableDS;
-  }
-  return nullptr;
-}
-
-static void DriverStationReadLine(ImGuiContext* ctx,
-                                  ImGuiSettingsHandler* handler, void* entry,
-                                  const char* line) {
-  auto [name, value] = wpi::split(line, '=');
-  name = wpi::trim(name);
-  value = wpi::trim(value);
-  if (name == "disable") {
-    if (auto num = wpi::parse_integer<int>(value, 10)) {
-      gDisableDS = num.value();
-    }
-  } else if (name == "zeroDisconnectedJoysticks") {
-    if (auto num = wpi::parse_integer<int>(value, 10)) {
-      gZeroDisconnectedJoysticks = num.value();
-    }
-  } else if (name == "enableDisableKeys") {
-    if (auto num = wpi::parse_integer<int>(value, 10)) {
-      gUseEnableDisableHotkeys = num.value();
-    }
-  } else if (name == "estopKey") {
-    if (auto num = wpi::parse_integer<int>(value, 10)) {
-      gUseEstopHotkey = num.value();
-    }
-  }
-}
-
-static void DriverStationWriteAll(ImGuiContext* ctx,
-                                  ImGuiSettingsHandler* handler,
-                                  ImGuiTextBuffer* out_buf) {
-  out_buf->appendf(
-      "[DriverStation][Main]\ndisable=%d\nzeroDisconnectedJoysticks=%d\n"
-      "enableDisableKeys=%d\nestopKey=%d\n\n",
-      gDisableDS ? 1 : 0, gZeroDisconnectedJoysticks ? 1 : 0,
-      gUseEnableDisableHotkeys ? 1 : 0, gUseEstopHotkey ? 1 : 0);
-}
-
 void GlfwSystemJoystick::Update() {
   bool wasPresent = m_present;
   m_present = glfwJoystickPresent(m_index);
@@ -520,7 +401,6 @@
     for (auto&& joy : gRobotJoysticks) {
       if (guid == joy.guid) {
         joy.sys = this;
-        joy.guid.clear();
         break;
       }
     }
@@ -616,21 +496,84 @@
   }
 }
 
-KeyboardJoystick::KeyboardJoystick(int index) : m_index{index} {
+KeyboardJoystick::AxisConfig::AxisConfig(glass::Storage& storage)
+    : incKey{storage.GetInt("incKey", -1)},
+      decKey{storage.GetInt("decKey", -1)},
+      keyRate{storage.GetFloat("keyRate", 0.05f)},
+      decayRate{storage.GetFloat("decayRate", 0.05f)},
+      maxAbsValue{storage.GetFloat("maxAbsValue", 1.0f)} {
+  // sanity check the key ranges
+  if (incKey < -1 || incKey >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    incKey = -1;
+  }
+  if (decKey < -1 || decKey >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    decKey = -1;
+  }
+}
+
+KeyboardJoystick::PovConfig::PovConfig(glass::Storage& storage)
+    : key0{storage.GetInt("key0", -1)},
+      key45{storage.GetInt("key45", -1)},
+      key90{storage.GetInt("key90", -1)},
+      key135{storage.GetInt("key135", -1)},
+      key180{storage.GetInt("key180", -1)},
+      key225{storage.GetInt("key225", -1)},
+      key270{storage.GetInt("key270", -1)},
+      key315{storage.GetInt("key315", -1)} {
+  // sanity check the key ranges
+  if (key0 < -1 || key0 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key0 = -1;
+  }
+  if (key45 < -1 || key45 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key45 = -1;
+  }
+  if (key90 < -1 || key90 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key90 = -1;
+  }
+  if (key135 < -1 || key135 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key135 = -1;
+  }
+  if (key180 < -1 || key180 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key180 = -1;
+  }
+  if (key225 < -1 || key225 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key225 = -1;
+  }
+  if (key270 < -1 || key270 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key270 = -1;
+  }
+  if (key315 < -1 || key315 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+    key315 = -1;
+  }
+}
+
+KeyboardJoystick::KeyboardJoystick(glass::Storage& storage, int index)
+    : m_index{index},
+      m_axisCount{storage.GetInt("axisCount", -1)},
+      m_buttonCount{storage.GetInt("buttonCount", -1)},
+      m_povCount{storage.GetInt("povCount", -1)},
+      m_axisStorage{storage.GetChildArray("axisConfig")},
+      m_buttonKey{storage.GetIntArray("buttonKeys")},
+      m_povStorage{storage.GetChildArray("povConfig")} {
   std::snprintf(m_name, sizeof(m_name), "Keyboard %d", index);
   std::snprintf(m_guid, sizeof(m_guid), "Keyboard%d", index);
 
   // init axes
-  m_data.axes.count = 0;
+  for (auto&& axisConfig : m_axisStorage) {
+    m_axisConfig.emplace_back(*axisConfig);
+  }
 
-  // init buttons
-  m_data.buttons.count = 0;
-  for (int i = 0; i < kMaxButtonCount; ++i) {
-    m_buttonKey[i] = -1;
+  // sanity check the button key ranges
+  for (auto&& key : m_buttonKey) {
+    if (key < -1 || key >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
+      key = -1;
+    }
   }
 
   // init POVs
-  m_data.povs.count = 0;
+  for (auto&& povConfig : m_povStorage) {
+    m_povConfig.emplace_back(*povConfig);
+  }
 
   // init desc structure
   m_data.desc.isXbox = 0;
@@ -678,17 +621,18 @@
   // axes
   if (ImGui::CollapsingHeader("Axes", ImGuiTreeNodeFlags_DefaultOpen)) {
     ImGui::PushID("Axes");
-    int axisCount = m_data.axes.count;
-    if (ImGui::InputInt("Count", &axisCount)) {
-      if (axisCount < 0) {
-        axisCount = 0;
+    if (ImGui::InputInt("Count", &m_axisCount)) {
+      if (m_axisCount < 0) {
+        m_axisCount = 0;
+      } else if (m_axisCount > HAL_kMaxJoystickAxes) {
+        m_axisCount = HAL_kMaxJoystickAxes;
       }
-      if (axisCount > HAL_kMaxJoystickAxes) {
-        axisCount = HAL_kMaxJoystickAxes;
-      }
-      m_data.axes.count = axisCount;
     }
-    for (int i = 0; i < axisCount; ++i) {
+    while (m_axisCount > static_cast<int>(m_axisConfig.size())) {
+      m_axisStorage.emplace_back(std::make_unique<glass::Storage>());
+      m_axisConfig.emplace_back(*m_axisStorage.back());
+    }
+    for (int i = 0; i < m_axisCount; ++i) {
       std::snprintf(label, sizeof(label), "Axis %d", i);
       if (ImGui::TreeNodeEx(label, ImGuiTreeNodeFlags_DefaultOpen)) {
         EditKey("Increase", &m_axisConfig[i].incKey);
@@ -710,17 +654,18 @@
   // buttons
   if (ImGui::CollapsingHeader("Buttons", ImGuiTreeNodeFlags_DefaultOpen)) {
     ImGui::PushID("Buttons");
-    int buttonCount = m_data.buttons.count;
-    if (ImGui::InputInt("Count", &buttonCount)) {
-      if (buttonCount < 0) {
-        buttonCount = 0;
+    if (ImGui::InputInt("Count", &m_buttonCount)) {
+      if (m_buttonCount < 0) {
+        m_buttonCount = 0;
       }
-      if (buttonCount > kMaxButtonCount) {
-        buttonCount = kMaxButtonCount;
+      if (m_buttonCount > kMaxButtonCount) {
+        m_buttonCount = kMaxButtonCount;
       }
-      m_data.buttons.count = buttonCount;
     }
-    for (int i = 0; i < buttonCount; ++i) {
+    while (m_buttonCount > static_cast<int>(m_buttonKey.size())) {
+      m_buttonKey.emplace_back(-1);
+    }
+    for (int i = 0; i < m_buttonCount; ++i) {
       std::snprintf(label, sizeof(label), "Button %d", i + 1);
       EditKey(label, &m_buttonKey[i]);
     }
@@ -730,17 +675,19 @@
   // povs
   if (ImGui::CollapsingHeader("POVs", ImGuiTreeNodeFlags_DefaultOpen)) {
     ImGui::PushID("POVs");
-    int povCount = m_data.povs.count;
-    if (ImGui::InputInt("Count", &povCount)) {
-      if (povCount < 0) {
-        povCount = 0;
+    if (ImGui::InputInt("Count", &m_povCount)) {
+      if (m_povCount < 0) {
+        m_povCount = 0;
       }
-      if (povCount > HAL_kMaxJoystickPOVs) {
-        povCount = HAL_kMaxJoystickPOVs;
+      if (m_povCount > HAL_kMaxJoystickPOVs) {
+        m_povCount = HAL_kMaxJoystickPOVs;
       }
-      m_data.povs.count = povCount;
     }
-    for (int i = 0; i < povCount; ++i) {
+    while (m_povCount > static_cast<int>(m_povConfig.size())) {
+      m_povStorage.emplace_back(std::make_unique<glass::Storage>());
+      m_povConfig.emplace_back(*m_povStorage.back());
+    }
+    for (int i = 0; i < m_povCount; ++i) {
       std::snprintf(label, sizeof(label), "POV %d", i);
       if (ImGui::TreeNodeEx(label, ImGuiTreeNodeFlags_DefaultOpen)) {
         EditKey("  0 deg", &m_povConfig[i].key0);
@@ -767,6 +714,23 @@
 void KeyboardJoystick::Update() {
   ImGuiIO& io = ImGui::GetIO();
 
+  if (m_axisCount < 0) {
+    m_axisCount = 0;
+  }
+  if (m_buttonCount < 0) {
+    m_buttonCount = 0;
+  }
+  if (m_povCount < 0) {
+    m_povCount = 0;
+  }
+
+  m_data.axes.count =
+      (std::min)(m_axisCount, static_cast<int>(m_axisConfig.size()));
+  m_data.buttons.count =
+      (std::min)(m_buttonCount, static_cast<int>(m_buttonKey.size()));
+  m_data.povs.count =
+      (std::min)(m_povCount, static_cast<int>(m_povConfig.size()));
+
   if (m_data.axes.count > 0 || m_data.buttons.count > 0 ||
       m_data.povs.count > 0) {
     m_present = true;
@@ -842,7 +806,6 @@
   for (auto&& joy : gRobotJoysticks) {
     if (m_guid == joy.guid) {
       joy.sys = this;
-      joy.guid.clear();
       break;
     }
   }
@@ -895,177 +858,88 @@
   }
 }
 
-void KeyboardJoystick::ReadIni(std::string_view name, std::string_view value) {
-  if (wpi::starts_with(name, "axis")) {
-    name.remove_prefix(4);
-    if (name == "Count") {
-      if (auto v = wpi::parse_integer<int>(value, 10)) {
-        m_data.axes.count = (std::min)(v.value(), HAL_kMaxJoystickAxes);
-      }
-      return;
-    }
-
-    auto index = wpi::consume_integer<unsigned int>(&name, 10).value_or(
-        HAL_kMaxJoystickAxes);
-    if (index >= HAL_kMaxJoystickAxes) {
-      return;
-    }
-    if (name == "incKey") {
-      if (auto v = wpi::parse_integer<int>(value, 10)) {
-        m_axisConfig[index].incKey = v.value();
-      }
-    } else if (name == "decKey") {
-      if (auto v = wpi::parse_integer<int>(value, 10)) {
-        m_axisConfig[index].decKey = v.value();
-      }
-    } else if (name == "keyRate") {
-      if (auto v = wpi::parse_float<float>(value)) {
-        m_axisConfig[index].keyRate = v.value();
-      }
-    } else if (name == "decayRate") {
-      if (auto v = wpi::parse_float<float>(value)) {
-        m_axisConfig[index].decayRate = v.value();
-      }
-    } else if (name == "maxAbsValue") {
-      if (auto v = wpi::parse_float<float>(value)) {
-        m_axisConfig[index].maxAbsValue = v.value();
-      }
-    }
-  } else if (wpi::starts_with(name, "button")) {
-    name.remove_prefix(6);
-    if (name == "Count") {
-      if (auto v = wpi::parse_integer<int>(value, 10)) {
-        m_data.buttons.count = (std::min)(v.value(), kMaxButtonCount);
-      }
-      return;
-    }
-
-    auto index =
-        wpi::parse_integer<unsigned int>(name, 10).value_or(kMaxButtonCount);
-    if (index >= kMaxButtonCount) {
-      return;
-    }
-    int v = wpi::parse_integer<int>(value, 10).value_or(-1);
-    if (v < 0 || v >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
-      return;
-    }
-    m_buttonKey[index] = v;
-  } else if (wpi::starts_with(name, "pov")) {
-    name.remove_prefix(3);
-    if (name == "Count") {
-      if (auto v = wpi::parse_integer<int>(value, 10)) {
-        m_data.povs.count = (std::min)(v.value(), HAL_kMaxJoystickPOVs);
-      }
-      return;
-    }
-
-    auto index = wpi::consume_integer<unsigned int>(&name, 10).value_or(
-        HAL_kMaxJoystickPOVs);
-    if (index >= HAL_kMaxJoystickPOVs) {
-      return;
-    }
-    int v = wpi::parse_integer<int>(value, 10).value_or(-1);
-    if (v < 0 || v >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) {
-      return;
-    }
-    if (name == "key0") {
-      m_povConfig[index].key0 = v;
-    } else if (name == "key45") {
-      m_povConfig[index].key45 = v;
-    } else if (name == "key90") {
-      m_povConfig[index].key90 = v;
-    } else if (name == "key135") {
-      m_povConfig[index].key135 = v;
-    } else if (name == "key180") {
-      m_povConfig[index].key180 = v;
-    } else if (name == "key225") {
-      m_povConfig[index].key225 = v;
-    } else if (name == "key270") {
-      m_povConfig[index].key270 = v;
-    } else if (name == "key315") {
-      m_povConfig[index].key315 = v;
-    }
-  }
-}
-
-void KeyboardJoystick::WriteIni(ImGuiTextBuffer* out_buf) const {
-  out_buf->appendf("axisCount=%d\nbuttonCount=%d\npovCount=%d\n",
-                   m_data.axes.count, m_data.buttons.count, m_data.povs.count);
-  for (int i = 0; i < m_data.axes.count; ++i) {
-    auto& c = m_axisConfig[i];
-    out_buf->appendf(
-        "axis%dincKey=%d\naxis%ddecKey=%d\naxis%dkeyRate=%f\n"
-        "axis%ddecayRate=%f\naxis%dmaxAbsValue=%f\n",
-        i, c.incKey, i, c.decKey, i, c.keyRate, i, c.decayRate, i,
-        c.maxAbsValue);
-  }
-  for (int i = 0; i < m_data.buttons.count; ++i) {
-    out_buf->appendf("button%d=%d\n", i, m_buttonKey[i]);
-  }
-  for (int i = 0; i < m_data.povs.count; ++i) {
-    auto& c = m_povConfig[i];
-    out_buf->appendf(
-        "pov%dkey0=%d\npov%dkey45=%d\npov%dkey90=%d\npov%dkey135=%d\n"
-        "pov%dkey180=%d\npov%dkey225=%d\npov%dkey270=%d\npov%dkey315=%d\n",
-        i, c.key0, i, c.key45, i, c.key90, i, c.key135, i, c.key180, i,
-        c.key225, i, c.key270, i, c.key315);
-  }
-}
-
-GlfwKeyboardJoystick::GlfwKeyboardJoystick(int index, bool noDefaults)
-    : KeyboardJoystick{index} {
-  if (noDefaults) {
-    return;
-  }
+GlfwKeyboardJoystick::GlfwKeyboardJoystick(glass::Storage& storage, int index)
+    : KeyboardJoystick{storage, index} {
   // set up a default keyboard config for 0, 1, and 2
   if (index == 0) {
-    m_data.axes.count = 3;
-    m_axisConfig[0].incKey = GLFW_KEY_D;
-    m_axisConfig[0].decKey = GLFW_KEY_A;
-    m_axisConfig[1].incKey = GLFW_KEY_S;
-    m_axisConfig[1].decKey = GLFW_KEY_W;
-    m_axisConfig[2].incKey = GLFW_KEY_R;
-    m_axisConfig[2].decKey = GLFW_KEY_E;
-    m_axisConfig[2].keyRate = 0.01f;
-    m_axisConfig[2].decayRate = 0;  // works like a throttle
-    m_data.buttons.count = 4;
-    m_buttonKey[0] = GLFW_KEY_Z;
-    m_buttonKey[1] = GLFW_KEY_X;
-    m_buttonKey[2] = GLFW_KEY_C;
-    m_buttonKey[3] = GLFW_KEY_V;
-    m_data.povs.count = 1;
-    m_povConfig[0].key0 = GLFW_KEY_KP_8;
-    m_povConfig[0].key45 = GLFW_KEY_KP_9;
-    m_povConfig[0].key90 = GLFW_KEY_KP_6;
-    m_povConfig[0].key135 = GLFW_KEY_KP_3;
-    m_povConfig[0].key180 = GLFW_KEY_KP_2;
-    m_povConfig[0].key225 = GLFW_KEY_KP_1;
-    m_povConfig[0].key270 = GLFW_KEY_KP_4;
-    m_povConfig[0].key315 = GLFW_KEY_KP_7;
+    if (m_axisCount == -1 && m_axisStorage.empty()) {
+      m_axisCount = 3;
+      for (int i = 0; i < 3; ++i) {
+        m_axisStorage.emplace_back(std::make_unique<glass::Storage>());
+        m_axisConfig.emplace_back(*m_axisStorage.back());
+      }
+      m_axisConfig[0].incKey = GLFW_KEY_D;
+      m_axisConfig[0].decKey = GLFW_KEY_A;
+      m_axisConfig[1].incKey = GLFW_KEY_S;
+      m_axisConfig[1].decKey = GLFW_KEY_W;
+      m_axisConfig[2].incKey = GLFW_KEY_R;
+      m_axisConfig[2].decKey = GLFW_KEY_E;
+      m_axisConfig[2].keyRate = 0.01f;
+      m_axisConfig[2].decayRate = 0;  // works like a throttle
+    }
+    if (m_buttonCount == -1 && m_buttonKey.empty()) {
+      m_buttonCount = 4;
+      m_buttonKey.resize(4);
+      m_buttonKey[0] = GLFW_KEY_Z;
+      m_buttonKey[1] = GLFW_KEY_X;
+      m_buttonKey[2] = GLFW_KEY_C;
+      m_buttonKey[3] = GLFW_KEY_V;
+    }
+    if (m_povCount == -1 && m_povStorage.empty()) {
+      m_povCount = 1;
+      m_povStorage.emplace_back(std::make_unique<glass::Storage>());
+      m_povConfig.emplace_back(*m_povStorage.back());
+      m_povConfig[0].key0 = GLFW_KEY_KP_8;
+      m_povConfig[0].key45 = GLFW_KEY_KP_9;
+      m_povConfig[0].key90 = GLFW_KEY_KP_6;
+      m_povConfig[0].key135 = GLFW_KEY_KP_3;
+      m_povConfig[0].key180 = GLFW_KEY_KP_2;
+      m_povConfig[0].key225 = GLFW_KEY_KP_1;
+      m_povConfig[0].key270 = GLFW_KEY_KP_4;
+      m_povConfig[0].key315 = GLFW_KEY_KP_7;
+    }
   } else if (index == 1) {
-    m_data.axes.count = 2;
-    m_axisConfig[0].incKey = GLFW_KEY_L;
-    m_axisConfig[0].decKey = GLFW_KEY_J;
-    m_axisConfig[1].incKey = GLFW_KEY_K;
-    m_axisConfig[1].decKey = GLFW_KEY_I;
-    m_data.buttons.count = 4;
-    m_buttonKey[0] = GLFW_KEY_M;
-    m_buttonKey[1] = GLFW_KEY_COMMA;
-    m_buttonKey[2] = GLFW_KEY_PERIOD;
-    m_buttonKey[3] = GLFW_KEY_SLASH;
+    if (m_axisCount == -1 && m_axisStorage.empty()) {
+      m_axisCount = 2;
+      for (int i = 0; i < 2; ++i) {
+        m_axisStorage.emplace_back(std::make_unique<glass::Storage>());
+        m_axisConfig.emplace_back(*m_axisStorage.back());
+      }
+      m_axisConfig[0].incKey = GLFW_KEY_L;
+      m_axisConfig[0].decKey = GLFW_KEY_J;
+      m_axisConfig[1].incKey = GLFW_KEY_K;
+      m_axisConfig[1].decKey = GLFW_KEY_I;
+    }
+    if (m_buttonCount == -1 && m_buttonKey.empty()) {
+      m_buttonCount = 4;
+      m_buttonKey.resize(4);
+      m_buttonKey[0] = GLFW_KEY_M;
+      m_buttonKey[1] = GLFW_KEY_COMMA;
+      m_buttonKey[2] = GLFW_KEY_PERIOD;
+      m_buttonKey[3] = GLFW_KEY_SLASH;
+    }
   } else if (index == 2) {
-    m_data.axes.count = 2;
-    m_axisConfig[0].incKey = GLFW_KEY_RIGHT;
-    m_axisConfig[0].decKey = GLFW_KEY_LEFT;
-    m_axisConfig[1].incKey = GLFW_KEY_DOWN;
-    m_axisConfig[1].decKey = GLFW_KEY_UP;
-    m_data.buttons.count = 6;
-    m_buttonKey[0] = GLFW_KEY_INSERT;
-    m_buttonKey[1] = GLFW_KEY_HOME;
-    m_buttonKey[2] = GLFW_KEY_PAGE_UP;
-    m_buttonKey[3] = GLFW_KEY_DELETE;
-    m_buttonKey[4] = GLFW_KEY_END;
-    m_buttonKey[5] = GLFW_KEY_PAGE_DOWN;
+    if (m_axisCount == -1 && m_axisStorage.empty()) {
+      m_axisCount = 2;
+      for (int i = 0; i < 2; ++i) {
+        m_axisStorage.emplace_back(std::make_unique<glass::Storage>());
+        m_axisConfig.emplace_back(*m_axisStorage.back());
+      }
+      m_axisConfig[0].incKey = GLFW_KEY_RIGHT;
+      m_axisConfig[0].decKey = GLFW_KEY_LEFT;
+      m_axisConfig[1].incKey = GLFW_KEY_DOWN;
+      m_axisConfig[1].decKey = GLFW_KEY_UP;
+    }
+    if (m_buttonCount == -1 && m_buttonKey.empty()) {
+      m_buttonCount = 6;
+      m_buttonKey.resize(6);
+      m_buttonKey[0] = GLFW_KEY_INSERT;
+      m_buttonKey[1] = GLFW_KEY_HOME;
+      m_buttonKey[2] = GLFW_KEY_PAGE_UP;
+      m_buttonKey[3] = GLFW_KEY_DELETE;
+      m_buttonKey[4] = GLFW_KEY_END;
+      m_buttonKey[5] = GLFW_KEY_PAGE_DOWN;
+    }
   }
 }
 
@@ -1103,6 +977,11 @@
   return "(Unknown)";
 }
 
+RobotJoystick::RobotJoystick(glass::Storage& storage)
+    : name{storage.GetString("name")},
+      guid{storage.GetString("guid")},
+      useGamepad{storage.GetBool("useGamepad")} {}
+
 void RobotJoystick::Update() {
   Clear();
   if (sys) {
@@ -1111,7 +990,9 @@
 }
 
 void RobotJoystick::SetHAL(int i) {
-  if (!gZeroDisconnectedJoysticks && (!sys || !sys->IsPresent())) {
+  if ((gpZeroDisconnectedJoysticks != nullptr &&
+       !gpZeroDisconnectedJoysticks) &&
+      (!sys || !sys->IsPresent())) {
     return;
   }
   // set at HAL level
@@ -1149,11 +1030,11 @@
 
   bool disableDS = IsDSDisabled();
   if (disableDS && !prevDisableDS) {
-    if (auto win = HALSimGui::manager.GetWindow("System Joysticks")) {
+    if (auto win = HALSimGui::manager->GetWindow("System Joysticks")) {
       win->SetVisibility(glass::Window::kDisabled);
     }
   } else if (!disableDS && prevDisableDS) {
-    if (auto win = HALSimGui::manager.GetWindow("System Joysticks")) {
+    if (auto win = HALSimGui::manager->GetWindow("System Joysticks")) {
       win->SetVisibility(glass::Window::kShow);
     }
   }
@@ -1190,7 +1071,7 @@
     // DS hotkeys
     bool enableHotkey = false;
     bool disableHotkey = false;
-    if (gUseEnableDisableHotkeys) {
+    if (gpUseEnableDisableHotkeys != nullptr && *gpUseEnableDisableHotkeys) {
       ImGuiIO& io = ImGui::GetIO();
       if (io.KeysDown[GLFW_KEY_ENTER] || io.KeysDown[GLFW_KEY_KP_ENTER]) {
         disableHotkey = true;
@@ -1200,7 +1081,7 @@
         enableHotkey = true;
       }
     }
-    if (gUseEstopHotkey) {
+    if (gpUseEstopHotkey != nullptr && *gpUseEstopHotkey) {
       ImGuiIO& io = ImGui::GetIO();
       if (io.KeysDown[GLFW_KEY_SPACE]) {
         HALSIM_SetDriverStationEnabled(false);
@@ -1232,7 +1113,8 @@
   }
 
   // Update HAL
-  for (int i = 0; i < HAL_kMaxJoysticks; ++i) {
+  for (int i = 0, end = gRobotJoysticks.size();
+       i < end && i < HAL_kMaxJoysticks; ++i) {
     gRobotJoysticks[i].SetHAL(i);
   }
 
@@ -1251,6 +1133,7 @@
   m_enabled.SetDigital(true);
   m_test.SetDigital(true);
   m_autonomous.SetDigital(true);
+  m_matchTime.SetValue(-1.0);
 }
 
 void FMSSimModel::Update() {
@@ -1264,25 +1147,23 @@
   m_autonomous.SetValue(HALSIM_GetDriverStationAutonomous());
 
   double matchTime = HALSIM_GetDriverStationMatchTime();
-  if (m_matchTimeEnabled && !IsDSDisabled()) {
+  if (!IsDSDisabled() && enabled) {
     int32_t status = 0;
     double curTime = HAL_GetFPGATime(&status) * 1.0e-6;
-    if (m_startMatchTime == 0.0) {
-      m_startMatchTime = curTime;
+    if (m_startMatchTime == -1.0) {
+      m_startMatchTime = matchTime + curTime;
     }
-    if (enabled) {
-      matchTime = curTime - m_startMatchTime;
-      HALSIM_SetDriverStationMatchTime(matchTime);
-    } else {
-      if (m_prevTime == 0.0) {
-        m_prevTime = curTime;
-      }
-      m_startMatchTime += (curTime - m_prevTime);
+    matchTime = m_startMatchTime - curTime;
+    if (matchTime < 0) {
+      matchTime = -1.0;
     }
-    m_prevTime = curTime;
+    HALSIM_SetDriverStationMatchTime(matchTime);
   } else {
-    m_startMatchTime = 0.0;
-    m_prevTime = 0.0;
+    if (m_startMatchTime != -1.0) {
+      matchTime = -1.0;
+      HALSIM_SetDriverStationMatchTime(matchTime);
+    }
+    m_startMatchTime = -1.0;
   }
   m_matchTime.SetValue(matchTime);
 }
@@ -1329,7 +1210,7 @@
       char buf[64];
       std::snprintf(buf, sizeof(buf), "%s Settings", joy->GetName());
       if (ImGui::MenuItem(buf)) {
-        if (auto win = DriverStationGui::dsManager.GetWindow(buf)) {
+        if (auto win = DriverStationGui::dsManager->GetWindow(buf)) {
           win->SetVisible(true);
         }
         ImGui::CloseCurrentPopup();
@@ -1370,10 +1251,11 @@
         for (auto&& joy2 : gRobotJoysticks) {
           if (joy2.sys == payload_sys) {
             joy2.sys = nullptr;
+            joy2.guid.clear();
           }
         }
         joy.sys = payload_sys;
-        joy.guid.clear();
+        joy.guid = payload_sys->GetGUID();
         std::string_view name{payload_sys->GetName()};
         joy.useGamepad =
             wpi::starts_with(name, "Xbox") || wpi::contains(name, "pad");
@@ -1453,15 +1335,23 @@
 }
 
 void DSManager::DisplayMenu() {
-  if (gDSSocketConnected && *gDSSocketConnected) {
+  if (gpDSSocketConnected && *gpDSSocketConnected) {
     ImGui::MenuItem("Turn off DS (real DS connected)", nullptr, true, false);
   } else {
-    ImGui::MenuItem("Turn off DS", nullptr, &gDisableDS);
-    ImGui::MenuItem("Zero disconnected joysticks", nullptr,
-                    &gZeroDisconnectedJoysticks);
-    ImGui::MenuItem("Enable on []\\ combo, Disable on Enter", nullptr,
-                    &gUseEnableDisableHotkeys);
-    ImGui::MenuItem("Disable on Spacebar", nullptr, &gUseEstopHotkey);
+    if (gpDisableDS != nullptr) {
+      ImGui::MenuItem("Turn off DS", nullptr, gpDisableDS);
+    }
+    if (gpZeroDisconnectedJoysticks != nullptr) {
+      ImGui::MenuItem("Zero disconnected joysticks", nullptr,
+                      gpZeroDisconnectedJoysticks);
+    }
+    if (gpUseEnableDisableHotkeys != nullptr) {
+      ImGui::MenuItem("Enable on []\\ combo, Disable on Enter", nullptr,
+                      gpUseEnableDisableHotkeys);
+    }
+    if (gpUseEstopHotkey != nullptr) {
+      ImGui::MenuItem("Disable on Spacebar", nullptr, gpUseEstopHotkey);
+    }
   }
   ImGui::Separator();
 
@@ -1470,85 +1360,93 @@
   }
 }
 
-static void DriverStationInitialize() {
-  // hook ini handler to save joystick settings
-  ImGuiSettingsHandler iniHandler;
-  iniHandler.TypeName = "Joystick";
-  iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
-  iniHandler.ReadOpenFn = JoystickReadOpen;
-  iniHandler.ReadLineFn = JoystickReadLine;
-  iniHandler.WriteAllFn = JoystickWriteAll;
-  ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
-
-  // hook ini handler to save keyboard settings
-  iniHandler.TypeName = "KeyboardJoystick";
-  iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
-  iniHandler.ReadOpenFn = KeyboardJoystickReadOpen;
-  iniHandler.ReadLineFn = KeyboardJoystickReadLine;
-  iniHandler.WriteAllFn = KeyboardJoystickWriteAll;
-  ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
-
-  // hook ini handler to save DS settings
-  iniHandler.TypeName = "DriverStation";
-  iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
-  iniHandler.ReadOpenFn = DriverStationReadOpen;
-  iniHandler.ReadLineFn = DriverStationReadLine;
-  iniHandler.WriteAllFn = DriverStationWriteAll;
-  ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
-}
-
 void DriverStationGui::GlobalInit() {
+  auto& storageRoot = glass::GetStorageRoot("ds");
+  dsManager = std::make_unique<DSManager>(storageRoot);
+
   // set up system joysticks (both GLFW and keyboard)
   for (int i = 0; i <= GLFW_JOYSTICK_LAST; ++i) {
     gGlfwJoysticks.emplace_back(std::make_unique<GlfwSystemJoystick>(i));
   }
-  for (int i = 0; i < 4; ++i) {
-    gKeyboardJoysticks.emplace_back(std::make_unique<GlfwKeyboardJoystick>(i));
-  }
 
-  dsManager.GlobalInit();
-
-  wpi::gui::AddInit(DriverStationInitialize);
+  dsManager->GlobalInit();
 
   gFMSModel = std::make_unique<FMSSimModel>();
 
   wpi::gui::AddEarlyExecute(DriverStationExecute);
   wpi::gui::AddEarlyExecute([] { gFMSModel->Update(); });
-  if (auto win = dsManager.AddWindow("FMS", [] {
-        DisplayFMS(gFMSModel.get(), &gFMSModel->m_matchTimeEnabled);
-      })) {
-    win->DisableRenamePopup();
-    win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
-    win->SetDefaultPos(5, 540);
-  }
-  if (auto win =
-          dsManager.AddWindow("System Joysticks", DisplaySystemJoysticks)) {
-    win->DisableRenamePopup();
-    win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
-    win->SetDefaultPos(5, 350);
-  }
-  if (auto win = dsManager.AddWindow("Joysticks", DisplayJoysticks)) {
-    win->DisableRenamePopup();
-    win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
-    win->SetDefaultPos(250, 465);
-  }
-  int i = 0;
-  for (auto&& joy : gKeyboardJoysticks) {
-    char label[64];
-    std::snprintf(label, sizeof(label), "%s Settings", joy->GetName());
-    if (auto win = dsManager.AddWindow(
-            label, [j = joy.get()] { j->SettingsDisplay(); })) {
-      win->SetVisible(false);
-      win->DisableRenamePopup();
-      win->SetDefaultPos(10 + 310 * i++, 50);
-      if (i > 3) {
-        i = 0;
+
+  storageRoot.SetCustomApply([&storageRoot] {
+    gpDisableDS = &storageRoot.GetBool("disable", false);
+    gpZeroDisconnectedJoysticks =
+        &storageRoot.GetBool("zeroDisconnectedJoysticks", true);
+    gpUseEnableDisableHotkeys =
+        &storageRoot.GetBool("useEnableDisableHotkeys", false);
+    gpUseEstopHotkey = &storageRoot.GetBool("useEstopHotkey", false);
+
+    auto& keyboardStorage = storageRoot.GetChildArray("keyboardJoysticks");
+    keyboardStorage.resize(4);
+    for (int i = 0; i < 4; ++i) {
+      if (!keyboardStorage[i]) {
+        keyboardStorage[i] = std::make_unique<glass::Storage>();
       }
-      win->SetDefaultSize(300, 560);
+      gKeyboardJoysticks.emplace_back(
+          std::make_unique<GlfwKeyboardJoystick>(*keyboardStorage[i], i));
     }
-  }
+
+    auto& robotJoystickStorage = storageRoot.GetChildArray("robotJoysticks");
+    robotJoystickStorage.resize(HAL_kMaxJoysticks);
+    for (int i = 0; i < HAL_kMaxJoysticks; ++i) {
+      if (!robotJoystickStorage[i]) {
+        robotJoystickStorage[i] = std::make_unique<glass::Storage>();
+      }
+      gRobotJoysticks.emplace_back(*robotJoystickStorage[i]);
+    }
+
+    int i = 0;
+    for (auto&& joy : gKeyboardJoysticks) {
+      char label[64];
+      std::snprintf(label, sizeof(label), "%s Settings", joy->GetName());
+      if (auto win = dsManager->AddWindow(
+              label, [j = joy.get()] { j->SettingsDisplay(); },
+              glass::Window::kHide)) {
+        win->DisableRenamePopup();
+        win->SetDefaultPos(10 + 310 * i++, 50);
+        if (i > 3) {
+          i = 0;
+        }
+        win->SetDefaultSize(300, 560);
+      }
+    }
+    if (auto win =
+            dsManager->AddWindow("FMS", [] { DisplayFMS(gFMSModel.get()); })) {
+      win->DisableRenamePopup();
+      win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+      win->SetDefaultPos(5, 540);
+    }
+    if (auto win =
+            dsManager->AddWindow("System Joysticks", DisplaySystemJoysticks)) {
+      win->DisableRenamePopup();
+      win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+      win->SetDefaultPos(5, 350);
+    }
+    if (auto win = dsManager->AddWindow("Joysticks", DisplayJoysticks)) {
+      win->DisableRenamePopup();
+      win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
+      win->SetDefaultPos(250, 465);
+    }
+  });
+
+  storageRoot.SetCustomClear([&storageRoot] {
+    dsManager->EraseWindows();
+    gKeyboardJoysticks.clear();
+    gRobotJoysticks.clear();
+    storageRoot.GetChildArray("keyboardJoysticks").clear();
+    storageRoot.GetChildArray("robotJoysticks").clear();
+    storageRoot.ClearValues();
+  });
 }
 
 void DriverStationGui::SetDSSocketExtension(void* data) {
-  gDSSocketConnected = static_cast<std::atomic<bool>*>(data);
+  gpDSSocketConnected = static_cast<std::atomic<bool>*>(data);
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h
index 51f7554..cb086c8 100644
--- a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h
+++ b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h
@@ -6,13 +6,14 @@
 
 #include <glass/WindowManager.h>
 
+#include <memory>
 #include <string_view>
 
 namespace halsimgui {
 
 class DSManager : public glass::WindowManager {
  public:
-  explicit DSManager(std::string_view iniName) : WindowManager{iniName} {}
+  explicit DSManager(glass::Storage& storage) : WindowManager{storage} {}
 
   void DisplayMenu() override;
 };
@@ -22,7 +23,7 @@
   static void GlobalInit();
   static void SetDSSocketExtension(void* data);
 
-  static DSManager dsManager;
+  static std::unique_ptr<DSManager> dsManager;
 };
 
 }  // namespace halsimgui
diff --git a/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp
index 56e8ea2..6a1be27 100644
--- a/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp
@@ -246,7 +246,7 @@
 }
 
 void EncoderSimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "Encoders", EncodersAnyInitialized,
       [] { return std::make_unique<EncodersSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
@@ -258,7 +258,7 @@
 }
 
 glass::EncodersModel& EncoderSimGui::GetEncodersModel() {
-  static auto model = HALSimGui::halProvider.GetModel("Encoders");
+  static auto model = HALSimGui::halProvider->GetModel("Encoders");
   assert(model);
   return *static_cast<glass::EncodersModel*>(model);
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp b/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp
index 72b9c29..dffcd00 100644
--- a/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp
@@ -5,6 +5,7 @@
 #include "HALProvider.h"
 
 #include <glass/Model.h>
+#include <glass/Storage.h>
 
 #include <algorithm>
 #include <string>
@@ -15,6 +16,29 @@
 
 static bool gDisableOutputsOnDSDisable = true;
 
+HALProvider::HALProvider(glass::Storage& storage) : Provider{storage} {
+  storage.SetCustomApply([this] {
+    for (auto&& childIt : m_storage.GetChildren()) {
+      auto it = FindViewEntry(childIt.key());
+      if (it != m_viewEntries.end() && (*it)->name == childIt.key()) {
+        Show(it->get(), nullptr);
+      }
+    }
+    for (auto&& entry : m_viewEntries) {
+      if (entry->showDefault) {
+        Show(entry.get(), entry->window);
+      }
+    }
+  });
+  storage.SetCustomClear([this, &storage] {
+    for (auto&& entry : m_viewEntries) {
+      entry->window = nullptr;
+    }
+    m_windows.clear();
+    storage.ClearValues();
+  });
+}
+
 bool HALProvider::AreOutputsDisabled() {
   return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled();
 }
@@ -28,34 +52,20 @@
     bool visible = viewEntry->window && viewEntry->window->IsVisible();
     bool wasVisible = visible;
     bool exists = viewEntry->modelEntry->exists();
-    ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible,
-                    visible || exists);
-    if (!wasVisible && visible) {
-      Show(viewEntry.get(), viewEntry->window);
-    } else if (wasVisible && !visible && viewEntry->window) {
-      viewEntry->window->SetVisible(false);
+    if (ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible,
+                        visible || exists)) {
+      if (!wasVisible && visible) {
+        Show(viewEntry.get(), viewEntry->window);
+        if (viewEntry->window) {
+          viewEntry->window->SetVisible(true);
+        }
+      } else if (wasVisible && !visible && viewEntry->window) {
+        viewEntry->window->SetVisible(false);
+      }
     }
   }
 }
 
-void HALProvider::Update() {
-  Provider::Update();
-
-  // check for visible windows that need displays (typically this is due to
-  // file loading)
-  for (auto&& window : m_windows) {
-    if (!window->IsVisible() || window->HasView()) {
-      continue;
-    }
-    auto id = window->GetId();
-    auto it = FindViewEntry(id);
-    if (it == m_viewEntries.end() || (*it)->name != id) {
-      continue;
-    }
-    Show(it->get(), window.get());
-  }
-}
-
 glass::Model* HALProvider::GetModel(std::string_view name) {
   auto it = FindModelEntry(name);
   if (it == m_modelEntries.end() || (*it)->name != name) {
@@ -71,9 +81,8 @@
 }
 
 void HALProvider::Show(ViewEntry* entry, glass::Window* window) {
-  // if there's already a window, just show it
+  // if there's already a window, we're done
   if (entry->window) {
-    entry->window->SetVisible(true);
     return;
   }
 
@@ -87,7 +96,9 @@
 
   // the window might exist and we're just not associated to it yet
   if (!window) {
-    window = GetOrAddWindow(entry->name, true);
+    window = GetOrAddWindow(
+        entry->name, true,
+        entry->showDefault ? glass::Window::kShow : glass::Window::kHide);
   }
   if (!window) {
     return;
@@ -100,6 +111,4 @@
     return;
   }
   window->SetView(std::move(view));
-
-  entry->window->SetVisible(true);
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp
index 8d66ac7..4d005ad 100644
--- a/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp
@@ -4,22 +4,51 @@
 
 #include "HALSimGui.h"
 
+#include <glass/Context.h>
+#include <glass/Storage.h>
+
 #include <imgui.h>
 #include <wpigui.h>
 
 using namespace halsimgui;
 
 glass::MainMenuBar HALSimGui::mainMenu;
-glass::WindowManager HALSimGui::manager{"SimWindow"};
-HALProvider HALSimGui::halProvider{"HALProvider"};
-glass::NetworkTablesProvider HALSimGui::ntProvider{"NTProvider"};
+std::unique_ptr<glass::WindowManager> HALSimGui::manager;
+std::unique_ptr<HALProvider> HALSimGui::halProvider;
+std::unique_ptr<glass::NetworkTablesProvider> HALSimGui::ntProvider;
 
 void HALSimGui::GlobalInit() {
-  manager.GlobalInit();
-  halProvider.GlobalInit();
-  ntProvider.GlobalInit();
+  manager = std::make_unique<glass::WindowManager>(
+      glass::GetStorageRoot().GetChild("SimWindow"));
+  manager->GlobalInit();
+  halProvider = std::make_unique<HALProvider>(
+      glass::GetStorageRoot().GetChild("HALProvider"));
+  halProvider->GlobalInit();
+  ntProvider = std::make_unique<glass::NetworkTablesProvider>(
+      glass::GetStorageRoot().GetChild("NTProvider"));
+  ntProvider->GlobalInit();
 
   wpi::gui::AddLateExecute([] { mainMenu.Display(); });
 
-  glass::AddStandardNetworkTablesViews(ntProvider);
+  glass::AddStandardNetworkTablesViews(*ntProvider);
 }
+
+namespace halsimgui {
+
+void AddGuiInit(std::function<void()> initialize) {
+  wpi::gui::AddInit(std::move(initialize));
+}
+
+void AddGuiEarlyExecute(std::function<void()> execute) {
+  wpi::gui::AddEarlyExecute(std::move(execute));
+}
+
+void AddGuiLateExecute(std::function<void()> execute) {
+  wpi::gui::AddLateExecute(std::move(execute));
+}
+
+void GuiExit() {
+  wpi::gui::Exit();
+}
+
+}  // namespace halsimgui
diff --git a/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp
index c17a4f8..325673a 100644
--- a/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp
@@ -4,6 +4,8 @@
 
 #include "NetworkTablesSimGui.h"
 
+#include <glass/Context.h>
+#include <glass/Storage.h>
 #include <glass/networktables/NetworkTables.h>
 
 #include <wpigui.h>
@@ -13,21 +15,24 @@
 using namespace halsimgui;
 
 static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
-static std::unique_ptr<glass::NetworkTablesView> gNetworkTablesView;
-static glass::Window* gNetworkTablesWindow;
+static std::unique_ptr<glass::Window> gNetworkTablesWindow;
 
 void NetworkTablesSimGui::Initialize() {
   gNetworkTablesModel = std::make_unique<glass::NetworkTablesModel>();
-  gNetworkTablesView =
-      std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get());
   wpi::gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); });
-  gNetworkTablesWindow = HALSimGui::ntProvider.AddWindow(
-      "NetworkTables", [] { gNetworkTablesView->Display(); });
-  if (gNetworkTablesWindow) {
-    gNetworkTablesWindow->SetDefaultPos(250, 277);
-    gNetworkTablesWindow->SetDefaultSize(750, 185);
-    gNetworkTablesWindow->DisableRenamePopup();
-  }
+  gNetworkTablesWindow = std::make_unique<glass::Window>(
+      glass::GetStorageRoot().GetChild("NetworkTables View"), "NetworkTables");
+  gNetworkTablesWindow->SetView(
+      std::make_unique<glass::NetworkTablesView>(gNetworkTablesModel.get()));
+  gNetworkTablesWindow->SetDefaultPos(250, 277);
+  gNetworkTablesWindow->SetDefaultSize(750, 185);
+  gNetworkTablesWindow->DisableRenamePopup();
+  wpi::gui::AddLateExecute([] { gNetworkTablesWindow->Display(); });
+
+  wpi::gui::AddWindowScaler([](float scale) {
+    // scale default window positions
+    gNetworkTablesWindow->ScaleDefault(scale);
+  });
 }
 
 void NetworkTablesSimGui::DisplayMenu() {
diff --git a/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp
index bcc510a..869c5b1 100644
--- a/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp
@@ -197,10 +197,10 @@
 }
 
 void PCMSimGui::Initialize() {
-  HALSimGui::halProvider.RegisterModel("CTREPCMs", PCMsAnyInitialized, [] {
+  HALSimGui::halProvider->RegisterModel("CTREPCMs", PCMsAnyInitialized, [] {
     return std::make_unique<PCMsSimModel>();
   });
-  HALSimGui::halProvider.RegisterView(
+  HALSimGui::halProvider->RegisterView(
       "Solenoids", "CTREPCMs",
       [](glass::Model* model) {
         bool any = false;
@@ -218,14 +218,14 @@
         return glass::MakeFunctionView([=] {
           glass::DisplayPCMsSolenoids(
               static_cast<PCMsSimModel*>(model),
-              HALSimGui::halProvider.AreOutputsEnabled());
+              HALSimGui::halProvider->AreOutputsEnabled());
         });
       });
 
   SimDeviceGui::GetDeviceTree().Add(
-      HALSimGui::halProvider.GetModel("CTREPCMs"), [](glass::Model* model) {
+      HALSimGui::halProvider->GetModel("CTREPCMs"), [](glass::Model* model) {
         glass::DisplayCompressorsDevice(
             static_cast<PCMsSimModel*>(model),
-            HALSimGui::halProvider.AreOutputsEnabled());
+            HALSimGui::halProvider->AreOutputsEnabled());
       });
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp
index 3cb5e1a..9eceabd 100644
--- a/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp
@@ -105,7 +105,7 @@
 }
 
 void PWMSimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "PWM Outputs", PWMsAnyInitialized,
       [] { return std::make_unique<PWMsSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
@@ -113,7 +113,7 @@
         win->SetDefaultPos(910, 20);
         return glass::MakeFunctionView([=] {
           glass::DisplayPWMs(static_cast<PWMsSimModel*>(model),
-                             HALSimGui::halProvider.AreOutputsEnabled());
+                             HALSimGui::halProvider->AreOutputsEnabled());
         });
       });
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp
index c511345..c136c7e 100644
--- a/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp
@@ -124,7 +124,7 @@
 }
 
 void PowerDistributionSimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "PowerDistributions", PowerDistributionsAnyInitialized,
       [] { return std::make_unique<PowerDistributionsSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
diff --git a/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp
index 3815617..badb4f9 100644
--- a/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp
@@ -104,7 +104,7 @@
 }
 
 void RelaySimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "Relays", RelayAnyInitialized,
       [] { return std::make_unique<RelaysSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
@@ -112,7 +112,7 @@
         win->SetDefaultPos(180, 20);
         return glass::MakeFunctionView([=] {
           glass::DisplayRelays(static_cast<RelaysSimModel*>(model),
-                               HALSimGui::halProvider.AreOutputsEnabled());
+                               HALSimGui::halProvider->AreOutputsEnabled());
         });
       });
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp
index ba13a2a..b693569 100644
--- a/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp
@@ -132,7 +132,7 @@
 }  // namespace
 
 void RoboRioSimGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "RoboRIO", [] { return true; },
       [] { return std::make_unique<RoboRioSimModel>(); },
       [](glass::Window* win, glass::Model* model) {
diff --git a/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp b/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp
index 62e2896..55b83ce 100644
--- a/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp
@@ -155,7 +155,7 @@
 }
 
 void SimDeviceGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "Other Devices", [] { return true; },
       [] { return std::make_unique<glass::DeviceTreeModel>(); },
       [](glass::Window* win, glass::Model* model) {
@@ -170,7 +170,7 @@
           static_cast<glass::DeviceTreeModel*>(model)->Display();
         });
       });
-  HALSimGui::halProvider.ShowDefault("Other Devices");
+  HALSimGui::halProvider->ShowDefault("Other Devices");
 
   auto model = std::make_unique<SimDevicesModel>();
   gSimDevicesModel = model.get();
@@ -185,7 +185,7 @@
 }
 
 glass::DeviceTreeModel& SimDeviceGui::GetDeviceTree() {
-  static auto model = HALSimGui::halProvider.GetModel("Other Devices");
+  static auto model = HALSimGui::halProvider->GetModel("Other Devices");
   assert(model);
   return *static_cast<glass::DeviceTreeModel*>(model);
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp b/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp
index 1928063..3ccf630 100644
--- a/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp
@@ -71,7 +71,7 @@
 }
 
 void TimingGui::Initialize() {
-  HALSimGui::halProvider.Register(
+  HALSimGui::halProvider->Register(
       "Timing", [] { return true; },
       [] { return std::make_unique<TimingModel>(); },
       [](glass::Window* win, glass::Model* model) {
@@ -80,5 +80,5 @@
         win->SetDefaultPos(5, 150);
         return glass::MakeFunctionView(DisplayTiming);
       });
-  HALSimGui::halProvider.ShowDefault("Timing");
+  HALSimGui::halProvider->ShowDefault("Timing");
 }
diff --git a/simulation/halsim_gui/src/main/native/cpp/main.cpp b/simulation/halsim_gui/src/main/native/cpp/main.cpp
index 6df6f35..d0db8d4 100644
--- a/simulation/halsim_gui/src/main/native/cpp/main.cpp
+++ b/simulation/halsim_gui/src/main/native/cpp/main.cpp
@@ -3,6 +3,7 @@
 // the WPILib BSD license file in the root directory of this project.
 
 #include <glass/Context.h>
+#include <glass/Storage.h>
 #include <glass/other/Plot.h>
 
 #include <cstdio>
@@ -22,6 +23,7 @@
 #include "DriverStationGui.h"
 #include "EncoderSimGui.h"
 #include "HALSimGui.h"
+#include "HALSimGuiExt.h"
 #include "NetworkTablesSimGui.h"
 #include "PCMSimGui.h"
 #include "PWMSimGui.h"
@@ -35,7 +37,7 @@
 
 namespace gui = wpi::gui;
 
-static glass::PlotProvider gPlotProvider{"Plot"};
+static std::unique_ptr<glass::PlotProvider> gPlotProvider;
 
 extern "C" {
 #if defined(WIN32) || defined(_WIN32)
@@ -46,54 +48,70 @@
 
   gui::CreateContext();
   glass::CreateContext();
+
+  glass::SetStorageName("simgui");
+
+  HAL_RegisterExtension(HALSIMGUI_EXT_ADDGUIINIT,
+                        reinterpret_cast<void*>((AddGuiInitFn)&AddGuiInit));
+  HAL_RegisterExtension(
+      HALSIMGUI_EXT_ADDGUILATEEXECUTE,
+      reinterpret_cast<void*>((AddGuiLateExecuteFn)&AddGuiLateExecute));
+  HAL_RegisterExtension(
+      HALSIMGUI_EXT_ADDGUIEARLYEXECUTE,
+      reinterpret_cast<void*>((AddGuiEarlyExecuteFn)&AddGuiEarlyExecute));
+  HAL_RegisterExtension(HALSIMGUI_EXT_GUIEXIT,
+                        reinterpret_cast<void*>((GuiExitFn)&GuiExit));
+
   HALSimGui::GlobalInit();
   DriverStationGui::GlobalInit();
-  gPlotProvider.GlobalInit();
+  gPlotProvider = std::make_unique<glass::PlotProvider>(
+      glass::GetStorageRoot().GetChild("Plot"));
+  gPlotProvider->GlobalInit();
 
   // These need to initialize first
-  gui::AddInit(EncoderSimGui::Initialize);
-  gui::AddInit(SimDeviceGui::Initialize);
+  EncoderSimGui::Initialize();
+  SimDeviceGui::Initialize();
 
-  gui::AddInit(AccelerometerSimGui::Initialize);
-  gui::AddInit(AddressableLEDGui::Initialize);
-  gui::AddInit(AnalogGyroSimGui::Initialize);
-  gui::AddInit(AnalogInputSimGui::Initialize);
-  gui::AddInit(AnalogOutputSimGui::Initialize);
-  gui::AddInit(DIOSimGui::Initialize);
-  gui::AddInit(NetworkTablesSimGui::Initialize);
-  gui::AddInit(PCMSimGui::Initialize);
-  gui::AddInit(PowerDistributionSimGui::Initialize);
-  gui::AddInit(PWMSimGui::Initialize);
-  gui::AddInit(RelaySimGui::Initialize);
-  gui::AddInit(RoboRioSimGui::Initialize);
-  gui::AddInit(TimingGui::Initialize);
+  AccelerometerSimGui::Initialize();
+  AddressableLEDGui::Initialize();
+  AnalogGyroSimGui::Initialize();
+  AnalogInputSimGui::Initialize();
+  AnalogOutputSimGui::Initialize();
+  DIOSimGui::Initialize();
+  NetworkTablesSimGui::Initialize();
+  PCMSimGui::Initialize();
+  PowerDistributionSimGui::Initialize();
+  PWMSimGui::Initialize();
+  RelaySimGui::Initialize();
+  RoboRioSimGui::Initialize();
+  TimingGui::Initialize();
 
   HALSimGui::mainMenu.AddMainMenu([] {
     if (ImGui::BeginMenu("Hardware")) {
-      HALSimGui::halProvider.DisplayMenu();
+      HALSimGui::halProvider->DisplayMenu();
       ImGui::EndMenu();
     }
     if (ImGui::BeginMenu("NetworkTables")) {
       NetworkTablesSimGui::DisplayMenu();
       ImGui::Separator();
-      HALSimGui::ntProvider.DisplayMenu();
+      HALSimGui::ntProvider->DisplayMenu();
       ImGui::EndMenu();
     }
     if (ImGui::BeginMenu("DS")) {
-      DriverStationGui::dsManager.DisplayMenu();
+      DriverStationGui::dsManager->DisplayMenu();
       ImGui::EndMenu();
     }
     if (ImGui::BeginMenu("Plot")) {
-      bool paused = gPlotProvider.IsPaused();
+      bool paused = gPlotProvider->IsPaused();
       if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
-        gPlotProvider.SetPaused(paused);
+        gPlotProvider->SetPaused(paused);
       }
       ImGui::Separator();
-      gPlotProvider.DisplayMenu();
+      gPlotProvider->DisplayMenu();
       ImGui::EndMenu();
     }
     if (ImGui::BeginMenu("Window")) {
-      HALSimGui::manager.DisplayMenu();
+      HALSimGui::manager->DisplayMenu();
       ImGui::EndMenu();
     }
   });
diff --git a/simulation/halsim_gui/src/main/native/include/HALProvider.h b/simulation/halsim_gui/src/main/native/include/HALProvider.h
index e3098db..0a26a09 100644
--- a/simulation/halsim_gui/src/main/native/include/HALProvider.h
+++ b/simulation/halsim_gui/src/main/native/include/HALProvider.h
@@ -15,9 +15,15 @@
 
 namespace halsimgui {
 
-class HALProvider : public glass::Provider<> {
+class HALProvider : private glass::Provider<> {
  public:
-  explicit HALProvider(std::string_view iniName) : Provider{iniName} {}
+  explicit HALProvider(glass::Storage& storage);
+
+  using Provider::GlobalInit;
+  using Provider::Register;
+  using Provider::RegisterModel;
+  using Provider::RegisterView;
+  using Provider::ShowDefault;
 
   void DisplayMenu() override;
 
@@ -38,8 +44,6 @@
   static bool AreOutputsEnabled() { return !AreOutputsDisabled(); }
 
  private:
-  void Update() override;
-
   void Show(ViewEntry* entry, glass::Window* window) override;
 };
 
diff --git a/simulation/halsim_gui/src/main/native/include/HALSimGui.h b/simulation/halsim_gui/src/main/native/include/HALSimGui.h
index 33ff242..68597e5 100644
--- a/simulation/halsim_gui/src/main/native/include/HALSimGui.h
+++ b/simulation/halsim_gui/src/main/native/include/HALSimGui.h
@@ -8,6 +8,9 @@
 #include <glass/WindowManager.h>
 #include <glass/networktables/NetworkTablesProvider.h>
 
+#include <functional>
+#include <memory>
+
 #include "HALProvider.h"
 
 namespace halsimgui {
@@ -17,10 +20,15 @@
   static void GlobalInit();
 
   static glass::MainMenuBar mainMenu;
-  static glass::WindowManager manager;
+  static std::unique_ptr<glass::WindowManager> manager;
 
-  static HALProvider halProvider;
-  static glass::NetworkTablesProvider ntProvider;
+  static std::unique_ptr<HALProvider> halProvider;
+  static std::unique_ptr<glass::NetworkTablesProvider> ntProvider;
 };
 
+void AddGuiInit(std::function<void()> initialize);
+void AddGuiLateExecute(std::function<void()> execute);
+void AddGuiEarlyExecute(std::function<void()> execute);
+void GuiExit();
+
 }  // namespace halsimgui
diff --git a/simulation/halsim_gui/src/main/native/include/HALSimGuiExt.h b/simulation/halsim_gui/src/main/native/include/HALSimGuiExt.h
new file mode 100644
index 0000000..e60976e
--- /dev/null
+++ b/simulation/halsim_gui/src/main/native/include/HALSimGuiExt.h
@@ -0,0 +1,26 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#pragma once
+
+#include <functional>
+
+namespace halsimgui {
+
+// These functions can be used to hook into the GUI, and can be accessed
+// via HAL_RegisterExtensionListener
+
+#define HALSIMGUI_EXT_ADDGUIINIT "halsimgui::AddGuiInit"
+using AddGuiInitFn = void (*)(std::function<void()> initialize);
+
+#define HALSIMGUI_EXT_ADDGUILATEEXECUTE "halsimgui::AddGuiLateExecute"
+using AddGuiLateExecuteFn = void (*)(std::function<void()> execute);
+
+#define HALSIMGUI_EXT_ADDGUIEARLYEXECUTE "halsimgui::AddGuiEarlyExecute"
+using AddGuiEarlyExecuteFn = void (*)(std::function<void()> execute);
+
+#define HALSIMGUI_EXT_GUIEXIT "halsimgui::GuiExit"
+using GuiExitFn = void (*)();
+
+}  // namespace halsimgui
diff --git a/simulation/halsim_ws_server/src/dev/native/cpp/main.cpp b/simulation/halsim_ws_server/src/dev/native/cpp/main.cpp
index d215df6..ad76c6b 100644
--- a/simulation/halsim_ws_server/src/dev/native/cpp/main.cpp
+++ b/simulation/halsim_ws_server/src/dev/native/cpp/main.cpp
@@ -3,7 +3,6 @@
 // the WPILib BSD license file in the root directory of this project.
 
 #include <cstdio>
-#include <iostream>
 #include <thread>
 
 #include <fmt/format.h>