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 "Event.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 "ranges.hh"
43 #include "statp.hh"
44 #include "stl.hh"
45 #include "StringOp.hh"
46 #include "unreachable.hh"
47 #include "build-info.hh"
48 #include <cassert>
49 #include <memory>
50 
51 using std::make_unique;
52 using std::string;
53 using std::string_view;
54 using std::vector;
55 
56 namespace openmsx {
57 
58 // global variable to communicate the exit-code from the 'exit' command to main()
59 int exitCode = 0;
60 
61 class ExitCommand final : public Command
62 {
63 public:
64  ExitCommand(CommandController& commandController, EventDistributor& distributor);
65  void execute(span<const TclObject> tokens, TclObject& result) override;
66  [[nodiscard]] string help(span<const TclObject> tokens) const override;
67 private:
68  EventDistributor& distributor;
69 };
70 
71 class MachineCommand final : public Command
72 {
73 public:
74  MachineCommand(CommandController& commandController, Reactor& reactor);
75  void execute(span<const TclObject> tokens, TclObject& result) override;
76  [[nodiscard]] string help(span<const TclObject> tokens) const override;
77  void tabCompletion(vector<string>& tokens) const override;
78 private:
79  Reactor& reactor;
80 };
81 
82 class TestMachineCommand final : public Command
83 {
84 public:
85  TestMachineCommand(CommandController& commandController, Reactor& reactor);
86  void execute(span<const TclObject> tokens, TclObject& result) override;
87  [[nodiscard]] string help(span<const TclObject> tokens) const override;
88  void tabCompletion(vector<string>& tokens) const override;
89 private:
90  Reactor& reactor;
91 };
92 
93 class CreateMachineCommand final : public Command
94 {
95 public:
96  CreateMachineCommand(CommandController& commandController, Reactor& reactor);
97  void execute(span<const TclObject> tokens, TclObject& result) override;
98  [[nodiscard]] string help(span<const TclObject> tokens) const override;
99 private:
100  Reactor& reactor;
101 };
102 
103 class DeleteMachineCommand final : public Command
104 {
105 public:
106  DeleteMachineCommand(CommandController& commandController, Reactor& reactor);
107  void execute(span<const TclObject> tokens, TclObject& result) override;
108  [[nodiscard]] string help(span<const TclObject> tokens) const override;
109  void tabCompletion(vector<string>& tokens) const override;
110 private:
111  Reactor& reactor;
112 };
113 
114 class ListMachinesCommand final : public Command
115 {
116 public:
117  ListMachinesCommand(CommandController& commandController, Reactor& reactor);
118  void execute(span<const TclObject> tokens, TclObject& result) override;
119  [[nodiscard]] string help(span<const TclObject> tokens) const override;
120 private:
121  Reactor& reactor;
122 };
123 
124 class ActivateMachineCommand final : public Command
125 {
126 public:
127  ActivateMachineCommand(CommandController& commandController, Reactor& reactor);
128  void execute(span<const TclObject> tokens, TclObject& result) override;
129  [[nodiscard]] string help(span<const TclObject> tokens) const override;
130  void tabCompletion(vector<string>& tokens) const override;
131 private:
132  Reactor& reactor;
133 };
134 
135 class StoreMachineCommand final : public Command
136 {
137 public:
138  StoreMachineCommand(CommandController& commandController, Reactor& reactor);
139  void execute(span<const TclObject> tokens, TclObject& result) override;
140  [[nodiscard]] string help(span<const TclObject> tokens) const override;
141  void tabCompletion(vector<string>& tokens) const override;
142 private:
143  Reactor& reactor;
144 };
145 
146 class RestoreMachineCommand final : public Command
147 {
148 public:
149  RestoreMachineCommand(CommandController& commandController, Reactor& reactor);
150  void execute(span<const TclObject> tokens, TclObject& result) override;
151  [[nodiscard]] string help(span<const TclObject> tokens) const override;
152  void tabCompletion(vector<string>& tokens) const override;
153 private:
154  Reactor& reactor;
155 };
156 
157 class GetClipboardCommand final : public Command
158 {
159 public:
160  GetClipboardCommand(CommandController& commandController, Reactor& reactor);
161  void execute(span<const TclObject> tokens, TclObject& result) override;
162  [[nodiscard]] string help(span<const TclObject> tokens) const override;
163 private:
164  Reactor& reactor;
165 };
166 
167 class SetClipboardCommand final : public Command
168 {
169 public:
170  SetClipboardCommand(CommandController& commandController, Reactor& reactor);
171  void execute(span<const TclObject> tokens, TclObject& result) override;
172  [[nodiscard]] string help(span<const TclObject> tokens) const override;
173 private:
174  Reactor& reactor;
175 };
176 
177 class ConfigInfo final : public InfoTopic
178 {
179 public:
180  ConfigInfo(InfoCommand& openMSXInfoCommand, const string& configName);
181  void execute(span<const TclObject> tokens,
182  TclObject& result) const override;
183  [[nodiscard]] string help(span<const TclObject> tokens) const override;
184  void tabCompletion(vector<string>& tokens) const override;
185 private:
186  const string configName;
187 };
188 
189 class RealTimeInfo final : public InfoTopic
190 {
191 public:
192  explicit RealTimeInfo(InfoCommand& openMSXInfoCommand);
193  void execute(span<const TclObject> tokens,
194  TclObject& result) const override;
195  [[nodiscard]] string help(span<const TclObject> tokens) const override;
196 private:
197  const uint64_t reference;
198 };
199 
201 {
202 public:
203  SoftwareInfoTopic(InfoCommand& openMSXInfoCommand, Reactor& reactor);
204  void execute(span<const TclObject> tokens,
205  TclObject& result) const override;
206  [[nodiscard]] std::string help(span<const TclObject> tokens) const override;
207 private:
208  Reactor& reactor;
209 };
210 
211 
212 Reactor::Reactor() = default;
213 
215 {
216  rtScheduler = make_unique<RTScheduler>();
217  eventDistributor = make_unique<EventDistributor>(*this);
218  globalCliComm = make_unique<GlobalCliComm>();
219  globalCommandController = make_unique<GlobalCommandController>(
220  *eventDistributor, *globalCliComm, *this);
221  globalSettings = make_unique<GlobalSettings>(
222  *globalCommandController);
223  inputEventGenerator = make_unique<InputEventGenerator>(
224  *globalCommandController, *eventDistributor, *globalSettings);
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, *this);
258  setClipboardCommand = make_unique<SetClipboardCommand>(
259  *globalCommandController, *this);
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>(
267  softwareInfoTopic = make_unique<SoftwareInfoTopic>(
268  getOpenMSXInfoCommand(), *this);
269  tclCallbackMessages = make_unique<TclCallbackMessages>(
270  *globalCliComm, *globalCommandController);
271 
272  createMachineSetting();
273 
275 
276  eventDistributor->registerEventListener(EventType::QUIT, *this);
277 #if PLATFORM_ANDROID
278  eventDistributor->registerEventListener(EventType::FOCUS, *this);
279 #endif
280  isInit = true;
281 }
282 
284 {
285  if (!isInit) return;
286  deleteBoard(activeBoard);
287 
288  eventDistributor->unregisterEventListener(EventType::QUIT, *this);
289 #if PLATFORM_ANDROID
290  eventDistributor->unregisterEventListener(EventType::FOCUS, *this);
291 #endif
292 
294 }
295 
297 {
298  if (!mixer) {
299  mixer = make_unique<Mixer>(*this, *globalCommandController);
300  }
301  return *mixer;
302 }
303 
305 {
306  if (!softwareDatabase) {
307  softwareDatabase = make_unique<RomDatabase>(*globalCliComm);
308  }
309  return *softwareDatabase;
310 }
311 
313 {
314  return *globalCliComm;
315 }
316 
318 {
320 }
321 
323 {
324  return *globalCommandController;
325 }
326 
328 {
329  return globalCommandController->getOpenMSXInfoCommand();
330 }
331 
332 vector<string> Reactor::getHwConfigs(string_view type)
333 {
334  vector<string> result;
335  for (const auto& p : systemFileContext().getPaths()) {
336  auto fileAction = [&](const std::string& /*path*/, std::string_view name) {
337  if (StringOp::endsWith(name, ".xml")) {
338  name.remove_suffix(4);
339  result.emplace_back(name);
340  }
341  };
342  auto dirAction = [&](std::string& path, std::string_view name) {
343  auto size = path.size();
344  path += "/hardwareconfig.xml";
345  if (FileOperations::isRegularFile(path)) {
346  result.emplace_back(name);
347  }
348  path.resize(size);
349  };
350  foreach_file_and_directory(FileOperations::join(p, type), fileAction, dirAction);
351  }
352  // remove duplicates
353  ranges::sort(result);
354  result.erase(ranges::unique(result), end(result));
355  return result;
356 }
357 
358 void Reactor::createMachineSetting()
359 {
360  auto names = getHwConfigs("machines");
361  EnumSetting<int>::Map machines; // int's are unique dummy values
362  machines.reserve(names.size() + 1);
363  int count = 1;
364  append(machines, view::transform(names,
365  [&](auto& name) { return EnumSettingBase::MapEntry(std::move(name), count++); }));
366  machines.emplace_back("C-BIOS_MSX2+", 0); // default machine
367 
368  machineSetting = make_unique<EnumSetting<int>>(
369  *globalCommandController, "default_machine",
370  "default machine (takes effect next time openMSX is started)",
371  0, std::move(machines));
372 }
373 
375 {
376  assert(Thread::isMainThread());
377  return activeBoard.get();
378 }
379 
380 string_view Reactor::getMachineID() const
381 {
382  return activeBoard ? activeBoard->getMachineID() : string_view{};
383 }
384 
385 Reactor::Board 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 std::make_shared<MSXMotherBoard>(*this);
398 }
399 
400 void Reactor::replaceBoard(MSXMotherBoard& oldBoard_, Board newBoard)
401 {
402  assert(Thread::isMainThread());
403 
404  // Add new board.
405  boards.push_back(newBoard);
406 
407  // Lookup old board (it must be present).
408  auto it = find_unguarded(boards, &oldBoard_,
409  [](auto& b) { return b.get(); });
410 
411  // If the old board was the active board, then activate the new board
412  if (*it == activeBoard) {
413  switchBoard(newBoard);
414  }
415 
416  // Remove the old board.
417  move_pop_back(boards, it);
418 }
419 
420 void Reactor::switchMachine(const string& machine)
421 {
422  if (!display) {
423  display = make_unique<Display>(*this);
424  // TODO: Currently it is not possible to move this call into the
425  // constructor of Display because the call to createVideoSystem()
426  // indirectly calls Reactor.getDisplay().
427  display->createVideoSystem();
428  }
429 
430  // create+load new machine
431  // switch to new machine
432  // delete old active machine
433 
434  assert(Thread::isMainThread());
435  // Note: loadMachine can throw an exception and in that case the
436  // motherboard must be considered as not created at all.
437  auto newBoard = createEmptyMotherBoard();
438  newBoard->loadMachine(machine);
439  boards.push_back(newBoard);
440 
441  auto oldBoard = activeBoard;
442  switchBoard(newBoard);
443  deleteBoard(oldBoard);
444 }
445 
446 void Reactor::switchBoard(Board newBoard)
447 {
448  assert(Thread::isMainThread());
449  assert(!newBoard || contains(boards, newBoard));
450  assert(!activeBoard || contains(boards, activeBoard));
451  if (activeBoard) {
452  activeBoard->activate(false);
453  }
454  {
455  // Don't hold the lock for longer than the actual switch.
456  // In the past we had a potential for deadlocks here, because
457  // (indirectly) the code below still acquires other locks.
458  std::lock_guard<std::mutex> lock(mbMutex);
459  activeBoard = newBoard;
460  }
461  eventDistributor->distributeEvent(
462  Event::create<MachineLoadedEvent>());
463  globalCliComm->update(CliComm::HARDWARE, getMachineID(), "select");
464  if (activeBoard) {
465  activeBoard->activate(true);
466  }
467 }
468 
469 void Reactor::deleteBoard(Board board)
470 {
471  // Note: pass 'board' by-value to keep the parameter from changing
472  // after the call to switchBoard(). switchBoard() changes the
473  // 'activeBoard' member variable, so the 'board' parameter would change
474  // if it were passed by reference to this method (AFAICS this only
475  // happens in ~Reactor()).
476  assert(Thread::isMainThread());
477  if (!board) return;
478 
479  if (board == activeBoard) {
480  // delete active board -> there is no active board anymore
481  switchBoard(nullptr);
482  }
483  auto it = rfind_unguarded(boards, board);
484  move_pop_back(boards, it);
485 }
486 
488 {
489  // Note: this method can get called from different threads
490  if (Thread::isMainThread()) {
491  // Don't take lock in main thread to avoid recursive locking.
492  if (activeBoard) {
493  activeBoard->exitCPULoopSync();
494  }
495  } else {
496  std::lock_guard<std::mutex> lock(mbMutex);
497  if (activeBoard) {
498  activeBoard->exitCPULoopAsync();
499  }
500  }
501 }
502 
504 {
505  auto& commandController = *globalCommandController;
506 
507  // execute init.tcl
508  try {
509  commandController.source(
510  preferSystemFileContext().resolve("init.tcl"));
511  } catch (FileException&) {
512  // no init.tcl, ignore
513  }
514 
515  // execute startup scripts
516  for (const auto& s : parser.getStartupScripts()) {
517  try {
518  commandController.source(userFileContext().resolve(s));
519  } catch (FileException& e) {
520  throw FatalError("Couldn't execute script: ",
521  e.getMessage());
522  }
523  }
524  for (const auto& cmd : parser.getStartupCommands()) {
525  try {
526  commandController.executeCommand(cmd);
527  } catch (CommandException& e) {
528  throw FatalError("Couldn't execute command: ", cmd,
529  '\n', e.getMessage());
530  }
531  }
532 
533  // At this point openmsx is fully started, it's OK now to start
534  // accepting external commands
536 
537  // Run
538  if (parser.getParseStatus() == CommandLineParser::RUN) {
539  // don't use Tcl to power up the machine, we cannot pass
540  // exceptions through Tcl and ADVRAM might throw in its
541  // powerUp() method. Solution is to implement dependencies
542  // between devices so ADVRAM can check the error condition
543  // in its constructor
544  //commandController.executeCommand("set power on");
545  if (activeBoard) {
546  activeBoard->powerUp();
547  }
548  }
549 
550  while (doOneIteration()) {
551  // nothing
552  }
553 }
554 
555 bool Reactor::doOneIteration()
556 {
557  eventDistributor->deliverEvents();
558  bool blocked = (blockedCounter > 0) || !activeBoard;
559  if (!blocked) {
560  // copy shared_ptr to keep Board alive (e.g. in case of Tcl
561  // callbacks)
562  auto copy = activeBoard;
563  blocked = !copy->execute();
564  }
565  if (blocked) {
566  // At first sight a better alternative is to use the
567  // SDL_WaitEvent() function. Though when inspecting
568  // the implementation of that function, it turns out
569  // to also use a sleep/poll loop, with even shorter
570  // sleep periods as we use here. Maybe in future
571  // SDL implementations this will be improved.
572  eventDistributor->sleep(20 * 1000);
573  }
574  return running;
575 }
576 
577 void Reactor::unpause()
578 {
579  if (paused) {
580  paused = false;
581  globalCliComm->update(CliComm::STATUS, "paused", "false");
582  unblock();
583  }
584 }
585 
586 void Reactor::pause()
587 {
588  if (!paused) {
589  paused = true;
590  globalCliComm->update(CliComm::STATUS, "paused", "true");
591  block();
592  }
593 }
594 
596 {
597  ++blockedCounter;
598  enterMainLoop();
599  getMixer().mute();
600 }
601 
603 {
604  --blockedCounter;
605  assert(blockedCounter >= 0);
606  getMixer().unmute();
607 }
608 
609 
610 // Observer<Setting>
611 void Reactor::update(const Setting& setting) noexcept
612 {
613  auto& pauseSetting = getGlobalSettings().getPauseSetting();
614  if (&setting == &pauseSetting) {
615  if (pauseSetting.getBoolean()) {
616  pause();
617  } else {
618  unpause();
619  }
620  }
621 }
622 
623 // EventListener
624 int Reactor::signalEvent(const Event& event) noexcept
625 {
627  [&](const QuitEvent& /*e*/) {
628  enterMainLoop();
629  running = false;
630  },
631  [&](const FocusEvent& e) {
632  (void)e;
633 #if PLATFORM_ANDROID
634  // Android SDL port sends a (un)focus event when an app is put in background
635  // by the OS for whatever reason (like an incoming phone call) and all screen
636  // resources are taken away from the app.
637  // In such case the app is supposed to behave as a good citizen
638  // and minimize its resource usage and related battery drain.
639  // The SDL Android port already takes care of halting the Java
640  // part of the sound processing. The Display class makes sure that it wont try
641  // to render anything to the (temporary missing) graphics resources but the
642  // main emulation should also be temporary stopped, in order to minimize CPU usage
643  if (e.getGain()) {
644  unblock();
645  } else {
646  block();
647  }
648 #endif
649  },
650  [](const EventBase /*e*/) {
651  UNREACHABLE; // we didn't subscribe to this event...
652  }
653  }, event);
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(Event::create<QuitEvent>());
679 }
680 
681 string ExitCommand::help(span<const TclObject> /*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 
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 
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 
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 
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 
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 
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 
908  out.serialize("machine", board);
909  out.close();
910  result = filename;
911 }
912 
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  string lastEntry;
949  time_t lastTime = 0;
950  foreach_file(
951  FileOperations::getUserOpenMSXDir() + "/savestates",
952  [&](const string& path, const FileOperations::Stat& st) {
953  time_t modTime = st.st_mtime;
954  if (modTime > lastTime) {
955  filename = path;
956  lastTime = modTime;
957  }
958  });
959  if (filename.empty()) {
960  throw CommandException("Can't find last saved state.");
961  }
962  break;
963  }
964  case 2:
965  filename = FileOperations::expandTilde(string(tokens[1].getString()));
966  break;
967  }
968 
969  //std::cerr << "Loading " << filename << '\n';
970  try {
972  in.serialize("machine", *newBoard);
973  } catch (XMLException& e) {
974  throw CommandException("Cannot load state, bad file format: ",
975  e.getMessage());
976  } catch (MSXException& e) {
977  throw CommandException("Cannot load state: ", e.getMessage());
978  }
979 
980  // Savestate also contains stuff like the keyboard state at the moment
981  // the snapshot was created (this is required for reverse/replay). But
982  // now we want the MSX to see the actual host keyboard state.
983  newBoard->getStateChangeDistributor().stopReplay(newBoard->getCurrentTime());
984 
985  result = newBoard->getMachineID();
986  reactor.boards.push_back(move(newBoard));
987 }
988 
990 {
991  return "restore_machine Load state from last saved state in default directory\n"
992  "restore_machine <filename> Load state from indicated file\n"
993  "\n"
994  "This is a low-level command, the 'loadstate' script is easier to use.";
995 }
996 
997 void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
998 {
999  // TODO: add the default files (state files in user's savestates dir)
1000  completeFileName(tokens, userFileContext());
1001 }
1002 
1003 
1004 // class GetClipboardCommand
1005 
1007  CommandController& commandController_, Reactor& reactor_)
1008  : Command(commandController_, "get_clipboard_text")
1009  , reactor(reactor_)
1010 {
1011  // Note: cannot yet call getReactor().getDisplay() (e.g. to cache it)
1012  // display may not yet be initialized.
1013 }
1014 
1016 {
1017  checkNumArgs(tokens, 1, Prefix{1}, nullptr);
1018  result = reactor.getDisplay().getVideoSystem().getClipboardText();
1019 }
1020 
1022 {
1023  return "Returns the (text) content of the clipboard as a string.";
1024 }
1025 
1026 
1027 // class SetClipboardCommand
1028 
1030  CommandController& commandController_, Reactor& reactor_)
1031  : Command(commandController_, "set_clipboard_text")
1032  , reactor(reactor_)
1033 {
1034 }
1035 
1037 {
1038  checkNumArgs(tokens, 2, "text");
1039  reactor.getDisplay().getVideoSystem().setClipboardText(tokens[1].getString());
1040 }
1041 
1043 {
1044  return "Send the given string to the clipboard.";
1045 }
1046 
1047 
1048 // class ConfigInfo
1049 
1051  const string& configName_)
1052  : InfoTopic(openMSXInfoCommand, configName_)
1053  , configName(configName_)
1054 {
1055 }
1056 
1058 {
1059  // TODO make meta info available through this info topic
1060  switch (tokens.size()) {
1061  case 2: {
1062  result.addListElements(Reactor::getHwConfigs(configName));
1063  break;
1064  }
1065  case 3: {
1066  try {
1067  char allocBuffer[8192]; // tweak
1068  XMLDocument doc{allocBuffer, sizeof(allocBuffer)};
1070  doc, configName, tokens[2].getString());
1071  if (auto* info = doc.getRoot()->findChild("info")) {
1072  for (const auto& c : info->getChildren()) {
1073  result.addDictKeyValue(c.getName(), c.getData());
1074  }
1075  }
1076  } catch (MSXException& e) {
1077  throw CommandException(
1078  "Couldn't get config info: ", e.getMessage());
1079  }
1080  break;
1081  }
1082  default:
1083  throw CommandException("Too many parameters");
1084  }
1085 }
1086 
1087 string ConfigInfo::help(span<const TclObject> /*tokens*/) const
1088 {
1089  return strCat("Shows a list of available ", configName, ", "
1090  "or get meta information about the selected item.\n");
1091 }
1092 
1093 void ConfigInfo::tabCompletion(vector<string>& tokens) const
1094 {
1095  completeString(tokens, Reactor::getHwConfigs(configName));
1096 }
1097 
1098 
1099 // class RealTimeInfo
1100 
1102  : InfoTopic(openMSXInfoCommand, "realtime")
1103  , reference(Timer::getTime())
1104 {
1105 }
1106 
1108  TclObject& result) const
1109 {
1110  auto delta = Timer::getTime() - reference;
1111  result = delta / 1000000.0;
1112 }
1113 
1115 {
1116  return "Returns the time in seconds since openMSX was started.";
1117 }
1118 
1119 
1120 // SoftwareInfoTopic
1121 
1123  : InfoTopic(openMSXInfoCommand, "software")
1124  , reactor(reactor_)
1125 {
1126 }
1127 
1129  span<const TclObject> tokens, TclObject& result) const
1130 {
1131  if (tokens.size() != 3) {
1132  throw CommandException("Wrong number of parameters");
1133  }
1134 
1135  Sha1Sum sha1sum(tokens[2].getString());
1136  auto& romDatabase = reactor.getSoftwareDatabase();
1137  const RomInfo* romInfo = romDatabase.fetchRomInfo(sha1sum);
1138  if (!romInfo) {
1139  // no match found
1140  throw CommandException(
1141  "Software with sha1sum ", sha1sum.toString(), " not found");
1142  }
1143 
1144  const char* bufStart = romDatabase.getBufferStart();
1145  result.addDictKeyValues("title", romInfo->getTitle(bufStart),
1146  "year", romInfo->getYear(bufStart),
1147  "company", romInfo->getCompany(bufStart),
1148  "country", romInfo->getCountry(bufStart),
1149  "orig_type", romInfo->getOrigType(bufStart),
1150  "remark", romInfo->getRemark(bufStart),
1151  "original", romInfo->getOriginal(),
1152  "mapper_type_name", RomInfo::romTypeToName(romInfo->getRomType()),
1153  "genmsxid", romInfo->getGenMSXid());
1154 }
1155 
1157 {
1158  return "Returns information about the software "
1159  "given its sha1sum, in a paired list.";
1160 }
1161 
1162 } // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:27
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:849
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:863
ActivateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:842
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:870
Interpreter & getInterpreter() const final
Definition: Command.cc:38
ParseStatus getParseStatus() const
const auto & getStartupCommands() const
const auto & getStartupScripts() const
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:147
void checkNumArgs(span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:176
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:133
string help(span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1087
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this topic.
Definition: Reactor.cc:1093
void execute(span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1057
ConfigInfo(InfoCommand &openMSXInfoCommand, const string &configName)
Definition: Reactor.cc:1050
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:771
CreateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:764
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:779
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:801
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:808
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:813
DeleteMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:794
VideoSystem & getVideoSystem()
Definition: Display.cc:108
EnumSettingBase::Map Map
Definition: EnumSetting.hh:49
void distributeEvent(Event &&event)
Schedule the given event for delivery.
ExitCommand(CommandController &commandController, EventDistributor &distributor)
Definition: Reactor.cc:660
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:681
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:667
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1015
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1021
GetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:1006
BooleanSetting & getPauseSetting()
static void loadConfig(XMLDocument &doc, std::string_view type, std::string_view name)
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:828
ListMachinesCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:821
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:834
const std::string & getMessage() const &
Definition: MSXException.hh:23
std::string loadMachine(const std::string &machine)
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:717
MachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:690
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:722
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:697
void mute()
This methods (un)mute the sound.
Definition: Mixer.cc:117
void unmute()
Definition: Mixer.cc:124
Contains the main loop of openMSX.
Definition: Reactor.hh:68
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:374
std::shared_ptr< MSXMotherBoard > Board
Definition: Reactor.hh:111
CommandController & getCommandController()
Definition: Reactor.cc:322
void enterMainLoop()
Definition: Reactor.cc:487
InfoCommand & getOpenMSXInfoCommand()
Definition: Reactor.cc:327
void switchMachine(const std::string &machine)
Definition: Reactor.cc:420
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:104
GlobalCliComm & getGlobalCliComm()
Definition: Reactor.hh:83
Display & getDisplay()
Definition: Reactor.hh:86
void unblock()
Definition: Reactor.cc:602
CliComm & getCliComm()
Definition: Reactor.cc:312
void run(CommandLineParser &parser)
Main loop.
Definition: Reactor.cc:503
Board createEmptyMotherBoard()
Definition: Reactor.cc:395
Interpreter & getInterpreter()
Definition: Reactor.cc:317
GlobalCommandController & getGlobalCommandController()
Definition: Reactor.hh:84
void replaceBoard(MSXMotherBoard &oldBoard, Board newBoard)
Definition: Reactor.cc:400
std::string_view getMachineID() const
Definition: Reactor.cc:380
Mixer & getMixer()
Definition: Reactor.cc:296
static std::vector< std::string > getHwConfigs(std::string_view type)
Definition: Reactor.cc:332
RomDatabase & getSoftwareDatabase()
Definition: Reactor.cc:304
void execute(span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1107
RealTimeInfo(InfoCommand &openMSXInfoCommand)
Definition: Reactor.cc:1101
string help(span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1114
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:938
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:997
RestoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:931
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:989
std::string_view getYear(const char *buf) const
Definition: RomInfo.hh:46
bool getOriginal() const
Definition: RomInfo.hh:62
int getGenMSXid() const
Definition: RomInfo.hh:63
std::string_view getTitle(const char *buf) const
Definition: RomInfo.hh:43
std::string_view getCompany(const char *buf) const
Definition: RomInfo.hh:49
std::string_view getRemark(const char *buf) const
Definition: RomInfo.hh:58
static std::string_view romTypeToName(RomType type)
Definition: RomInfo.cc:185
std::string_view getCountry(const char *buf) const
Definition: RomInfo.hh:52
RomType getRomType() const
Definition: RomInfo.hh:61
std::string_view getOrigType(const char *buf) const
Definition: RomInfo.hh:55
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1042
SetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:1029
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1036
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:230
std::string help(span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1156
void execute(span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1128
SoftwareInfoTopic(InfoCommand &openMSXInfoCommand, Reactor &reactor)
Definition: Reactor.cc:1122
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:885
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:923
StoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:878
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:913
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:130
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:143
void addDictKeyValues(Args &&... args)
Definition: TclObject.hh:146
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:756
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:737
TestMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:730
string help(span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:749
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:955
ALWAYS_INLINE void serialize(const char *tag, const T &t, Args &&...args)
Definition: serialize.hh:876
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:39
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:9
const FileContext & systemFileContext()
Definition: FileContext.cc:157
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:653
constexpr const char *const filename
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:173
bool foreach_file(std::string path, FileAction fileAction)
int exitCode
Definition: Reactor.cc:59
const FileContext & preferSystemFileContext()
Definition: FileContext.cc:165
bool foreach_file_and_directory(std::string path, FileAction fileAction, DirAction dirAction)
auto unique(ForwardRange &&range)
Definition: ranges.hh:166
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:178
void sort(RandomAccessRange &&range)
Definition: ranges.hh:33
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:391
ITER find_unguarded(ITER first, ITER last, const VAL &val, Proj proj={})
Faster alternative to 'find' when it's guaranteed that the value will be found (if not the behavior i...
Definition: stl.hh:71
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:133
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:108
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:31
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:84