cc26e8ba05fc78dbb624cc07c07608895d8e04d3
[SCSI2SD-V6.git] / software / scsi2sd-util / scsi2sd-util.cc
1 // Copyright (C) 2014 Michael McMaster <michael@codesrc.com>
2 //
3 // This file is part of SCSI2SD.
4 //
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.
9 //
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.
14 //
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/>.
17
18
19 // For compilers that support precompilation, includes "wx/wx.h".
20 #include <wx/wxprec.h>
21 #ifndef WX_PRECOMP
22 #include <wx/wx.h>
23 #endif
24
25 #include <wx/filedlg.h>
26 #include <wx/filefn.h>
27 #include <wx/filename.h>
28 #include <wx/log.h>
29 #include <wx/notebook.h>
30 #include <wx/progdlg.h>
31 #include <wx/utils.h>
32 #include <wx/windowptr.h>
33 #include <wx/thread.h>
34
35 #include <zipper.hh>
36
37 #include "ConfigUtil.hh"
38 #include "TargetPanel.hh"
39 #include "SCSI2SD_Bootloader.hh"
40 #include "SCSI2SD_HID.hh"
41 #include "Firmware.hh"
42
43 #include <algorithm>
44 #include <iomanip>
45 #include <vector>
46 #include <set>
47 #include <sstream>
48
49 #if __cplusplus >= 201103L
50 #include <cstdint>
51 #include <memory>
52 using std::shared_ptr;
53 #else
54 #include <stdint.h>
55 #include <tr1/memory>
56 using std::tr1::shared_ptr;
57 #endif
58
59 #define MIN_FIRMWARE_VERSION 0x0400
60
61 using namespace SCSI2SD;
62
63 class ProgressWrapper
64 {
65 public:
66 void setProgressDialog(
67 const wxWindowPtr<wxGenericProgressDialog>& dlg,
68 size_t maxRows)
69 {
70 myProgressDialog = dlg;
71 myMaxRows = maxRows;
72 myNumRows = 0;
73 }
74
75 void clearProgressDialog()
76 {
77 myProgressDialog->Show(false);
78 myProgressDialog.reset();
79 }
80
81 void update(unsigned char arrayId, unsigned short rowNum)
82 {
83 if (!myProgressDialog) return;
84
85 myNumRows++;
86
87 std::stringstream ss;
88 ss << "Writing flash array " <<
89 static_cast<int>(arrayId) << " row " <<
90 static_cast<int>(rowNum);
91 wxLogMessage("%s", ss.str());
92 myProgressDialog->Update(myNumRows, ss.str());
93 }
94
95 private:
96 wxWindowPtr<wxGenericProgressDialog> myProgressDialog;
97 size_t myMaxRows;
98 size_t myNumRows;
99 };
100 static ProgressWrapper TheProgressWrapper;
101
102 extern "C"
103 void ProgressUpdate(unsigned char arrayId, unsigned short rowNum)
104 {
105 TheProgressWrapper.update(arrayId, rowNum);
106 }
107
108 namespace
109 {
110
111 class TimerLock
112 {
113 public:
114 TimerLock(wxTimer* timer) :
115 myTimer(timer),
116 myInterval(myTimer->GetInterval())
117 {
118 myTimer->Stop();
119 };
120
121 virtual ~TimerLock()
122 {
123 if (myTimer && myInterval > 0)
124 {
125 myTimer->Start(myInterval);
126 }
127 }
128 private:
129 wxTimer* myTimer;
130 int myInterval;
131 };
132
133 class AppFrame : public wxFrame
134 {
135 public:
136 AppFrame() :
137 wxFrame(NULL, wxID_ANY, "scsi2sd-util", wxPoint(50, 50), wxSize(600, 650)),
138 myInitialConfig(false),
139 myTickCounter(0),
140 myLastPollTime(0)
141 {
142 wxMenu *menuFile = new wxMenu();
143 menuFile->Append(
144 ID_ConfigDefaults,
145 "Load &Defaults",
146 "Load default configuration options.");
147 menuFile->Append(
148 ID_Firmware,
149 "&Upgrade Firmware...",
150 "Upgrade or inspect device firmware version.");
151 menuFile->AppendSeparator();
152 menuFile->Append(wxID_EXIT);
153
154 wxMenu *menuWindow= new wxMenu();
155 menuWindow->Append(
156 ID_LogWindow,
157 "Show &Log",
158 "Show debug log window");
159
160 wxMenu *menuDebug = new wxMenu();
161 mySCSILogChk = menuDebug->AppendCheckItem(
162 ID_SCSILog,
163 "Log SCSI data",
164 "Log SCSI commands");
165
166 wxMenu *menuHelp = new wxMenu();
167 menuHelp->Append(wxID_ABOUT);
168
169 wxMenuBar *menuBar = new wxMenuBar();
170 menuBar->Append( menuFile, "&File" );
171 menuBar->Append( menuDebug, "&Debug" );
172 menuBar->Append( menuWindow, "&Window" );
173 menuBar->Append( menuHelp, "&Help" );
174 SetMenuBar( menuBar );
175
176 CreateStatusBar();
177
178 {
179 wxPanel* cfgPanel = new wxPanel(this);
180 wxFlexGridSizer *fgs = new wxFlexGridSizer(3, 1, 15, 15);
181 cfgPanel->SetSizer(fgs);
182
183 // Empty space below menu bar.
184 fgs->Add(5, 5, wxALL);
185
186 wxNotebook* tabs = new wxNotebook(cfgPanel, ID_Notebook);
187
188 for (int i = 0; i < MAX_SCSI_TARGETS; ++i)
189 {
190 TargetPanel* target =
191 new TargetPanel(tabs, ConfigUtil::Default(i));
192 myTargets.push_back(target);
193 std::stringstream ss;
194 ss << "Device " << (i + 1);
195 tabs->AddPage(target, ss.str());
196 target->Fit();
197 }
198 tabs->Fit();
199 fgs->Add(tabs);
200
201
202 wxPanel* btnPanel = new wxPanel(cfgPanel);
203 wxFlexGridSizer *btnFgs = new wxFlexGridSizer(1, 2, 5, 5);
204 btnPanel->SetSizer(btnFgs);
205 myLoadButton =
206 new wxButton(btnPanel, ID_BtnLoad, wxT("Load from device"));
207 btnFgs->Add(myLoadButton);
208 mySaveButton =
209 new wxButton(btnPanel, ID_BtnSave, wxT("Save to device"));
210 btnFgs->Add(mySaveButton);
211 fgs->Add(btnPanel);
212
213 btnPanel->Fit();
214 cfgPanel->Fit();
215 }
216 //Fit(); // Needed to reduce window size on Windows
217 FitInside(); // Needed on Linux to prevent status bar overlap
218
219 myLogWindow = new wxLogWindow(this, wxT("scsi2sd-util debug log"), true);
220 myLogWindow->PassMessages(false); // Prevent messagebox popups
221
222 myTimer = new wxTimer(this, ID_Timer);
223 myTimer->Start(16); //ms, suitable for scsi debug logging
224 }
225
226 private:
227 wxLogWindow* myLogWindow;
228 std::vector<TargetPanel*> myTargets;
229 wxButton* myLoadButton;
230 wxButton* mySaveButton;
231 wxMenuItem* mySCSILogChk;
232 wxTimer* myTimer;
233 shared_ptr<HID> myHID;
234 shared_ptr<Bootloader> myBootloader;
235 bool myInitialConfig;
236
237 uint8_t myTickCounter;
238
239 time_t myLastPollTime;
240
241 void mmLogStatus(const std::string& msg)
242 {
243 // We set PassMessages to false on our log window to prevent popups, but
244 // this also prevents wxLogStatus from updating the status bar.
245 SetStatusText(msg);
246 wxLogMessage(this, "%s", msg.c_str());
247 }
248
249 void onConfigChanged(wxCommandEvent& event)
250 {
251 evaluate();
252 }
253
254 void evaluate()
255 {
256 bool valid = true;
257
258 // Check for duplicate SCSI IDs
259 std::set<uint8_t> enabledID;
260
261 // Check for overlapping SD sectors.
262 std::vector<std::pair<uint32_t, uint64_t> > sdSectors;
263
264 bool isTargetEnabled = false; // Need at least one enabled
265 uint32_t autoStartSector = 0;
266 for (size_t i = 0; i < myTargets.size(); ++i)
267 {
268 myTargets[i]->setAutoStartSector(autoStartSector);
269 valid = myTargets[i]->evaluate() && valid;
270
271 if (myTargets[i]->isEnabled())
272 {
273 isTargetEnabled = true;
274 uint8_t scsiID = myTargets[i]->getSCSIId();
275 if (enabledID.find(scsiID) != enabledID.end())
276 {
277 myTargets[i]->setDuplicateID(true);
278 valid = false;
279 }
280 else
281 {
282 enabledID.insert(scsiID);
283 myTargets[i]->setDuplicateID(false);
284 }
285
286 auto sdSectorRange = myTargets[i]->getSDSectorRange();
287 for (auto it(sdSectors.begin()); it != sdSectors.end(); ++it)
288 {
289 if (sdSectorRange.first < it->second &&
290 sdSectorRange.second > it->first)
291 {
292 valid = false;
293 myTargets[i]->setSDSectorOverlap(true);
294 }
295 else
296 {
297 myTargets[i]->setSDSectorOverlap(false);
298 }
299 }
300 sdSectors.push_back(sdSectorRange);
301 autoStartSector = sdSectorRange.second + 1;
302 }
303 else
304 {
305 myTargets[i]->setDuplicateID(false);
306 myTargets[i]->setSDSectorOverlap(false);
307 }
308 }
309
310 valid = valid && isTargetEnabled; // Need at least one.
311
312 mySaveButton->Enable(
313 valid &&
314 myHID &&
315 (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
316
317 myLoadButton->Enable(
318 myHID &&
319 (myHID->getFirmwareVersion() >= MIN_FIRMWARE_VERSION));
320 }
321
322
323 enum
324 {
325 ID_ConfigDefaults = wxID_HIGHEST + 1,
326 ID_Firmware,
327 ID_Timer,
328 ID_Notebook,
329 ID_BtnLoad,
330 ID_BtnSave,
331 ID_LogWindow,
332 ID_SCSILog
333 };
334
335 void OnID_ConfigDefaults(wxCommandEvent& event)
336 {
337 for (size_t i = 0; i < myTargets.size(); ++i)
338 {
339 myTargets[i]->setConfig(ConfigUtil::Default(i));
340 }
341 }
342
343 void OnID_Firmware(wxCommandEvent& event)
344 {
345 TimerLock lock(myTimer);
346 doFirmwareUpdate();
347 }
348
349 void OnID_LogWindow(wxCommandEvent& event)
350 {
351 myLogWindow->Show();
352 }
353
354 void doFirmwareUpdate()
355 {
356 wxFileDialog dlg(
357 this,
358 "Load firmware file",
359 "",
360 "",
361 "SCSI2SD Firmware files (*.scsi2sd;*.cyacd)|*.cyacd;*.scsi2sd",
362 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
363 if (dlg.ShowModal() == wxID_CANCEL) return;
364
365 std::string filename(dlg.GetPath());
366
367 wxWindowPtr<wxGenericProgressDialog> progress(
368 new wxGenericProgressDialog(
369 "Searching for bootloader",
370 "Searching for bootloader",
371 100,
372 this,
373 wxPD_AUTO_HIDE | wxPD_CAN_ABORT)
374 );
375 mmLogStatus("Searching for bootloader");
376 while (true)
377 {
378 try
379 {
380 if (!myHID) myHID.reset(HID::Open());
381 if (myHID)
382 {
383 mmLogStatus("Resetting SCSI2SD into bootloader");
384
385 myHID->enterBootloader();
386 myHID.reset();
387 }
388
389
390 if (!myBootloader)
391 {
392 myBootloader.reset(Bootloader::Open());
393 if (myBootloader)
394 {
395 mmLogStatus("Bootloader found");
396 break;
397 }
398 }
399
400 else if (myBootloader)
401 {
402 // Verify the USB HID connection is valid
403 if (!myBootloader->ping())
404 {
405 mmLogStatus("Bootloader ping failed");
406 myBootloader.reset();
407 }
408 else
409 {
410 mmLogStatus("Bootloader found");
411 break;
412 }
413 }
414 }
415 catch (std::exception& e)
416 {
417 mmLogStatus(e.what());
418 myHID.reset();
419 myBootloader.reset();
420 }
421 wxMilliSleep(100);
422 if (!progress->Pulse())
423 {
424 return; // user cancelled.
425 }
426 }
427
428 int totalFlashRows = 0;
429 std::string tmpFile;
430 try
431 {
432 zipper::ReaderPtr reader(new zipper::FileReader(filename));
433 zipper::Decompressor decomp(reader);
434 std::vector<zipper::CompressedFilePtr> files(decomp.getEntries());
435 for (auto it(files.begin()); it != files.end(); it++)
436 {
437 if (myBootloader->isCorrectFirmware((*it)->getPath()))
438 {
439 std::stringstream msg;
440 msg << "Found firmware entry " << (*it)->getPath() <<
441 " within archive " << filename;
442 mmLogStatus(msg.str());
443 tmpFile =
444 wxFileName::CreateTempFileName(
445 wxT("SCSI2SD_Firmware"), static_cast<wxFile*>(NULL)
446 );
447 zipper::FileWriter out(tmpFile);
448 (*it)->decompress(out);
449 msg.clear();
450 msg << "Firmware extracted to " << tmpFile;
451 mmLogStatus(msg.str());
452 break;
453 }
454 }
455
456 if (tmpFile.empty())
457 {
458 // TODO allow "force" option
459 wxMessageBox(
460 "Wrong filename",
461 "Wrong filename",
462 wxOK | wxICON_ERROR);
463 return;
464 }
465
466 Firmware firmware(tmpFile);
467 totalFlashRows = firmware.totalFlashRows();
468 }
469 catch (std::exception& e)
470 {
471 mmLogStatus(e.what());
472 std::stringstream msg;
473 msg << "Could not open firmware file: " << e.what();
474 wxMessageBox(
475 msg.str(),
476 "Bad file",
477 wxOK | wxICON_ERROR);
478 wxRemoveFile(tmpFile);
479 return;
480 }
481
482 {
483 wxWindowPtr<wxGenericProgressDialog> progress(
484 new wxGenericProgressDialog(
485 "Loading firmware",
486 "Loading firmware",
487 totalFlashRows,
488 this,
489 wxPD_AUTO_HIDE | wxPD_REMAINING_TIME)
490 );
491 TheProgressWrapper.setProgressDialog(progress, totalFlashRows);
492 }
493
494 std::stringstream msg;
495 msg << "Upgrading firmware from file: " << tmpFile;
496 mmLogStatus(msg.str());
497
498 try
499 {
500 myBootloader->load(tmpFile, &ProgressUpdate);
501 TheProgressWrapper.clearProgressDialog();
502
503 wxMessageBox(
504 "Firmware update successful",
505 "Firmware OK",
506 wxOK);
507 mmLogStatus("Firmware update successful");
508
509
510 myHID.reset();
511 myBootloader.reset();
512 }
513 catch (std::exception& e)
514 {
515 TheProgressWrapper.clearProgressDialog();
516 mmLogStatus(e.what());
517 myHID.reset();
518 myBootloader.reset();
519
520 wxMessageBox(
521 "Firmware Update Failed",
522 e.what(),
523 wxOK | wxICON_ERROR);
524
525 wxRemoveFile(tmpFile);
526 }
527 }
528
529 void logSCSI()
530 {
531 if (!mySCSILogChk->IsChecked() ||
532 !myHID)
533 {
534 return;
535 }
536 try
537 {
538 std::vector<uint8_t> info(HID::HID_PACKET_SIZE);
539 if (myHID->readSCSIDebugInfo(info))
540 {
541 std::stringstream msg;
542 msg << std::hex;
543 for (size_t i = 0; i < 32 && i < info.size(); ++i)
544 {
545 msg << std::setfill('0') << std::setw(2) <<
546 static_cast<int>(info[i]) << ' ';
547 }
548 wxLogMessage(this, msg.str().c_str());
549 }
550 }
551 catch (std::exception& e)
552 {
553 wxLogWarning(this, e.what());
554 myHID.reset();
555 }
556 }
557
558 void OnID_Timer(wxTimerEvent& event)
559 {
560 logSCSI();
561 time_t now = time(NULL);
562 if (now == myLastPollTime) return;
563 myLastPollTime = now;
564
565 // Check if we are connected to the HID device.
566 // AND/or bootloader device.
567 try
568 {
569 if (myBootloader)
570 {
571 // Verify the USB HID connection is valid
572 if (!myBootloader->ping())
573 {
574 myBootloader.reset();
575 }
576 }
577
578 if (!myBootloader)
579 {
580 myBootloader.reset(Bootloader::Open());
581
582 if (myBootloader)
583 {
584 mmLogStatus("SCSI2SD Bootloader Ready");
585 }
586 }
587
588 int supressLog = 0;
589 if (myHID && myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
590 {
591 // No method to check connection is still valid.
592 // So assume it isn't.
593 myHID.reset();
594 supressLog = 1;
595 }
596 else if (myHID && !myHID->ping())
597 {
598 // Verify the USB HID connection is valid
599 myHID.reset();
600 }
601
602 if (!myHID)
603 {
604 myHID.reset(HID::Open());
605 if (myHID)
606 {
607 if (myHID->getFirmwareVersion() < MIN_FIRMWARE_VERSION)
608 {
609 if (!supressLog)
610 {
611 // Oh dear, old firmware
612 std::stringstream msg;
613 msg << "Firmware update required. Version " <<
614 myHID->getFirmwareVersionStr();
615 mmLogStatus(msg.str());
616 }
617 }
618 else
619 {
620 std::stringstream msg;
621 msg << "SCSI2SD Ready, firmware version " <<
622 myHID->getFirmwareVersionStr();
623 mmLogStatus(msg.str());
624
625 std::vector<uint8_t> csd(myHID->getSD_CSD());
626 std::vector<uint8_t> cid(myHID->getSD_CID());
627 std::stringstream sdinfo;
628 sdinfo << "SD Capacity (512-byte sectors): " <<
629 myHID->getSDCapacity() << std::endl;
630
631 sdinfo << "SD CSD Register: ";
632 for (size_t i = 0; i < csd.size(); ++i)
633 {
634 sdinfo <<
635 std::hex << std::setfill('0') << std::setw(2) <<
636 static_cast<int>(csd[i]);
637 }
638 sdinfo << std::endl;
639 sdinfo << "SD CID Register: ";
640 for (size_t i = 0; i < cid.size(); ++i)
641 {
642 sdinfo <<
643 std::hex << std::setfill('0') << std::setw(2) <<
644 static_cast<int>(cid[i]);
645 }
646
647 wxLogMessage(this, "%s", sdinfo.str());
648
649 if (!myInitialConfig)
650 {
651 wxCommandEvent loadEvent(wxEVT_NULL, ID_BtnLoad);
652 GetEventHandler()->AddPendingEvent(loadEvent);
653 }
654
655 }
656 }
657 else
658 {
659 char ticks[] = {'/', '-', '\\', '|'};
660 std::stringstream ss;
661 ss << "Searching for SCSI2SD device " << ticks[myTickCounter % sizeof(ticks)];
662 myTickCounter++;
663 SetStatusText(ss.str());
664 }
665 }
666 }
667 catch (std::runtime_error& e)
668 {
669 std::cerr << e.what() << std::endl;
670 mmLogStatus(e.what());
671 }
672
673 evaluate();
674 }
675
676 void doLoad(wxCommandEvent& event)
677 {
678 TimerLock lock(myTimer);
679 if (!myHID) return;
680
681 mmLogStatus("Loading configuration");
682
683 wxWindowPtr<wxGenericProgressDialog> progress(
684 new wxGenericProgressDialog(
685 "Load config settings",
686 "Loading config settings",
687 100,
688 this,
689 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
690 );
691
692 int flashRow = SCSI_CONFIG_0_ROW;
693 int currentProgress = 0;
694 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
695 for (size_t i = 0;
696 i < myTargets.size();
697 ++i, flashRow += SCSI_CONFIG_ROWS)
698 {
699 std::vector<uint8_t> raw(sizeof(TargetConfig));
700
701 for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
702 {
703 std::stringstream ss;
704 ss << "Reading flash array " << SCSI_CONFIG_ARRAY <<
705 " row " << (flashRow + j);
706 mmLogStatus(ss.str());
707 currentProgress += 1;
708 if (!progress->Update(
709 (100 * currentProgress) / totalProgress,
710 ss.str()
711 )
712 )
713 {
714 goto abort;
715 }
716
717 std::vector<uint8_t> flashData;
718
719 try
720 {
721 myHID->readFlashRow(
722 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
723
724 }
725 catch (std::runtime_error& e)
726 {
727 mmLogStatus(e.what());
728 goto err;
729 }
730
731 std::copy(
732 flashData.begin(),
733 flashData.end(),
734 &raw[j * SCSI_CONFIG_ROW_SIZE]);
735 }
736 myTargets[i]->setConfig(ConfigUtil::fromBytes(&raw[0]));
737 }
738
739 myInitialConfig = true;
740 mmLogStatus("Load Complete");
741 while (progress->Update(100, "Load Complete"))
742 {
743 // Wait for the user to click "Close"
744 wxMilliSleep(50);
745 }
746 goto out;
747
748 err:
749 mmLogStatus("Load failed");
750 while (progress->Update(100, "Load failed"))
751 {
752 // Wait for the user to click "Close"
753 wxMilliSleep(50);
754 }
755 goto out;
756
757 abort:
758 mmLogStatus("Load Aborted");
759
760 out:
761 return;
762 }
763
764 void doSave(wxCommandEvent& event)
765 {
766 TimerLock lock(myTimer);
767 if (!myHID) return;
768
769 mmLogStatus("Saving configuration");
770
771 wxWindowPtr<wxGenericProgressDialog> progress(
772 new wxGenericProgressDialog(
773 "Save config settings",
774 "Saving config settings",
775 100,
776 this,
777 wxPD_CAN_ABORT | wxPD_REMAINING_TIME)
778 );
779
780 int flashRow = SCSI_CONFIG_0_ROW;
781 int currentProgress = 0;
782 int totalProgress = myTargets.size() * SCSI_CONFIG_ROWS;
783 for (size_t i = 0;
784 i < myTargets.size();
785 ++i, flashRow += SCSI_CONFIG_ROWS)
786 {
787 TargetConfig config(myTargets[i]->getConfig());
788 std::vector<uint8_t> raw(ConfigUtil::toBytes(config));
789
790 for (size_t j = 0; j < SCSI_CONFIG_ROWS; ++j)
791 {
792 std::stringstream ss;
793 ss << "Programming flash array " << SCSI_CONFIG_ARRAY <<
794 " row " << (flashRow + j);
795 mmLogStatus(ss.str());
796 currentProgress += 1;
797 if (!progress->Update(
798 (100 * currentProgress) / totalProgress,
799 ss.str()
800 )
801 )
802 {
803 goto abort;
804 }
805
806 std::vector<uint8_t> flashData(SCSI_CONFIG_ROW_SIZE, 0);
807 std::copy(
808 &raw[j * SCSI_CONFIG_ROW_SIZE],
809 &raw[(1+j) * SCSI_CONFIG_ROW_SIZE],
810 flashData.begin());
811 try
812 {
813 myHID->writeFlashRow(
814 SCSI_CONFIG_ARRAY, flashRow + j, flashData);
815 }
816 catch (std::runtime_error& e)
817 {
818 mmLogStatus(e.what());
819 goto err;
820 }
821 }
822 }
823
824 // Reboot so new settings take effect.
825 myHID->enterBootloader();
826 myHID.reset();
827
828 mmLogStatus("Save Complete");
829 while (progress->Update(100, "Save Complete"))
830 {
831 // Wait for the user to click "Close"
832 wxMilliSleep(50);
833 }
834 goto out;
835
836 err:
837 mmLogStatus("Save failed");
838 while (progress->Update(100, "Save failed"))
839 {
840 // Wait for the user to click "Close"
841 wxMilliSleep(50);
842 }
843 goto out;
844
845 abort:
846 mmLogStatus("Save Aborted");
847
848 out:
849 return;
850 }
851
852 // Note: Don't confuse this with the wxApp::OnExit virtual method
853 void OnExitEvt(wxCommandEvent& event)
854 {
855 Close(true);
856 }
857
858 void OnAbout(wxCommandEvent& event)
859 {
860 wxMessageBox(
861 "SCSI2SD (scsi2sd-util)\n"
862 "Copyright (C) 2014 Michael McMaster <michael@codesrc.com>\n"
863 "\n"
864 "This program is free software: you can redistribute it and/or modify\n"
865 "it under the terms of the GNU General Public License as published by\n"
866 "the Free Software Foundation, either version 3 of the License, or\n"
867 "(at your option) any later version.\n"
868 "\n"
869 "This program is distributed in the hope that it will be useful,\n"
870 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
871 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
872 "GNU General Public License for more details.\n"
873 "\n"
874 "You should have received a copy of the GNU General Public License\n"
875 "along with this program. If not, see <http://www.gnu.org/licenses/>.\n",
876
877 "About scsi2sd-util", wxOK | wxICON_INFORMATION );
878 }
879
880 wxDECLARE_EVENT_TABLE();
881 };
882
883 wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
884 EVT_MENU(AppFrame::ID_ConfigDefaults, AppFrame::OnID_ConfigDefaults)
885 EVT_MENU(AppFrame::ID_Firmware, AppFrame::OnID_Firmware)
886 EVT_MENU(AppFrame::ID_LogWindow, AppFrame::OnID_LogWindow)
887 EVT_MENU(wxID_EXIT, AppFrame::OnExitEvt)
888 EVT_MENU(wxID_ABOUT, AppFrame::OnAbout)
889
890 EVT_TIMER(AppFrame::ID_Timer, AppFrame::OnID_Timer)
891
892 EVT_COMMAND(wxID_ANY, ConfigChangedEvent, AppFrame::onConfigChanged)
893
894 EVT_BUTTON(ID_BtnSave, AppFrame::doSave)
895 EVT_BUTTON(ID_BtnLoad, AppFrame::doLoad)
896
897
898 wxEND_EVENT_TABLE()
899
900
901
902 class App : public wxApp
903 {
904 public:
905 virtual bool OnInit()
906 {
907 AppFrame* frame = new AppFrame();
908 frame->Show(true);
909 SetTopWindow(frame);
910 return true;
911 }
912 };
913 } // namespace
914
915 // Main Method
916 wxIMPLEMENT_APP(App);
917
918