1 // Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
3 // This file is part of SCSI2SD.
5 // SCSI2SD is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // SCSI2SD is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with SCSI2SD. If not, see <http://www.gnu.org/licenses/>.
19 // For compilers that support precompilation, includes "wx/wx.h".
20 #include <wx/wxprec.h>
26 #include <wx/filedlg.h>
27 #include <wx/filefn.h>
28 #include <wx/filename.h>
30 #include <wx/notebook.h>
31 #include <wx/progdlg.h>
33 #include <wx/wfstream.h>
34 #include <wx/windowptr.h>
35 #include <wx/stdpaths.h>
36 #include <wx/stream.h>
37 #include <wx/thread.h>
38 #include <wx/txtstrm.h>
42 #include "ConfigUtil.hh"
43 #include "BoardPanel.hh"
44 #include "TargetPanel.hh"
45 #include "SCSI2SD_HID.hh"
48 #include "terminalwx.h"
56 #if __cplusplus >= 201103L
59 using std::shared_ptr;
63 using std::tr1::shared_ptr;
66 using namespace SCSI2SD;
71 void setProgressDialog(
72 const wxWindowPtr<wxGenericProgressDialog>& dlg,
75 myProgressDialog = dlg;
80 void clearProgressDialog()
82 myProgressDialog->Show(false);
83 myProgressDialog.reset();
86 void update(unsigned char arrayId, unsigned short rowNum)
88 if (!myProgressDialog) return;
93 ss << "Writing flash array " <<
94 static_cast<int>(arrayId) << " row " <<
95 static_cast<int>(rowNum);
96 wxLogMessage("%s", ss.str());
97 myProgressDialog->Update(myNumRows, ss.str());
101 wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
105 static ProgressWrapper TheProgressWrapper;
108 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
110 TheProgressWrapper.update(arrayId, rowNum);
120 TimerLock(wxTimer* timer) :
122 myInterval(myTimer->GetInterval())
129 if (myTimer && myInterval > 0)
131 myTimer->Start(myInterval);
139 class AppFrame : public wxFrame
143 wxFrame(NULL, wxID_ANY, "scsi2sd-util6", wxPoint(50, 50), wxSize(600, 700)),
144 myInitialConfig(false),
147 myConsoleProcess(NULL)
149 wxMenu *menuFile = new wxMenu();
152 _("&Save to file..."),
153 _("Save settings to local file."));
157 _("Load settings from local file."));
158 menuFile->AppendSeparator();
162 _("Load default configuration options."));
164 menuFile->AppendSeparator();
165 myLoadButton = menuFile->Append(
167 _("Load from device"),
168 _("Load configuration from hardware device"));
169 mySaveButton = menuFile->Append(
172 _("Save configuration to hardware device"));
174 menuFile->AppendSeparator();
177 _("&Upgrade Firmware..."),
178 _("Upgrade or inspect device firmware version."));
179 menuFile->Append(wxID_EXIT);
181 wxMenu *menuWindow= new wxMenu();
185 _("Show debug log window"));
187 wxMenu *menuDebug = new wxMenu();
188 mySCSILogChk = menuDebug->AppendCheckItem(
191 _("Log SCSI commands"));
193 mySelfTestChk = menuDebug->AppendCheckItem(
195 _("SCSI Standalone Self-Test"),
196 _("SCSI Standalone Self-Test"));
198 wxMenu *menuHelp = new wxMenu();
199 menuHelp->Append(wxID_ABOUT);
201 wxMenuBar *menuBar = new wxMenuBar();
202 menuBar->Append( menuFile, _("&File") );
203 menuBar->Append( menuDebug, _("&Debug") );
204 menuBar->Append( menuWindow, _("&Window") );
205 menuBar->Append( menuHelp, _("&Help") );
206 SetMenuBar( menuBar );
211 wxPanel* cfgPanel = new wxPanel(this);
212 wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
213 cfgPanel->SetSizer(fgs);
215 // Empty space below menu bar.
216 fgs->Add(5, 5, wxALL);
218 wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
219 myBoardPanel = new BoardPanel(tabs, ConfigUtil::DefaultBoardConfig());
220 tabs->AddPage(myBoardPanel, _("General Settings"));
221 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
223 TargetPanel* target =
224 new TargetPanel(tabs, ConfigUtil::Default(i));
225 myTargets.push_back(target);
226 std::stringstream ss;
227 ss << "Device " << (i + 1);
228 tabs->AddPage(target, ss.str());
238 Fit(); // Needed to reduce window size on Windows
240 FitInside(); // Needed on Linux to prevent status bar overlap
243 myLogWindow = new wxLogWindow(this, _("scsi2sd-util6 debug log"), true);
244 myLogWindow->PassMessages(false); // Prevent messagebox popups
246 myTimer = new wxTimer(this, ID_Timer);
247 myTimer->Start(64); //ms, suitable for scsi debug logging
252 wxLogWindow* myLogWindow;
253 BoardPanel* myBoardPanel;
254 std::vector<TargetPanel*> myTargets;
255 wxMenuItem* mySCSILogChk;
256 wxMenuItem* mySelfTestChk;
257 wxMenuItem* myLoadButton;
258 wxMenuItem* mySaveButton;
260 shared_ptr<HID> myHID;
261 bool myInitialConfig;
263 uint8_t myTickCounter;
265 time_t myLastPollTime;
267 wxWindowPtr<TerminalWx> myConsoleTerm;
268 shared_ptr<wxProcess> myConsoleProcess;
269 wxInputStream* myConsoleStdout;
270 wxInputStream* myConsoleStderr;
273 void mmLogStatus(const std::string& msg)
275 // We set PassMessages to false on our log window to prevent popups, but
276 // this also prevents wxLogStatus from updating the status bar.
278 wxLogMessage(this, "%s", msg.c_str());
281 void onConfigChanged(wxCommandEvent& event)
290 // Check for duplicate SCSI IDs
291 std::set<uint8_t> enabledID;
293 // Check for overlapping SD sectors.
294 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
296 bool isTargetEnabled = false; // Need at least one enabled
297 uint32_t autoStartSector = 0;
298 for (size_t i = 0; i < myTargets.size(); ++i)
300 myTargets[i]->setAutoStartSector(autoStartSector);
301 valid = myTargets[i]->evaluate() && valid;
303 if (myTargets[i]->isEnabled())
305 isTargetEnabled = true;
306 uint8_t scsiID = myTargets[i]->getSCSIId();
307 if (enabledID.find(scsiID) != enabledID.end())
309 myTargets[i]->setDuplicateID(true);
314 enabledID.insert(scsiID);
315 myTargets[i]->setDuplicateID(false);
318 auto sdSectorRange = myTargets[i]->getSDSectorRange();
319 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
321 if (sdSectorRange.first < it->second &&
322 sdSectorRange.second > it->first)
325 myTargets[i]->setSDSectorOverlap(true);
329 myTargets[i]->setSDSectorOverlap(false);
332 sdSectors.push_back(sdSectorRange);
333 autoStartSector = sdSectorRange.second;
337 myTargets[i]->setDuplicateID(false);
338 myTargets[i]->setSDSectorOverlap(false);
342 valid = valid && isTargetEnabled; // Need at least one.
344 mySaveButton->Enable(valid && myHID);
346 myLoadButton->Enable(static_cast<bool>(myHID));
352 ID_ConfigDefaults = wxID_HIGHEST + 1,
366 void OnID_ConfigDefaults(wxCommandEvent& event)
368 myBoardPanel->setConfig(ConfigUtil::DefaultBoardConfig());
369 for (size_t i = 0; i < myTargets.size(); ++i)
371 myTargets[i]->setConfig(ConfigUtil::Default(i));
375 void OnID_SaveFile(wxCommandEvent& event)
377 TimerLock lock(myTimer);
383 "Save config settings",
386 "XML files (*.xml)|*.xml",
387 wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
388 if (dlg.ShowModal() == wxID_CANCEL) return;
390 wxFileOutputStream file(dlg.GetPath());
393 wxLogError("Cannot save settings to file '%s'.", dlg.GetPath());
397 wxTextOutputStream s(file);
401 s << ConfigUtil::toXML(myBoardPanel->getConfig());
402 for (size_t i = 0; i < myTargets.size(); ++i)
404 s << ConfigUtil::toXML(myTargets[i]->getConfig());
410 void OnID_OpenFile(wxCommandEvent& event)
412 TimerLock lock(myTimer);
416 "Load config settings",
419 "XML files (*.xml)|*.xml",
420 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
421 if (dlg.ShowModal() == wxID_CANCEL) return;
425 std::pair<S2S_BoardCfg, std::vector<S2S_TargetCfg>> configs(
426 ConfigUtil::fromXML(std::string(dlg.GetPath())));
428 myBoardPanel->setConfig(configs.first);
431 for (i = 0; i < configs.second.size() && i < myTargets.size(); ++i)
433 myTargets[i]->setConfig(configs.second[i]);
436 for (; i < myTargets.size(); ++i)
438 myTargets[i]->setConfig(ConfigUtil::Default(i));
441 catch (std::exception& e)
444 "Cannot load settings from file '%s'.\n%s",
451 wxOK | wxICON_ERROR);
455 void OnID_Firmware(wxCommandEvent& event)
457 TimerLock lock(myTimer);
461 void OnID_LogWindow(wxCommandEvent& event)
466 void doFirmwareUpdate()
470 "Load firmware file",
473 "SCSI2SD Firmware files (*.dfu)|*.dfu",
474 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
475 if (dlg.ShowModal() == wxID_CANCEL) return;
477 std::string filename(dlg.GetPath());
478 wxLogMessage("Attempting firmware update from file %s", filename);
480 wxWindowPtr<wxGenericProgressDialog> progress(
481 new wxGenericProgressDialog(
482 "Searching for bootloader",
483 "Searching for bootloader",
486 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
488 mmLogStatus("Searching for bootloader");
489 bool versionChecked = false;
494 if (!myHID) myHID.reset(HID::Open());
497 if (!myHID->isCorrectFirmware(filename))
500 "Firmware does not match device hardware",
502 wxOK | wxICON_ERROR);
505 versionChecked = true;
507 mmLogStatus("Resetting SCSI2SD into bootloader");
509 myHID->enterBootloader();
514 if (myDfu.hasDevice() && !versionChecked)
516 mmLogStatus("STM DFU Bootloader found, checking compatibility");
518 if (!checkVersionMarker(filename))
521 "Firmware does not match device hardware",
523 wxOK | wxICON_ERROR);
526 versionChecked = true;
529 if (myDfu.hasDevice())
531 mmLogStatus("STM DFU Bootloader found");
533 doDFUUpdate(filename);
537 catch (std::exception& e)
539 mmLogStatus(e.what());
543 if (!progress->Pulse())
545 return; // user cancelled.
550 bool checkVersionMarker(const std::string& firmware)
552 std::stringstream ss;
556 if (wxExecute("which dfu-util", wxEXEC_SYNC) == 0)
560 wxFileName exePath(wxStandardPaths::Get().GetExecutablePath());
561 ss << '"' << exePath.GetPathWithSep() << "dfu-util\" ";
565 std::string tmpFile =
566 wxFileName::CreateTempFileName(
567 _("SCSI2SD_MARKER"), static_cast<wxFile*>(NULL));
568 wxRemoveFile(tmpFile); // dfu-util won't overwrite.
570 ss << "--alt 2 -s 0x1FFF7800:4 -U \"" << tmpFile << "\"";
572 wxLogMessage("Running: %s", ss.str());
574 std::string cmd = ss.str();
575 long result = wxExecute(
581 wxLogMessage("OTP Version check failed.");
585 // Ok, we now have a file with 8 bytes in it.
586 wxFile file(tmpFile);
587 if (file.Length() != 4)
589 wxLogMessage("OTP Version check file isn't 4 bytes.");
594 if (file.Read(data, sizeof(data)) != sizeof(data))
597 "Couldn't read file",
598 "Couldn't read file",
599 wxOK | wxICON_ERROR);
602 wxRemoveFile(tmpFile);
605 (((uint32_t)(data[0]))) |
606 (((uint32_t)(data[1])) << 8) |
607 (((uint32_t)(data[2])) << 16) |
608 (((uint32_t)(data[3])) << 24);
609 if (value == 0xFFFFFFFF)
612 wxLogMessage("OTP Hardware version not set. Ignoring.");
615 else if (value == 0x06002020)
617 wxLogMessage("Found V6 2020 hardware marker");
618 return firmware.rfind("firmware.V6.2020.dfu") != std::string::npos;
620 else if (value == 0x06002019)
622 wxLogMessage("Found V6 revF hardware marker");
623 return firmware.rfind("firmware.V6.revF.dfu") != std::string::npos ||
624 firmware.rfind("firmware.dfu") != std::string::npos;
628 wxLogMessage("Found unknown hardware marker: %u", value);
629 return false; // Some unknown version.
634 void doDFUUpdate(const std::string& filename)
636 if (filename.find(".dfu") == std::string::npos)
640 "SCSI2SD V6 requires a .dfu file",
641 wxOK | wxICON_ERROR);
645 std::stringstream ss;
647 ss << "dfu-util --download \""
648 << filename.c_str() << "\" --alt 0 --reset";
650 if (wxExecute("which dfu-util", wxEXEC_SYNC) == 0)
654 wxFileName exePath(wxStandardPaths::Get().GetExecutablePath());
655 ss << '"' << exePath.GetPathWithSep() << "dfu-util\" ";
657 ss << "--download \"" << filename.c_str() << "\" --alt 0 --reset";
660 wxLogMessage("Running: %s", ss.str());
662 myConsoleProcess.reset(new wxProcess(this));
663 myConsoleProcess->Redirect();
664 std::string cmd = ss.str();
665 long result = wxExecute(
668 myConsoleProcess.get()
674 "Firmware update failed (dfu-util not found ?) Command = " + cmd,
675 wxOK | wxICON_ERROR);
676 mmLogStatus("Firmware update failed");
677 myConsoleProcess.reset();
679 myConsoleStdout = myConsoleProcess->GetInputStream();
680 myConsoleStderr = myConsoleProcess->GetErrorStream();
681 wxFrame* frame(new wxFrame(this, wxID_ANY, "dfu-util"));
682 myConsoleTerm.reset(new TerminalWx(frame, wxID_ANY, wxDefaultPosition));
688 void redirectDfuOutput()
690 if (myConsoleProcess)
692 std::stringstream ss;
693 while (myConsoleStderr && !myConsoleStderr->Eof() && myConsoleStderr->CanRead())
695 int c = myConsoleStderr->GetC();
705 while (myConsoleStdout && !myConsoleStdout->Eof() && myConsoleStdout->CanRead())
707 int c = myConsoleStdout->GetC();
717 myConsoleTerm->DisplayCharsUnsafe(ss.str());
721 void doFinishDfu(wxProcessEvent& event)
725 if (event.GetExitCode() == 0)
729 "Firmware update complete. Please reconnect USB cable.",
730 wxOK | wxICON_ERROR);
731 mmLogStatus("Firmware update succeeded");
735 "Firmware update failed.",
736 wxOK | wxICON_ERROR);
737 mmLogStatus("Firmware update failed");
740 myConsoleStdout = myConsoleStderr = NULL;
741 myConsoleProcess.reset();
742 myConsoleTerm->GetParent()->Close();
743 myConsoleTerm->Close();
744 myConsoleTerm.reset();
748 void dumpSCSICommand(std::vector<uint8_t> buf)
750 std::stringstream msg;
752 for (size_t i = 0; i < 32 && i < buf.size(); ++i)
754 msg << std::setfill('0') << std::setw(2) <<
755 static_cast<int>(buf[i]) << ' ';
757 wxLogMessage(this, msg.str().c_str());
762 if (!mySCSILogChk->IsChecked() ||
769 std::vector<uint8_t> info;
770 if (myHID->readSCSIDebugInfo(info))
772 dumpSCSICommand(info);
775 catch (std::exception& e)
777 wxLogWarning(this, e.what());
782 void OnID_Timer(wxTimerEvent& event)
787 time_t now = time(NULL);
788 if (now == myLastPollTime) return;
789 myLastPollTime = now;
791 // Check if we are connected to the HID device.
794 if (myHID && !myHID->ping())
796 // Verify the USB HID connection is valid
802 myHID.reset(HID::Open());
805 std::stringstream msg;
806 msg << "SCSI2SD Ready, firmware version " <<
807 myHID->getFirmwareVersionStr();
808 mmLogStatus(msg.str());
810 std::stringstream devInfo;
811 devInfo << "Hardware version: " <<
812 myHID->getHardwareVersion() << std::endl <<
814 myHID->getSerialNumber();
815 wxLogMessage(this, "%s", devInfo.str());
817 std::vector<uint8_t> csd(myHID->getSD_CSD());
818 std::vector<uint8_t> cid(myHID->getSD_CID());
819 std::stringstream sdinfo;
820 sdinfo << "SD Capacity (512-byte sectors): " <<
821 myHID->getSDCapacity() << std::endl;
823 sdinfo << "SD CSD Register: ";
824 for (size_t i = 0; i < csd.size(); ++i)
827 std::hex << std::setfill('0') << std::setw(2) <<
828 static_cast<int>(csd[i]);
831 sdinfo << "SD CID Register: ";
832 for (size_t i = 0; i < cid.size(); ++i)
835 std::hex << std::setfill('0') << std::setw(2) <<
836 static_cast<int>(cid[i]);
839 wxLogMessage(this, "%s", sdinfo.str());
841 if (mySelfTestChk->IsChecked())
843 std::stringstream scsiInfo;
845 scsiInfo << "SCSI Self-Test: ";
846 if (myHID->scsiSelfTest(errcode))
848 scsiInfo << "Passed";
852 scsiInfo << "FAIL (" << errcode << ")";
854 wxLogMessage(this, "%s", scsiInfo.str());
857 if (!myInitialConfig)
859 /* This doesn't work properly, and causes crashes.
860 wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
861 GetEventHandler()->AddPendingEvent(loadEvent);
868 char ticks[] = {'/', '-', '\\', '|'};
869 std::stringstream ss;
870 ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
872 SetStatusText(ss.str());
876 catch (std::runtime_error& e)
878 std::cerr << e.what() << std::endl;
879 mmLogStatus(e.what());
885 void doLoad(wxCommandEvent& event)
887 TimerLock lock(myTimer);
890 mmLogStatus("Loading configuration");
892 wxWindowPtr<wxGenericProgressDialog> progress(
893 new wxGenericProgressDialog(
894 "Load config settings",
895 "Loading config settings",
898 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
901 int currentProgress = 0;
902 int totalProgress = 2;
904 std::vector<uint8_t> cfgData(S2S_CFG_SIZE);
905 uint32_t sector = myHID->getSDCapacity() - 2;
906 for (size_t i = 0; i < 2; ++i)
908 std::stringstream ss;
909 ss << "Reading sector " << sector;
910 mmLogStatus(ss.str());
911 currentProgress += 1;
912 if (currentProgress == totalProgress)
914 ss.str("Load Complete.");
915 mmLogStatus("Load Complete.");
918 if (!progress->Update(
919 (100 * currentProgress) / totalProgress,
927 std::vector<uint8_t> sdData;
931 myHID->readSector(sector++, sdData);
933 catch (std::runtime_error& e)
935 mmLogStatus(e.what());
945 myBoardPanel->setConfig(ConfigUtil::boardConfigFromBytes(&cfgData[0]));
946 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
948 myTargets[i]->setConfig(
949 ConfigUtil::fromBytes(
950 &cfgData[sizeof(S2S_BoardCfg) + i * sizeof(S2S_TargetCfg)]
955 myInitialConfig = true;
959 mmLogStatus("Load failed");
960 progress->Update(100, "Load failed");
964 mmLogStatus("Load Aborted");
971 void doSave(wxCommandEvent& event)
973 TimerLock lock(myTimer);
976 mmLogStatus("Saving configuration");
978 wxWindowPtr<wxGenericProgressDialog> progress(
979 new wxGenericProgressDialog(
980 "Save config settings",
981 "Saving config settings",
984 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
988 int currentProgress = 0;
989 int totalProgress = 2;
991 std::vector<uint8_t> cfgData(
992 ConfigUtil::boardConfigToBytes(myBoardPanel->getConfig())
994 for (int i = 0; i < S2S_MAX_TARGETS; ++i)
996 std::vector<uint8_t> raw(
997 ConfigUtil::toBytes(myTargets[i]->getConfig())
999 cfgData.insert(cfgData.end(), raw.begin(), raw.end());
1002 uint32_t sector = myHID->getSDCapacity() - 2;
1004 for (size_t i = 0; i < 2; ++i)
1006 std::stringstream ss;
1007 ss << "Writing SD sector " << sector;
1008 mmLogStatus(ss.str());
1009 currentProgress += 1;
1011 if (currentProgress == totalProgress)
1013 ss.str("Save Complete.");
1014 mmLogStatus("Save Complete.");
1016 if (!progress->Update(
1017 (100 * currentProgress) / totalProgress,
1027 std::vector<uint8_t> buf;
1028 buf.insert(buf.end(), &cfgData[i * 512], &cfgData[(i+1) * 512]);
1029 myHID->writeSector(sector++, buf);
1031 catch (std::runtime_error& e)
1033 mmLogStatus(e.what());
1043 mmLogStatus("Save failed");
1044 progress->Update(100, "Save failed");
1048 mmLogStatus("Save Aborted");
1055 // Note: Don't confuse this with the wxApp::OnExit virtual method
1056 void OnExitEvt(wxCommandEvent& event);
1058 void OnCloseEvt(wxCloseEvent& event);
1060 void OnAbout(wxCommandEvent& event)
1063 "SCSI2SD (scsi2sd-util6)\n"
1064 "Copyright (C) 2014-2017 Michael McMaster <michael@codesrc.com>\n"
1066 "This program is free software: you can redistribute it and/or modify\n"
1067 "it under the terms of the GNU General Public License as published by\n"
1068 "the Free Software Foundation, either version 3 of the License, or\n"
1069 "(at your option) any later version.\n"
1071 "This program is distributed in the hope that it will be useful,\n"
1072 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
1073 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
1074 "GNU General Public License for more details.\n"
1076 "You should have received a copy of the GNU General Public License\n"
1077 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
1079 "About scsi2sd-util6", wxOK | wxICON_INFORMATION );
1082 wxDECLARE_EVENT_TABLE();
1085 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
1086 EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
1087 EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
1088 EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
1089 EVT_MENU(AppFrame::ID_SaveFile, AppFrame::OnID_SaveFile)
1090 EVT_MENU(AppFrame::ID_OpenFile, AppFrame::OnID_OpenFile)
1091 EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)
1092 EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
1094 EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
1096 EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
1098 EVT_MENU(ID_BtnSave, AppFrame::doSave)
1099 EVT_MENU(ID_BtnLoad, AppFrame::doLoad)
1101 EVT_CLOSE(AppFrame::OnCloseEvt)
1103 EVT_END_PROCESS(wxID_ANY, AppFrame::doFinishDfu)
1109 class App : public wxApp
1112 virtual bool OnInit()
1114 AppFrame* frame = new AppFrame();
1116 SetTopWindow(frame);
1123 wxIMPLEMENT_APP(App);
1126 AppFrame::OnExitEvt(wxCommandEvent& event)
1128 wxGetApp().ExitMainLoop();
1132 AppFrame::OnCloseEvt(wxCloseEvent& event)
1134 wxGetApp().ExitMainLoop();