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