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