openMSX
Reactor.cc
Go to the documentation of this file.
1 #include "Reactor.hh"
2 #include "CommandLineParser.hh"
3 #include "RTScheduler.hh"
4 #include "EventDistributor.hh"
6 #include "InputEventGenerator.hh"
7 #include "InputEvents.hh"
8 #include "DiskFactory.hh"
9 #include "DiskManipulator.hh"
10 #include "DiskChanger.hh"
11 #include "FilePool.hh"
12 #include "UserSettings.hh"
13 #include "RomDatabase.hh"
14 #include "RomInfo.hh"
15 #include "TclCallbackMessages.hh"
16 #include "MSXMotherBoard.hh"
18 #include "Command.hh"
19 #include "AfterCommand.hh"
20 #include "MessageCommand.hh"
21 #include "CommandException.hh"
22 #include "GlobalCliComm.hh"
23 #include "InfoTopic.hh"
24 #include "Display.hh"
25 #include "Mixer.hh"
26 #include "AviRecorder.hh"
27 #include "GlobalSettings.hh"
28 #include "BooleanSetting.hh"
29 #include "EnumSetting.hh"
30 #include "TclObject.hh"
31 #include "HardwareConfig.hh"
32 #include "XMLElement.hh"
33 #include "XMLException.hh"
34 #include "FileContext.hh"
35 #include "FileException.hh"
36 #include "FileOperations.hh"
37 #include "ReadDir.hh"
38 #include "Thread.hh"
39 #include "Timer.hh"
40 #include "serialize.hh"
41 #include "checked_cast.hh"
42 #include "ranges.hh"
43 #include "statp.hh"
44 #include "stl.hh"
45 #include "unreachable.hh"
46 #include "view.hh"
47 #include "build-info.hh"
48 #include <cassert>
49 #include <memory>
50 
51 using std::make_shared;
52 using std::make_unique;
53 using std::string;
54 using std::vector;
55 
56 namespace openmsx {
57 
58 // global variable to communicate the exit-code from the 'exit' command to main()
59 int exitCode = 0;
60 
61 class ExitCommand final : public Command
62 {
63 public:
64  ExitCommand(CommandController& commandController, EventDistributor& distributor);
65  void execute(span<const TclObject> tokens, TclObject& result) override;
66  string help(const vector<string>& tokens) const override;
67 private:
68  EventDistributor& distributor;
69 };
70 
71 class MachineCommand final : public Command
72 {
73 public:
74  MachineCommand(CommandController& commandController, Reactor& reactor);
75  void execute(span<const TclObject> tokens, TclObject& result) override;
76  string help(const vector<string>& tokens) const override;
77  void tabCompletion(vector<string>& tokens) const override;
78 private:
79  Reactor& reactor;
80 };
81 
82 class TestMachineCommand final : public Command
83 {
84 public:
85  TestMachineCommand(CommandController& commandController, Reactor& reactor);
86  void execute(span<const TclObject> tokens, TclObject& result) override;
87  string help(const vector<string>& tokens) const override;
88  void tabCompletion(vector<string>& tokens) const override;
89 private:
90  Reactor& reactor;
91 };
92 
93 class CreateMachineCommand final : public Command
94 {
95 public:
96  CreateMachineCommand(CommandController& commandController, Reactor& reactor);
97  void execute(span<const TclObject> tokens, TclObject& result) override;
98  string help(const vector<string>& tokens) const override;
99 private:
100  Reactor& reactor;
101 };
102 
103 class DeleteMachineCommand final : public Command
104 {
105 public:
106  DeleteMachineCommand(CommandController& commandController, Reactor& reactor);
107  void execute(span<const TclObject> tokens, TclObject& result) override;
108  string help(const vector<string>& tokens) const override;
109  void tabCompletion(vector<string>& tokens) const override;
110 private:
111  Reactor& reactor;
112 };
113 
114 class ListMachinesCommand final : public Command
115 {
116 public:
117  ListMachinesCommand(CommandController& commandController, Reactor& reactor);
118  void execute(span<const TclObject> tokens, TclObject& result) override;
119  string help(const vector<string>& tokens) const override;
120 private:
121  Reactor& reactor;
122 };
123 
124 class ActivateMachineCommand final : public Command
125 {
126 public:
127  ActivateMachineCommand(CommandController& commandController, Reactor& reactor);
128  void execute(span<const TclObject> tokens, TclObject& result) override;
129  string help(const vector<string>& tokens) const override;
130  void tabCompletion(vector<string>& tokens) const override;
131 private:
132  Reactor& reactor;
133 };
134 
135 class StoreMachineCommand final : public Command
136 {
137 public:
138  StoreMachineCommand(CommandController& commandController, Reactor& reactor);
139  void execute(span<const TclObject> tokens, TclObject& result) override;
140  string help(const vector<string>& tokens) const override;
141  void tabCompletion(vector<string>& tokens) const override;
142 private:
143  Reactor& reactor;
144 };
145 
146 class RestoreMachineCommand final : public Command
147 {
148 public:
149  RestoreMachineCommand(CommandController& commandController, Reactor& reactor);
150  void execute(span<const TclObject> tokens, TclObject& result) override;
151  string help(const vector<string>& tokens) const override;
152  void tabCompletion(vector<string>& tokens) const override;
153 private:
154  Reactor& reactor;
155 };
156 
157 class GetClipboardCommand final : public Command
158 {
159 public:
160  GetClipboardCommand(CommandController& commandController);
161  void execute(span<const TclObject> tokens, TclObject& result) override;
162  string help(const vector<string>& tokens) const override;
163 };
164 
165 class SetClipboardCommand final : public Command
166 {
167 public:
168  SetClipboardCommand(CommandController& commandController);
169  void execute(span<const TclObject> tokens, TclObject& result) override;
170  string help(const vector<string>& tokens) const override;
171 };
172 
173 class ConfigInfo final : public InfoTopic
174 {
175 public:
176  ConfigInfo(InfoCommand& openMSXInfoCommand, const string& configName);
177  void execute(span<const TclObject> tokens,
178  TclObject& result) const override;
179  string help(const vector<string>& tokens) const override;
180  void tabCompletion(vector<string>& tokens) const override;
181 private:
182  const string configName;
183 };
184 
185 class RealTimeInfo final : public InfoTopic
186 {
187 public:
188  explicit RealTimeInfo(InfoCommand& openMSXInfoCommand);
189  void execute(span<const TclObject> tokens,
190  TclObject& result) const override;
191  string help(const vector<string>& tokens) const override;
192 private:
193  const uint64_t reference;
194 };
195 
197 {
198 public:
199  SoftwareInfoTopic(InfoCommand& openMSXInfoCommand, Reactor& reactor);
200  void execute(span<const TclObject> tokens,
201  TclObject& result) const override;
202  std::string help(const std::vector<std::string>& tokens) const override;
203 private:
204  Reactor& reactor;
205 };
206 
207 
208 Reactor::Reactor() = default;
209 
211 {
212  rtScheduler = make_unique<RTScheduler>();
213  eventDistributor = make_unique<EventDistributor>(*this);
214  globalCliComm = make_unique<GlobalCliComm>();
215  globalCommandController = make_unique<GlobalCommandController>(
216  *eventDistributor, *globalCliComm, *this);
217  globalSettings = make_unique<GlobalSettings>(
218  *globalCommandController);
219  inputEventGenerator = make_unique<InputEventGenerator>(
220  *globalCommandController, *eventDistributor, *globalSettings);
221  mixer = make_unique<Mixer>(
222  *this, *globalCommandController);
223  diskFactory = make_unique<DiskFactory>(
224  *this);
225  diskManipulator = make_unique<DiskManipulator>(
226  *globalCommandController, *this);
227  virtualDrive = make_unique<DiskChanger>(
228  *this, "virtual_drive");
229  filePool = make_unique<FilePool>(*globalCommandController, *this);
230  userSettings = make_unique<UserSettings>(
231  *globalCommandController);
232  afterCommand = make_unique<AfterCommand>(
233  *this, *eventDistributor, *globalCommandController);
234  exitCommand = make_unique<ExitCommand>(
235  *globalCommandController, *eventDistributor);
236  messageCommand = make_unique<MessageCommand>(
237  *globalCommandController);
238  machineCommand = make_unique<MachineCommand>(
239  *globalCommandController, *this);
240  testMachineCommand = make_unique<TestMachineCommand>(
241  *globalCommandController, *this);
242  createMachineCommand = make_unique<CreateMachineCommand>(
243  *globalCommandController, *this);
244  deleteMachineCommand = make_unique<DeleteMachineCommand>(
245  *globalCommandController, *this);
246  listMachinesCommand = make_unique<ListMachinesCommand>(
247  *globalCommandController, *this);
248  activateMachineCommand = make_unique<ActivateMachineCommand>(
249  *globalCommandController, *this);
250  storeMachineCommand = make_unique<StoreMachineCommand>(
251  *globalCommandController, *this);
252  restoreMachineCommand = make_unique<RestoreMachineCommand>(
253  *globalCommandController, *this);
254  getClipboardCommand = make_unique<GetClipboardCommand>(
255  *globalCommandController);
256  setClipboardCommand = make_unique<SetClipboardCommand>(
257  *globalCommandController);
258  aviRecordCommand = make_unique<AviRecorder>(*this);
259  extensionInfo = make_unique<ConfigInfo>(
260  getOpenMSXInfoCommand(), "extensions");
261  machineInfo = make_unique<ConfigInfo>(
262  getOpenMSXInfoCommand(), "machines");
263  realTimeInfo = make_unique<RealTimeInfo>(
264  getOpenMSXInfoCommand());
265  softwareInfoTopic = make_unique<SoftwareInfoTopic>(
266  getOpenMSXInfoCommand(), *this);
267  tclCallbackMessages = make_unique<TclCallbackMessages>(
268  *globalCliComm, *globalCommandController);
269 
270  createMachineSetting();
271 
272  getGlobalSettings().getPauseSetting().attach(*this);
273 
274  eventDistributor->registerEventListener(OPENMSX_QUIT_EVENT, *this);
275 #if PLATFORM_ANDROID
276  eventDistributor->registerEventListener(OPENMSX_FOCUS_EVENT, *this);
277 #endif
278  eventDistributor->registerEventListener(OPENMSX_DELETE_BOARDS, *this);
279  isInit = true;
280 }
281 
283 {
284  if (!isInit) return;
285  deleteBoard(activeBoard);
286 
287  eventDistributor->unregisterEventListener(OPENMSX_QUIT_EVENT, *this);
288 #if PLATFORM_ANDROID
289  eventDistributor->unregisterEventListener(OPENMSX_FOCUS_EVENT, *this);
290 #endif
291  eventDistributor->unregisterEventListener(OPENMSX_DELETE_BOARDS, *this);
292 
293  getGlobalSettings().getPauseSetting().detach(*this);
294 }
295 
297 {
298  if (!softwareDatabase) {
299  softwareDatabase = make_unique<RomDatabase>(*globalCliComm);
300  }
301  return *softwareDatabase;
302 }
303 
305 {
306  return *globalCliComm;
307 }
308 
310 {
312 }
313 
315 {
316  return *globalCommandController;
317 }
318 
320 {
321  return globalCommandController->getOpenMSXInfoCommand();
322 }
323 
324 vector<string> Reactor::getHwConfigs(string_view type)
325 {
326  vector<string> result;
327  for (auto& p : systemFileContext().getPaths()) {
328  const auto& path = FileOperations::join(p, type);
329  ReadDir configsDir(path);
330  while (auto* entry = configsDir.getEntry()) {
331  string_view name = entry->d_name;
332  const auto& fullname = FileOperations::join(path, name);
333  if (name.ends_with(".xml") &&
334  FileOperations::isRegularFile(fullname)) {
335  name.remove_suffix(4);
336  result.push_back(name.str());
337  } else if (FileOperations::isDirectory(fullname)) {
338  const auto& config = FileOperations::join(
339  fullname, "hardwareconfig.xml");
340  if (FileOperations::isRegularFile(config)) {
341  result.push_back(name.str());
342  }
343  }
344  }
345  }
346  // remove duplicates
347  ranges::sort(result);
348  result.erase(ranges::unique(result), end(result));
349  return result;
350 }
351 
352 void Reactor::createMachineSetting()
353 {
354  EnumSetting<int>::Map machines; // int's are unique dummy values
355  int count = 1;
356  append(machines, view::transform(getHwConfigs("machines"),
357  [&](auto& name) { return std::make_pair(name, count++); }));
358  machines.emplace_back("C-BIOS_MSX2+", 0); // default machine
359 
360  machineSetting = make_unique<EnumSetting<int>>(
361  *globalCommandController, "default_machine",
362  "default machine (takes effect next time openMSX is started)",
363  0, std::move(machines));
364 }
365 
367 {
368  assert(Thread::isMainThread());
369  return activeBoard;
370 }
371 
373 {
374  return activeBoard ? activeBoard->getMachineID() : string_view{};
375 }
376 
377 vector<string_view> Reactor::getMachineIDs() const
378 {
379  return to_vector(view::transform(
380  boards, [](auto& b) { return b->getMachineID(); }));
381 }
382 
383 MSXMotherBoard& Reactor::getMachine(string_view machineID) const
384 {
385  for (auto& b : boards) {
386  if (b->getMachineID() == machineID) {
387  return *b;
388  }
389  }
390  throw CommandException("No machine with ID: ", machineID);
391 }
392 
394 {
395  return make_unique<MSXMotherBoard>(*this);
396 }
397 
398 void Reactor::replaceBoard(MSXMotherBoard& oldBoard_, Board newBoard_)
399 {
400  assert(Thread::isMainThread());
401 
402  // Add new board.
403  auto* newBoard = newBoard_.get();
404  boards.push_back(move(newBoard_));
405 
406  // Lookup old board (it must be present).
407  auto it = find_if_unguarded(boards,
408  [&](Boards::value_type& b) { return b.get() == &oldBoard_; });
409 
410  // If the old board was the active board, then activate the new board
411  if (it->get() == activeBoard) {
412  switchBoard(newBoard);
413  }
414 
415  // Remove (=delete) the old board.
416  // Note that we don't use the 'garbageBoards' mechanism as used in
417  // deleteBoard(). This means oldBoard cannot be used anymore right
418  // after this method returns.
419  move_pop_back(boards, it);
420 }
421 
422 void Reactor::switchMachine(const string& machine)
423 {
424  if (!display) {
425  display = make_unique<Display>(*this);
426  // TODO: Currently it is not possible to move this call into the
427  // constructor of Display because the call to createVideoSystem()
428  // indirectly calls Reactor.getDisplay().
429  display->createVideoSystem();
430  }
431 
432  // create+load new machine
433  // switch to new machine
434  // delete old active machine
435 
436  assert(Thread::isMainThread());
437  // Note: loadMachine can throw an exception and in that case the
438  // motherboard must be considered as not created at all.
439  auto newBoard_ = createEmptyMotherBoard();
440  auto* newBoard = newBoard_.get();
441  newBoard->loadMachine(machine);
442  boards.push_back(move(newBoard_));
443 
444  auto* oldBoard = activeBoard;
445  switchBoard(newBoard);
446  deleteBoard(oldBoard);
447 }
448 
449 void Reactor::switchBoard(MSXMotherBoard* newBoard)
450 {
451  assert(Thread::isMainThread());
452  assert(!newBoard ||
453  (ranges::any_of(boards, [&](auto& b) { return b.get() == newBoard; })));
454  assert(!activeBoard ||
455  (ranges::any_of(boards, [&](auto& b) { return b.get() == activeBoard; })));
456  if (activeBoard) {
457  activeBoard->activate(false);
458  }
459  {
460  // Don't hold the lock for longer than the actual switch.
461  // In the past we had a potential for deadlocks here, because
462  // (indirectly) the code below still acquires other locks.
463  std::lock_guard<std::mutex> lock(mbMutex);
464  activeBoard = newBoard;
465  }
466  eventDistributor->distributeEvent(
467  make_shared<SimpleEvent>(OPENMSX_MACHINE_LOADED_EVENT));
468  globalCliComm->update(CliComm::HARDWARE, getMachineID(), "select");
469  if (activeBoard) {
470  activeBoard->activate(true);
471  }
472 }
473 
474 void Reactor::deleteBoard(MSXMotherBoard* board)
475 {
476  // Note: pass 'board' by-value to keep the parameter from changing
477  // after the call to switchBoard(). switchBoard() changes the
478  // 'activeBoard' member variable, so the 'board' parameter would change
479  // if it were passed by reference to this method (AFAICS this only
480  // happens in ~Reactor()).
481  assert(Thread::isMainThread());
482  if (!board) return;
483 
484  if (board == activeBoard) {
485  // delete active board -> there is no active board anymore
486  switchBoard(nullptr);
487  }
488  auto it = rfind_if_unguarded(boards,
489  [&](Boards::value_type& b) { return b.get() == board; });
490  auto board_ = move(*it);
491  move_pop_back(boards, it);
492  // Don't immediately delete old boards because it's possible this
493  // routine is called via a code path that goes through the old
494  // board. Instead remember this board and delete it at a safe moment
495  // in time.
496  garbageBoards.push_back(move(board_));
497  eventDistributor->distributeEvent(
498  make_shared<SimpleEvent>(OPENMSX_DELETE_BOARDS));
499 }
500 
502 {
503  // Note: this method can get called from different threads
504  if (Thread::isMainThread()) {
505  // Don't take lock in main thread to avoid recursive locking.
506  if (activeBoard) {
507  activeBoard->exitCPULoopSync();
508  }
509  } else {
510  std::lock_guard<std::mutex> lock(mbMutex);
511  if (activeBoard) {
512  activeBoard->exitCPULoopAsync();
513  }
514  }
515 }
516 
518 {
519  auto& commandController = *globalCommandController;
520 
521  // execute init.tcl
522  try {
523  commandController.source(
524  preferSystemFileContext().resolve("init.tcl"));
525  } catch (FileException&) {
526  // no init.tcl, ignore
527  }
528 
529  // execute startup scripts
530  for (auto& s : parser.getStartupScripts()) {
531  try {
532  commandController.source(userFileContext().resolve(s));
533  } catch (FileException& e) {
534  throw FatalError("Couldn't execute script: ",
535  e.getMessage());
536  }
537  }
538 
539  // At this point openmsx is fully started, it's OK now to start
540  // accepting external commands
541  getGlobalCliComm().setAllowExternalCommands();
542 
543  // Run
544  if (parser.getParseStatus() == CommandLineParser::RUN) {
545  // don't use Tcl to power up the machine, we cannot pass
546  // exceptions through Tcl and ADVRAM might throw in its
547  // powerUp() method. Solution is to implement dependencies
548  // between devices so ADVRAM can check the error condition
549  // in its constructor
550  //commandController.executeCommand("set power on");
551  if (activeBoard) {
552  activeBoard->powerUp();
553  }
554  }
555 
556  while (running) {
557  eventDistributor->deliverEvents();
558  assert(garbageBoards.empty());
559  bool blocked = (blockedCounter > 0) || !activeBoard;
560  if (!blocked) blocked = !activeBoard->execute();
561  if (blocked) {
562  // At first sight a better alternative is to use the
563  // SDL_WaitEvent() function. Though when inspecting
564  // the implementation of that function, it turns out
565  // to also use a sleep/poll loop, with even shorter
566  // sleep periods as we use here. Maybe in future
567  // SDL implementations this will be improved.
568  eventDistributor->sleep(20 * 1000);
569  }
570  }
571 }
572 
573 void Reactor::unpause()
574 {
575  if (paused) {
576  paused = false;
577  globalCliComm->update(CliComm::STATUS, "paused", "false");
578  unblock();
579  }
580 }
581 
582 void Reactor::pause()
583 {
584  if (!paused) {
585  paused = true;
586  globalCliComm->update(CliComm::STATUS, "paused", "true");
587  block();
588  }
589 }
590 
592 {
593  ++blockedCounter;
594  enterMainLoop();
595  mixer->mute();
596 }
597 
599 {
600  --blockedCounter;
601  assert(blockedCounter >= 0);
602  mixer->unmute();
603 }
604 
605 
606 // Observer<Setting>
607 void Reactor::update(const Setting& setting)
608 {
609  auto& pauseSetting = getGlobalSettings().getPauseSetting();
610  if (&setting == &pauseSetting) {
611  if (pauseSetting.getBoolean()) {
612  pause();
613  } else {
614  unpause();
615  }
616  }
617 }
618 
619 // EventListener
620 int Reactor::signalEvent(const std::shared_ptr<const Event>& event)
621 {
622  auto type = event->getType();
623  if (type == OPENMSX_QUIT_EVENT) {
624  enterMainLoop();
625  running = false;
626 #if PLATFORM_ANDROID
627  } else if (type == OPENMSX_FOCUS_EVENT) {
628  // Android SDL port sends a (un)focus event when an app is put in background
629  // by the OS for whatever reason (like an incoming phone call) and all screen
630  // resources are taken away from the app.
631  // In such case the app is supposed to behave as a good citizen
632  // and minize its resource usage and related battery drain.
633  // The SDL Android port already takes care of halting the Java
634  // part of the sound processing. The Display class makes sure that it wont try
635  // to render anything to the (temporary missing) graphics resources but the
636  // main emulation should also be temporary stopped, in order to minimize CPU usage
637  auto& focusEvent = checked_cast<const FocusEvent&>(*event);
638  if (focusEvent.getGain()) {
639  unblock();
640  } else {
641  block();
642  }
643 #endif
644  } else if (type == OPENMSX_DELETE_BOARDS) {
645  // Doesn't really matter which one we delete, just that we do
646  // one per event.
647  assert(!garbageBoards.empty());
648  garbageBoards.pop_back();
649  } else {
650  UNREACHABLE; // we didn't subscribe to this event...
651  }
652  return 0;
653 }
654 
655 
656 // class ExitCommand
657 
659  EventDistributor& distributor_)
660  : Command(commandController_, "exit")
661  , distributor(distributor_)
662 {
663 }
664 
666 {
667  checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?exitcode?");
668  switch (tokens.size()) {
669  case 1:
670  exitCode = 0;
671  break;
672  case 2:
673  exitCode = tokens[1].getInt(getInterpreter());
674  break;
675  }
676  distributor.distributeEvent(make_shared<QuitEvent>());
677 }
678 
679 string ExitCommand::help(const vector<string>& /*tokens*/) const
680 {
681  return "Use this command to stop the emulator.\n"
682  "Optionally you can pass an exit-code.\n";
683 }
684 
685 
686 // class MachineCommand
687 
689  Reactor& reactor_)
690  : Command(commandController_, "machine")
691  , reactor(reactor_)
692 {
693 }
694 
696 {
697  checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?machinetype?");
698  switch (tokens.size()) {
699  case 1: // get current machine
700  // nothing
701  break;
702  case 2:
703  try {
704  reactor.switchMachine(tokens[1].getString().str());
705  } catch (MSXException& e) {
706  throw CommandException("Machine switching failed: ",
707  e.getMessage());
708  }
709  break;
710  }
711  // Always return machineID (of current or of new machine).
712  result = reactor.getMachineID();
713 }
714 
715 string MachineCommand::help(const vector<string>& /*tokens*/) const
716 {
717  return "Switch to a different MSX machine.";
718 }
719 
720 void MachineCommand::tabCompletion(vector<string>& tokens) const
721 {
722  completeString(tokens, Reactor::getHwConfigs("machines"));
723 }
724 
725 
726 // class TestMachineCommand
727 
729  Reactor& reactor_)
730  : Command(commandController_, "test_machine")
731  , reactor(reactor_)
732 {
733 }
734 
736  TclObject& result)
737 {
738  checkNumArgs(tokens, 2, "machinetype");
739  try {
740  MSXMotherBoard mb(reactor);
741  mb.loadMachine(tokens[1].getString().str());
742  } catch (MSXException& e) {
743  result = e.getMessage(); // error
744  }
745 }
746 
747 string TestMachineCommand::help(const vector<string>& /*tokens*/) const
748 {
749  return "Test the configuration for the given machine. "
750  "Returns an error message explaining why the configuration is "
751  "invalid or an empty string in case of success.";
752 }
753 
754 void TestMachineCommand::tabCompletion(vector<string>& tokens) const
755 {
756  completeString(tokens, Reactor::getHwConfigs("machines"));
757 }
758 
759 
760 // class CreateMachineCommand
761 
763  CommandController& commandController_, Reactor& reactor_)
764  : Command(commandController_, "create_machine")
765  , reactor(reactor_)
766 {
767 }
768 
770 {
771  checkNumArgs(tokens, 1, Prefix{1}, nullptr);
772  auto newBoard = reactor.createEmptyMotherBoard();
773  result = newBoard->getMachineID();
774  reactor.boards.push_back(move(newBoard));
775 }
776 
777 string CreateMachineCommand::help(const vector<string>& /*tokens*/) const
778 {
779  return "Creates a new (empty) MSX machine. Returns the ID for the new "
780  "machine.\n"
781  "Use 'load_machine' to actually load a machine configuration "
782  "into this new machine.\n"
783  "The main reason create_machine and load_machine are two "
784  "separate commands is that sometimes you already want to know "
785  "the ID of the machine before load_machine starts emitting "
786  "events for this machine.";
787 }
788 
789 
790 // class DeleteMachineCommand
791 
793  CommandController& commandController_, Reactor& reactor_)
794  : Command(commandController_, "delete_machine")
795  , reactor(reactor_)
796 {
797 }
798 
800  TclObject& /*result*/)
801 {
802  checkNumArgs(tokens, 2, "id");
803  reactor.deleteBoard(&reactor.getMachine(tokens[1].getString()));
804 }
805 
806 string DeleteMachineCommand::help(const vector<string>& /*tokens*/) const
807 {
808  return "Deletes the given MSX machine.";
809 }
810 
811 void DeleteMachineCommand::tabCompletion(vector<string>& tokens) const
812 {
813  completeString(tokens, reactor.getMachineIDs());
814 }
815 
816 
817 // class ListMachinesCommand
818 
820  CommandController& commandController_, Reactor& reactor_)
821  : Command(commandController_, "list_machines")
822  , reactor(reactor_)
823 {
824 }
825 
827  TclObject& result)
828 {
829  result.addListElements(reactor.getMachineIDs());
830 }
831 
832 string ListMachinesCommand::help(const vector<string>& /*tokens*/) const
833 {
834  return "Returns a list of all machine IDs.";
835 }
836 
837 
838 // class ActivateMachineCommand
839 
841  CommandController& commandController_, Reactor& reactor_)
842  : Command(commandController_, "activate_machine")
843  , reactor(reactor_)
844 {
845 }
846 
848  TclObject& result)
849 {
850  checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "id");
851  switch (tokens.size()) {
852  case 1:
853  break;
854  case 2:
855  reactor.switchBoard(&reactor.getMachine(tokens[1].getString()));
856  break;
857  }
858  result = reactor.getMachineID();
859 }
860 
861 string ActivateMachineCommand::help(const vector<string>& /*tokens*/) const
862 {
863  return "Make another machine the active msx machine.\n"
864  "Or when invoked without arguments, query the ID of the "
865  "active msx machine.";
866 }
867 
868 void ActivateMachineCommand::tabCompletion(vector<string>& tokens) const
869 {
870  completeString(tokens, reactor.getMachineIDs());
871 }
872 
873 
874 // class StoreMachineCommand
875 
877  CommandController& commandController_, Reactor& reactor_)
878  : Command(commandController_, "store_machine")
879  , reactor(reactor_)
880 {
881 }
882 
884 {
885  checkNumArgs(tokens, Between{1, 3}, Prefix{1}, "?id? ?filename?");
886  string filename;
887  string_view machineID;
888  switch (tokens.size()) {
889  case 1:
890  machineID = reactor.getMachineID();
891  filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
892  break;
893  case 2:
894  machineID = tokens[1].getString();
895  filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
896  break;
897  case 3:
898  machineID = tokens[1].getString();
899  filename = tokens[2].getString().str();
900  break;
901  }
902 
903  auto& board = reactor.getMachine(machineID);
904 
905  XmlOutputArchive out(filename);
906  out.serialize("machine", board);
907  out.close();
908  result = filename;
909 }
910 
911 string StoreMachineCommand::help(const vector<string>& /*tokens*/) const
912 {
913  return
914  "store_machine Save state of current machine to file \"openmsxNNNN.xml.gz\"\n"
915  "store_machine machineID Save state of machine \"machineID\" to file \"openmsxNNNN.xml.gz\"\n"
916  "store_machine machineID <filename> Save state of machine \"machineID\" to indicated file\n"
917  "\n"
918  "This is a low-level command, the 'savestate' script is easier to use.";
919 }
920 
921 void StoreMachineCommand::tabCompletion(vector<string>& tokens) const
922 {
923  completeString(tokens, reactor.getMachineIDs());
924 }
925 
926 
927 // class RestoreMachineCommand
928 
930  CommandController& commandController_, Reactor& reactor_)
931  : Command(commandController_, "restore_machine")
932  , reactor(reactor_)
933 {
934 }
935 
937  TclObject& result)
938 {
939  checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
940  auto newBoard = reactor.createEmptyMotherBoard();
941 
942  string filename;
943  switch (tokens.size()) {
944  case 1: {
945  // load last saved entry
946  struct stat st;
947  string dirName = FileOperations::getUserOpenMSXDir() + "/savestates/";
948  string lastEntry;
949  time_t lastTime = 0;
950  ReadDir dir(dirName);
951  while (dirent* d = dir.getEntry()) {
952  int res = stat(strCat(dirName, d->d_name).c_str(), &st);
953  if ((res == 0) && S_ISREG(st.st_mode)) {
954  time_t modTime = st.st_mtime;
955  if (modTime > lastTime) {
956  lastEntry = string(d->d_name);
957  lastTime = modTime;
958  }
959  }
960  }
961  if (lastEntry.empty()) {
962  throw CommandException("Can't find last saved state.");
963  }
964  filename = dirName + lastEntry;
965  break;
966  }
967  case 2:
968  filename = tokens[1].getString().str();
969  break;
970  }
971 
972  //std::cerr << "Loading " << filename << '\n';
973  try {
974  XmlInputArchive in(filename);
975  in.serialize("machine", *newBoard);
976  } catch (XMLException& e) {
977  throw CommandException("Cannot load state, bad file format: ",
978  e.getMessage());
979  } catch (MSXException& e) {
980  throw CommandException("Cannot load state: ", e.getMessage());
981  }
982 
983  // Savestate also contains stuff like the keyboard state at the moment
984  // the snapshot was created (this is required for reverse/replay). But
985  // now we want the MSX to see the actual host keyboard state.
986  newBoard->getStateChangeDistributor().stopReplay(newBoard->getCurrentTime());
987 
988  result = newBoard->getMachineID();
989  reactor.boards.push_back(move(newBoard));
990 }
991 
992 string RestoreMachineCommand::help(const vector<string>& /*tokens*/) const
993 {
994  return "restore_machine Load state from last saved state in default directory\n"
995  "restore_machine <filename> Load state from indicated file\n"
996  "\n"
997  "This is a low-level command, the 'loadstate' script is easier to use.";
998 }
999 
1000 void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
1001 {
1002  // TODO: add the default files (state files in user's savestates dir)
1003  completeFileName(tokens, userFileContext());
1004 }
1005 
1006 
1007 // class GetClipboardCommand
1008 
1010  : Command(commandController_, "get_clipboard_text")
1011 {
1012 }
1013 
1015 {
1016  checkNumArgs(tokens, 1, Prefix{1}, nullptr);
1017  if (char* text = SDL_GetClipboardText()) {
1018  result = text;
1019  SDL_free(text);
1020  }
1021 }
1022 
1023 string GetClipboardCommand::help(const vector<string>& /*tokens*/) const
1024 {
1025  return "Returns the (text) content of the clipboard as a string.";
1026 }
1027 
1028 
1029 // class SetClipboardCommand
1030 
1032  : Command(commandController_, "set_clipboard_text")
1033 {
1034 }
1035 
1037 {
1038  checkNumArgs(tokens, 2, "text");
1039  string text = tokens[1].getString().str();
1040  if (SDL_SetClipboardText(text.c_str()) != 0) {
1041  const char* err = SDL_GetError();
1042  SDL_ClearError();
1043  throw CommandException(err);
1044  }
1045 }
1046 
1047 string SetClipboardCommand::help(const vector<string>& /*tokens*/) const
1048 {
1049  return "Send the given string to the clipboard.";
1050 }
1051 
1052 
1053 // class ConfigInfo
1054 
1056  const string& configName_)
1057  : InfoTopic(openMSXInfoCommand, configName_)
1058  , configName(configName_)
1059 {
1060 }
1061 
1063 {
1064  // TODO make meta info available through this info topic
1065  switch (tokens.size()) {
1066  case 2: {
1067  result.addListElements(Reactor::getHwConfigs(configName));
1068  break;
1069  }
1070  case 3: {
1071  try {
1072  auto config = HardwareConfig::loadConfig(
1073  configName, tokens[2].getString());
1074  if (auto* info = config.findChild("info")) {
1075  for (auto& i : info->getChildren()) {
1076  result.addDictKeyValue(i.getName(), i.getData());
1077  }
1078  }
1079  } catch (MSXException& e) {
1080  throw CommandException(
1081  "Couldn't get config info: ", e.getMessage());
1082  }
1083  break;
1084  }
1085  default:
1086  throw CommandException("Too many parameters");
1087  }
1088 }
1089 
1090 string ConfigInfo::help(const vector<string>& /*tokens*/) const
1091 {
1092  return strCat("Shows a list of available ", configName, ", "
1093  "or get meta information about the selected item.\n");
1094 }
1095 
1096 void ConfigInfo::tabCompletion(vector<string>& tokens) const
1097 {
1098  completeString(tokens, Reactor::getHwConfigs(configName));
1099 }
1100 
1101 
1102 // class RealTimeInfo
1103 
1105  : InfoTopic(openMSXInfoCommand, "realtime")
1106  , reference(Timer::getTime())
1107 {
1108 }
1109 
1111  TclObject& result) const
1112 {
1113  auto delta = Timer::getTime() - reference;
1114  result = delta / 1000000.0;
1115 }
1116 
1117 string RealTimeInfo::help(const vector<string>& /*tokens*/) const
1118 {
1119  return "Returns the time in seconds since openMSX was started.";
1120 }
1121 
1122 
1123 // SoftwareInfoTopic
1124 
1126  : InfoTopic(openMSXInfoCommand, "software")
1127  , reactor(reactor_)
1128 {
1129 }
1130 
1132  span<const TclObject> tokens, TclObject& result) const
1133 {
1134  if (tokens.size() != 3) {
1135  throw CommandException("Wrong number of parameters");
1136  }
1137 
1138  Sha1Sum sha1sum = Sha1Sum(tokens[2].getString());
1139  auto& romDatabase = reactor.getSoftwareDatabase();
1140  const RomInfo* romInfo = romDatabase.fetchRomInfo(sha1sum);
1141  if (!romInfo) {
1142  // no match found
1143  throw CommandException(
1144  "Software with sha1sum ", sha1sum.toString(), " not found");
1145  }
1146 
1147  const char* bufStart = romDatabase.getBufferStart();
1148  result.addDictKeyValues("title", romInfo->getTitle(bufStart),
1149  "year", romInfo->getYear(bufStart),
1150  "company", romInfo->getCompany(bufStart),
1151  "country", romInfo->getCountry(bufStart),
1152  "orig_type", romInfo->getOrigType(bufStart),
1153  "remark", romInfo->getRemark(bufStart),
1154  "original", romInfo->getOriginal(),
1155  "mapper_type_name", RomInfo::romTypeToName(romInfo->getRomType()),
1156  "genmsxid", romInfo->getGenMSXid());
1157 }
1158 
1159 string SoftwareInfoTopic::help(const vector<string>& /*tokens*/) const
1160 {
1161  return "Returns information about the software "
1162  "given its sha1sum, in a paired list.";
1163 }
1164 
1165 } // namespace openmsx
ActivateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:840
void execute(span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1110
string help(const vector< string > &tokens) const override
Print help for this topic.
Definition: Reactor.cc:1090
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:312
Contains the main loop of openMSX.
Definition: Reactor.hh:66
GlobalCommandController & getGlobalCommandController() const
Definition: Command.cc:30
bool isRegularFile(const Stat &st)
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:806
string join(string_view part1, string_view part2)
Join two paths.
static XMLElement loadConfig(string_view type, string_view name)
const std::string & getMessage() const &
Definition: MSXException.hh:23
Send when a (new) machine configuration is loaded.
Definition: Event.hh:58
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:715
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:911
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:769
FileContext preferSystemFileContext()
Definition: FileContext.cc:154
ParseStatus getParseStatus() const
void replaceBoard(MSXMotherBoard &oldBoard, Board newBoard)
Definition: Reactor.cc:398
auto unique(ForwardRange &&range)
Definition: ranges.hh:137
FileContext systemFileContext()
Definition: FileContext.cc:148
ALWAYS_INLINE void serialize(const char *tag, const T &t, Args &&...args)
Definition: serialize.hh:896
string_view getRemark(const char *buf) const
Definition: RomInfo.hh:47
Definition: span.hh:34
std::unique_ptr< MSXMotherBoard > Board
Definition: Reactor.hh:110
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:777
void checkNumArgs(span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:180
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:754
MachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:688
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:936
ALWAYS_INLINE void serialize(const char *tag, T &t, Args &&...args)
Definition: serialize.hh:967
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension)
Gets the next numbered file name with the specified prefix in the specified directory, with the specified extension.
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:665
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:1000
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1036
RestoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:929
bool ends_with(string_view x) const
Definition: string_view.cc:126
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:992
static std::vector< std::string > getHwConfigs(string_view type)
Definition: Reactor.cc:324
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:191
CommandController & getCommandController()
Definition: Reactor.cc:314
string_view getOrigType(const char *buf) const
Definition: RomInfo.hh:44
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:366
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:138
static string_view romTypeToName(RomType type)
Definition: RomInfo.cc:194
RomDatabase & getSoftwareDatabase()
Definition: Reactor.cc:296
DeleteMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:792
string_view getCountry(const char *buf) const
Definition: RomInfo.hh:41
string_view getTitle(const char *buf) const
Definition: RomInfo.hh:32
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:832
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:1023
void run(CommandLineParser &parser)
Main loop.
Definition: Reactor.cc:517
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:160
string help(const vector< string > &tokens) const override
Print help for this topic.
Definition: Reactor.cc:1117
void append(Result &)
Definition: stl.hh:354
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1014
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:19
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:847
std::string loadMachine(const std::string &machine)
const Scripts & getStartupScripts() const
RomType getRomType() const
Definition: RomInfo.hh:50
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:720
void sort(RandomAccessRange &&range)
Definition: ranges.hh:35
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:921
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:747
bool getOriginal() const
Definition: RomInfo.hh:51
InfoCommand & getOpenMSXInfoCommand()
Definition: Reactor.cc:319
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:735
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:125
auto count(InputRange &&range, const T &value)
Definition: ranges.hh:209
ConfigInfo(InfoCommand &openMSXInfoCommand, const string &configName)
Definition: Reactor.cc:1055
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:124
std::string toString() const
Definition: utils/sha1.cc:232
ExitCommand(CommandController &commandController, EventDistributor &distributor)
Definition: Reactor.cc:658
string_view getCompany(const char *buf) const
Definition: RomInfo.hh:38
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
const string & getUserOpenMSXDir()
Get the openMSX dir in the user&#39;s home directory.
void unblock()
Definition: Reactor.cc:598
Delete old MSXMotherboards.
Definition: Event.hh:70
bool isDirectory(const Stat &st)
SoftwareInfoTopic(InfoCommand &openMSXInfoCommand, Reactor &reactor)
Definition: Reactor.cc:1125
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:1047
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:695
RealTimeInfo(InfoCommand &openMSXInfoCommand)
Definition: Reactor.cc:1104
std::string str() const
Definition: string_view.cc:12
Interpreter & getInterpreter() const final
Definition: Command.cc:41
void activate(bool active)
TestMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:728
void execute(span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1062
void addDictKeyValues(Args &&... args)
Definition: TclObject.hh:138
std::string help(const std::vector< std::string > &tokens) const override
Print help for this topic.
Definition: Reactor.cc:1159
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:679
GetClipboardCommand(CommandController &commandController)
Definition: Reactor.cc:1009
void enterMainLoop()
Definition: Reactor.cc:501
Simple wrapper around openmdir() / readdir() / closedir() functions.
Definition: ReadDir.hh:15
string_view getYear(const char *buf) const
Definition: RomInfo.hh:35
CliComm & getCliComm()
Definition: Reactor.cc:304
SetClipboardCommand(CommandController &commandController)
Definition: Reactor.cc:1031
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this topic.
Definition: Reactor.cc:1096
Board createEmptyMotherBoard()
Definition: Reactor.cc:393
ITER find_if_unguarded(ITER first, ITER last, PRED pred)
Faster alternative to &#39;find_if&#39; when it&#39;s guaranteed that the predicate will be true for at least one...
Definition: stl.hh:145
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:799
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
void switchMachine(const std::string &machine)
Definition: Reactor.cc:422
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:8
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:135
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
Definition: Command.cc:66
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:826
void addListElements(ITER first, ITER last)
Definition: TclObject.hh:122
struct dirent * getEntry()
Get directory entry for next file.
Definition: ReadDir.cc:17
ListMachinesCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:819
int getGenMSXid() const
Definition: RomInfo.hh:52
StoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:876
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:174
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:868
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))>>
Definition: stl.hh:325
string_view getMachineID() const
Definition: Reactor.cc:372
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:811
auto end(const string_view &x)
Definition: string_view.hh:152
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: Reactor.cc:861
bool isMainThread()
Returns true when called from the main thread.
Definition: Thread.cc:16
CreateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:762
void execute(span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1131
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:883
Interpreter & getInterpreter()
Definition: Reactor.cc:309
void remove_suffix(size_type n)
Definition: string_view.hh:70
#define UNREACHABLE
Definition: unreachable.hh:38
int exitCode
Definition: Reactor.cc:59