openMSX
Reactor.cc
Go to the documentation of this file.
1#include "Reactor.hh"
3#include "RTScheduler.hh"
4#include "EventDistributor.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"
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 "narrow.hh"
42#include "ranges.hh"
43#include "serialize.hh"
44#include "stl.hh"
45#include "StringOp.hh"
46#include "unreachable.hh"
47#include "build-info.hh"
48#include <array>
49#include <cassert>
50#include <memory>
51
52using std::make_unique;
53using std::string;
54using std::string_view;
55using std::vector;
56
57namespace openmsx {
58
59// global variable to communicate the exit-code from the 'exit' command to main()
60int exitCode = 0;
61
62class ExitCommand final : public Command
63{
64public:
65 ExitCommand(CommandController& commandController, EventDistributor& distributor);
66 void execute(std::span<const TclObject> tokens, TclObject& result) override;
67 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
68private:
69 EventDistributor& distributor;
70};
71
72class MachineCommand final : public Command
73{
74public:
75 MachineCommand(CommandController& commandController, Reactor& reactor);
76 void execute(std::span<const TclObject> tokens, TclObject& result) override;
77 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
78 void tabCompletion(vector<string>& tokens) const override;
79private:
80 Reactor& reactor;
81};
82
83class TestMachineCommand final : public Command
84{
85public:
86 TestMachineCommand(CommandController& commandController, Reactor& reactor);
87 void execute(std::span<const TclObject> tokens, TclObject& result) override;
88 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
89 void tabCompletion(vector<string>& tokens) const override;
90private:
91 Reactor& reactor;
92};
93
94class CreateMachineCommand final : public Command
95{
96public:
97 CreateMachineCommand(CommandController& commandController, Reactor& reactor);
98 void execute(std::span<const TclObject> tokens, TclObject& result) override;
99 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
100private:
101 Reactor& reactor;
102};
103
104class DeleteMachineCommand final : public Command
105{
106public:
107 DeleteMachineCommand(CommandController& commandController, Reactor& reactor);
108 void execute(std::span<const TclObject> tokens, TclObject& result) override;
109 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
110 void tabCompletion(vector<string>& tokens) const override;
111private:
112 Reactor& reactor;
113};
114
115class ListMachinesCommand final : public Command
116{
117public:
118 ListMachinesCommand(CommandController& commandController, Reactor& reactor);
119 void execute(std::span<const TclObject> tokens, TclObject& result) override;
120 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
121private:
122 Reactor& reactor;
123};
124
125class ActivateMachineCommand final : public Command
126{
127public:
128 ActivateMachineCommand(CommandController& commandController, Reactor& reactor);
129 void execute(std::span<const TclObject> tokens, TclObject& result) override;
130 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
131 void tabCompletion(vector<string>& tokens) const override;
132private:
133 Reactor& reactor;
134};
135
136class StoreMachineCommand final : public Command
137{
138public:
139 StoreMachineCommand(CommandController& commandController, Reactor& reactor);
140 void execute(std::span<const TclObject> tokens, TclObject& result) override;
141 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
142 void tabCompletion(vector<string>& tokens) const override;
143private:
144 Reactor& reactor;
145};
146
147class RestoreMachineCommand final : public Command
148{
149public:
150 RestoreMachineCommand(CommandController& commandController, Reactor& reactor);
151 void execute(std::span<const TclObject> tokens, TclObject& result) override;
152 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
153 void tabCompletion(vector<string>& tokens) const override;
154private:
155 Reactor& reactor;
156};
157
158class GetClipboardCommand final : public Command
159{
160public:
161 GetClipboardCommand(CommandController& commandController, Reactor& reactor);
162 void execute(std::span<const TclObject> tokens, TclObject& result) override;
163 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
164private:
165 Reactor& reactor;
166};
167
168class SetClipboardCommand final : public Command
169{
170public:
171 SetClipboardCommand(CommandController& commandController, Reactor& reactor);
172 void execute(std::span<const TclObject> tokens, TclObject& result) override;
173 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
174private:
175 Reactor& reactor;
176};
177
178class ConfigInfo final : public InfoTopic
179{
180public:
181 ConfigInfo(InfoCommand& openMSXInfoCommand, const string& configName);
182 void execute(std::span<const TclObject> tokens,
183 TclObject& result) const override;
184 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
185 void tabCompletion(vector<string>& tokens) const override;
186private:
187 const string configName;
188};
189
190class RealTimeInfo final : public InfoTopic
191{
192public:
193 explicit RealTimeInfo(InfoCommand& openMSXInfoCommand);
194 void execute(std::span<const TclObject> tokens,
195 TclObject& result) const override;
196 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
197private:
198 const uint64_t reference;
199};
200
202{
203public:
204 SoftwareInfoTopic(InfoCommand& openMSXInfoCommand, Reactor& reactor);
205 void execute(std::span<const TclObject> tokens,
206 TclObject& result) const override;
207 [[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
208private:
209 Reactor& reactor;
210};
211
212
213Reactor::Reactor() = default;
214
216{
217 rtScheduler = make_unique<RTScheduler>();
218 eventDistributor = make_unique<EventDistributor>(*this);
219 globalCliComm = make_unique<GlobalCliComm>();
220 globalCommandController = make_unique<GlobalCommandController>(
221 *eventDistributor, *globalCliComm, *this);
222 globalSettings = make_unique<GlobalSettings>(
223 *globalCommandController);
224 inputEventGenerator = make_unique<InputEventGenerator>(
225 *globalCommandController, *eventDistributor, *globalSettings);
226 diskFactory = make_unique<DiskFactory>(
227 *this);
228 diskManipulator = make_unique<DiskManipulator>(
229 *globalCommandController, *this);
230 virtualDrive = make_unique<DiskChanger>(
231 *this, "virtual_drive");
232 filePool = make_unique<FilePool>(*globalCommandController, *this);
233 userSettings = make_unique<UserSettings>(
234 *globalCommandController);
235 afterCommand = make_unique<AfterCommand>(
236 *this, *eventDistributor, *globalCommandController);
237 exitCommand = make_unique<ExitCommand>(
238 *globalCommandController, *eventDistributor);
239 messageCommand = make_unique<MessageCommand>(
240 *globalCommandController);
241 machineCommand = make_unique<MachineCommand>(
242 *globalCommandController, *this);
243 testMachineCommand = make_unique<TestMachineCommand>(
244 *globalCommandController, *this);
245 createMachineCommand = make_unique<CreateMachineCommand>(
246 *globalCommandController, *this);
247 deleteMachineCommand = make_unique<DeleteMachineCommand>(
248 *globalCommandController, *this);
249 listMachinesCommand = make_unique<ListMachinesCommand>(
250 *globalCommandController, *this);
251 activateMachineCommand = make_unique<ActivateMachineCommand>(
252 *globalCommandController, *this);
253 storeMachineCommand = make_unique<StoreMachineCommand>(
254 *globalCommandController, *this);
255 restoreMachineCommand = make_unique<RestoreMachineCommand>(
256 *globalCommandController, *this);
257 getClipboardCommand = make_unique<GetClipboardCommand>(
258 *globalCommandController, *this);
259 setClipboardCommand = make_unique<SetClipboardCommand>(
260 *globalCommandController, *this);
261 aviRecordCommand = make_unique<AviRecorder>(*this);
262 extensionInfo = make_unique<ConfigInfo>(
263 getOpenMSXInfoCommand(), "extensions");
264 machineInfo = make_unique<ConfigInfo>(
265 getOpenMSXInfoCommand(), "machines");
266 realTimeInfo = make_unique<RealTimeInfo>(
268 softwareInfoTopic = make_unique<SoftwareInfoTopic>(
269 getOpenMSXInfoCommand(), *this);
270 tclCallbackMessages = make_unique<TclCallbackMessages>(
271 *globalCliComm, *globalCommandController);
272
273 createMachineSetting();
274
276
277 eventDistributor->registerEventListener(EventType::QUIT, *this);
278#if PLATFORM_ANDROID
279 eventDistributor->registerEventListener(EventType::FOCUS, *this);
280#endif
281 isInit = true;
282}
283
285{
286 if (!isInit) return;
287 deleteBoard(activeBoard);
288
289 eventDistributor->unregisterEventListener(EventType::QUIT, *this);
290#if PLATFORM_ANDROID
291 eventDistributor->unregisterEventListener(EventType::FOCUS, *this);
292#endif
293
295}
296
298{
299 if (!mixer) {
300 mixer = make_unique<Mixer>(*this, *globalCommandController);
301 }
302 return *mixer;
303}
304
306{
307 if (!softwareDatabase) {
308 softwareDatabase = make_unique<RomDatabase>(*globalCliComm);
309 }
310 return *softwareDatabase;
311}
312
314{
315 return *globalCliComm;
316}
317
319{
321}
322
324{
325 return *globalCommandController;
326}
327
329{
330 return globalCommandController->getOpenMSXInfoCommand();
331}
332
333vector<string> Reactor::getHwConfigs(string_view type)
334{
335 vector<string> result;
336 for (const auto& p : systemFileContext().getPaths()) {
337 auto fileAction = [&](const std::string& /*path*/, std::string_view name) {
338 if (name.ends_with(".xml")) {
339 name.remove_suffix(4);
340 result.emplace_back(name);
341 }
342 };
343 auto dirAction = [&](std::string& path, std::string_view name) {
344 auto size = path.size();
345 path += "/hardwareconfig.xml";
347 result.emplace_back(name);
348 }
349 path.resize(size);
350 };
351 foreach_file_and_directory(FileOperations::join(p, type), fileAction, dirAction);
352 }
353 // remove duplicates
354 ranges::sort(result);
355 result.erase(ranges::unique(result), end(result));
356 return result;
357}
358
359void Reactor::createMachineSetting()
360{
361 auto names = getHwConfigs("machines");
362 EnumSetting<int>::Map machines; // int's are unique dummy values
363 machines.reserve(names.size() + 1);
364 int count = 1;
365 append(machines, view::transform(names,
366 [&](auto& name) { return EnumSettingBase::MapEntry(std::move(name), count++); }));
367 machines.emplace_back("C-BIOS_MSX2+", 0); // default machine
368
369 machineSetting = make_unique<EnumSetting<int>>(
370 *globalCommandController, "default_machine",
371 "default machine (takes effect next time openMSX is started)",
372 0, std::move(machines));
373}
374
376{
377 assert(Thread::isMainThread());
378 return activeBoard.get();
379}
380
381string_view Reactor::getMachineID() const
382{
383 return activeBoard ? activeBoard->getMachineID() : string_view{};
384}
385
386Reactor::Board Reactor::getMachine(string_view machineID) const
387{
388 if (auto it = ranges::find(boards, machineID, &MSXMotherBoard::getMachineID);
389 it != boards.end()) {
390 return *it;
391 }
392 throw CommandException("No machine with ID: ", machineID);
393}
394
396{
397 return std::make_shared<MSXMotherBoard>(*this);
398}
399
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
420void 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
446void 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
469void 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
555bool 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
577void Reactor::unpause()
578{
579 if (paused) {
580 paused = false;
581 globalCliComm->update(CliComm::STATUS, "paused", "false");
582 unblock();
583 }
584}
585
586void Reactor::pause()
587{
588 if (!paused) {
589 paused = true;
590 globalCliComm->update(CliComm::STATUS, "paused", "true");
591 block();
592 }
593}
594
596{
597 ++blockedCounter;
599 getMixer().mute();
600}
601
603{
604 --blockedCounter;
605 assert(blockedCounter >= 0);
606 getMixer().unmute();
607}
608
609
610// Observer<Setting>
611void 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
624int Reactor::signalEvent(const Event& event)
625{
627 [&](const QuitEvent& /*e*/) {
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
667void ExitCommand::execute(std::span<const TclObject> tokens, TclObject& /*result*/)
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
681string ExitCommand::help(std::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
697void MachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
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
717string MachineCommand::help(std::span<const TclObject> /*tokens*/) const
718{
719 return "Switch to a different MSX machine.";
720}
721
722void 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
737void TestMachineCommand::execute(std::span<const TclObject> tokens,
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
749string TestMachineCommand::help(std::span<const TclObject> /*tokens*/) const
750{
751 return "Test the configuration for the given machine. "
752 "Returns an error message explaining why the configuration is "
753 "invalid or an empty string in case of success.";
754}
755
756void 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
771void CreateMachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
772{
773 checkNumArgs(tokens, 1, Prefix{1}, nullptr);
774 auto newBoard = reactor.createEmptyMotherBoard();
775 result = newBoard->getMachineID();
776 reactor.boards.push_back(std::move(newBoard));
777}
778
779string CreateMachineCommand::help(std::span<const TclObject> /*tokens*/) const
780{
781 return "Creates a new (empty) MSX machine. Returns the ID for the new "
782 "machine.\n"
783 "Use 'load_machine' to actually load a machine configuration "
784 "into this new machine.\n"
785 "The main reason create_machine and load_machine are two "
786 "separate commands is that sometimes you already want to know "
787 "the ID of the machine before load_machine starts emitting "
788 "events for this machine.";
789}
790
791
792// class DeleteMachineCommand
793
795 CommandController& commandController_, Reactor& reactor_)
796 : Command(commandController_, "delete_machine")
797 , reactor(reactor_)
798{
799}
800
801void DeleteMachineCommand::execute(std::span<const TclObject> tokens,
802 TclObject& /*result*/)
803{
804 checkNumArgs(tokens, 2, "id");
805 reactor.deleteBoard(reactor.getMachine(tokens[1].getString()));
806}
807
808string DeleteMachineCommand::help(std::span<const TclObject> /*tokens*/) const
809{
810 return "Deletes the given MSX machine.";
811}
812
813void 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
828void ListMachinesCommand::execute(std::span<const TclObject> /*tokens*/,
829 TclObject& result)
830{
831 result.addListElements(reactor.getMachineIDs());
832}
833
834string ListMachinesCommand::help(std::span<const TclObject> /*tokens*/) const
835{
836 return "Returns a list of all machine IDs.";
837}
838
839
840// class ActivateMachineCommand
841
843 CommandController& commandController_, Reactor& reactor_)
844 : Command(commandController_, "activate_machine")
845 , reactor(reactor_)
846{
847}
848
849void ActivateMachineCommand::execute(std::span<const TclObject> tokens,
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
863string ActivateMachineCommand::help(std::span<const TclObject> /*tokens*/) const
864{
865 return "Make another machine the active msx machine.\n"
866 "Or when invoked without arguments, query the ID of the "
867 "active msx machine.";
868}
869
870void 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
885void StoreMachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
886{
887 checkNumArgs(tokens, Between{1, 3}, Prefix{1}, "?id? ?filename?");
888 string filename;
889 string_view machineID;
890 switch (tokens.size()) {
891 case 1:
892 machineID = reactor.getMachineID();
893 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
894 break;
895 case 2:
896 machineID = tokens[1].getString();
897 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
898 break;
899 case 3:
900 machineID = tokens[1].getString();
901 filename = tokens[2].getString();
902 break;
903 }
904
905 auto& board = *reactor.getMachine(machineID);
906
907 XmlOutputArchive out(filename);
908 out.serialize("machine", board);
909 out.close();
910 result = filename;
911}
912
913string StoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
914{
915 return
916 "store_machine Save state of current machine to file \"openmsxNNNN.xml.gz\"\n"
917 "store_machine machineID Save state of machine \"machineID\" to file \"openmsxNNNN.xml.gz\"\n"
918 "store_machine machineID <filename> Save state of machine \"machineID\" to indicated file\n"
919 "\n"
920 "This is a low-level command, the 'savestate' script is easier to use.";
921}
922
923void 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
938void RestoreMachineCommand::execute(std::span<const TclObject> tokens,
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;
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 {
971 XmlInputArchive in(filename);
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(std::move(newBoard));
987}
988
989string RestoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
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
997void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
998{
999 // TODO: add the default files (state files in user's savestates dir)
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) / 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
Definition: Interpreter.cc:28
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:849
string help(std::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
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:147
static void completeString(std::vector< std::string > &tokens, ITER begin, ITER end, bool caseSensitive=true)
Definition: Completer.hh:133
void checkNumArgs(std::span< const TclObject > tokens, unsigned exactly, const char *errMessage) const
Definition: Completer.cc:177
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this topic.
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:779
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:771
CreateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:764
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:801
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
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:808
VideoSystem & getVideoSystem()
Definition: Display.cc:107
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:681
ExitCommand(CommandController &commandController, EventDistributor &distributor)
Definition: Reactor.cc:660
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:667
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:828
ListMachinesCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:821
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:834
std::string loadMachine(const std::string &machine)
std::string_view getMachineID() const
MachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:690
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:697
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:722
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:717
void mute()
This methods (un)mute the sound.
Definition: Mixer.cc:116
void unmute()
Definition: Mixer.cc:123
Contains the main loop of openMSX.
Definition: Reactor.hh:68
GlobalSettings & getGlobalSettings()
Definition: Reactor.hh:104
MSXMotherBoard * getMotherBoard() const
Definition: Reactor.cc:375
std::shared_ptr< MSXMotherBoard > Board
Definition: Reactor.hh:111
CommandController & getCommandController()
Definition: Reactor.cc:323
void enterMainLoop()
Definition: Reactor.cc:487
GlobalCommandController & getGlobalCommandController()
Definition: Reactor.hh:84
InfoCommand & getOpenMSXInfoCommand()
Definition: Reactor.cc:328
void switchMachine(const std::string &machine)
Definition: Reactor.cc:420
Display & getDisplay()
Definition: Reactor.hh:86
void unblock()
Definition: Reactor.cc:602
CliComm & getCliComm()
Definition: Reactor.cc:313
void run(CommandLineParser &parser)
Main loop.
Definition: Reactor.cc:503
GlobalCliComm & getGlobalCliComm()
Definition: Reactor.hh:83
Board createEmptyMotherBoard()
Definition: Reactor.cc:395
Interpreter & getInterpreter()
Definition: Reactor.cc:318
void replaceBoard(MSXMotherBoard &oldBoard, Board newBoard)
Definition: Reactor.cc:400
std::string_view getMachineID() const
Definition: Reactor.cc:381
Mixer & getMixer()
Definition: Reactor.cc:297
static std::vector< std::string > getHwConfigs(std::string_view type)
Definition: Reactor.cc:333
RomDatabase & getSoftwareDatabase()
Definition: Reactor.cc:305
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:989
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
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:938
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: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
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:23
std::string toString() const
Definition: utils/sha1.cc:230
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:923
StoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:878
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:913
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:885
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:129
void addDictKeyValue(const Key &key, const Value &value)
Definition: TclObject.hh:142
void addDictKeyValues(Args &&... args)
Definition: TclObject.hh:145
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:756
TestMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:730
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:737
string help(std::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:970
ALWAYS_INLINE void serialize(const char *tag, const T &t, Args &&...args)
Definition: serialize.hh:891
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition: lz4.cc:147
constexpr double e
Definition: Math.hh:20
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:155
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:652
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:171
bool foreach_file(std::string path, FileAction fileAction)
int exitCode
Definition: Reactor.cc:60
const FileContext & preferSystemFileContext()
Definition: FileContext.cc:163
bool foreach_file_and_directory(std::string path, FileAction fileAction, DirAction dirAction)
auto unique(ForwardRange &&range)
Definition: ranges.hh:204
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:232
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:160
constexpr void sort(RandomAccessRange &&range)
Definition: ranges.hh:49
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:458
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:63
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:125
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:100
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:23
std::string strCat(Ts &&...ts)
Definition: strCat.hh:542
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto end(const zstring_view &x)