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