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 fullyStarted = true;
534
535 // At this point openmsx is fully started, it's OK now to start
536 // accepting external commands
538
539 // ...and re-emit any postponed message callbacks now that the scripts
540 // are loaded
541 tclCallbackMessages->redoPostponedCallbacks();
542
543 // Run
544 if (parser.getParseStatus() == CommandLineParser::RUN) {
545 // don't use Tcl to power up the machine, we cannot pass
546 // exceptions through Tcl and ADVRAM might throw in its
547 // powerUp() method. Solution is to implement dependencies
548 // between devices so ADVRAM can check the error condition
549 // in its constructor
550 //commandController.executeCommand("set power on");
551 if (activeBoard) {
552 activeBoard->powerUp();
553 }
554 }
555
556 while (doOneIteration()) {
557 // nothing
558 }
559}
560
561bool Reactor::doOneIteration()
562{
563 eventDistributor->deliverEvents();
564 bool blocked = (blockedCounter > 0) || !activeBoard;
565 if (!blocked) {
566 // copy shared_ptr to keep Board alive (e.g. in case of Tcl
567 // callbacks)
568 auto copy = activeBoard;
569 blocked = !copy->execute();
570 }
571 if (blocked) {
572 // At first sight a better alternative is to use the
573 // SDL_WaitEvent() function. Though when inspecting
574 // the implementation of that function, it turns out
575 // to also use a sleep/poll loop, with even shorter
576 // sleep periods as we use here. Maybe in future
577 // SDL implementations this will be improved.
578 eventDistributor->sleep(20 * 1000);
579 }
580 return running;
581}
582
583void Reactor::unpause()
584{
585 if (paused) {
586 paused = false;
587 globalCliComm->update(CliComm::STATUS, "paused", "false");
588 unblock();
589 }
590}
591
592void Reactor::pause()
593{
594 if (!paused) {
595 paused = true;
596 globalCliComm->update(CliComm::STATUS, "paused", "true");
597 block();
598 }
599}
600
602{
603 ++blockedCounter;
605 getMixer().mute();
606}
607
609{
610 --blockedCounter;
611 assert(blockedCounter >= 0);
612 getMixer().unmute();
613}
614
615
616// Observer<Setting>
617void Reactor::update(const Setting& setting) noexcept
618{
619 auto& pauseSetting = getGlobalSettings().getPauseSetting();
620 if (&setting == &pauseSetting) {
621 if (pauseSetting.getBoolean()) {
622 pause();
623 } else {
624 unpause();
625 }
626 }
627}
628
629// EventListener
630int Reactor::signalEvent(const Event& event)
631{
633 [&](const QuitEvent& /*e*/) {
635 running = false;
636 },
637 [&](const FocusEvent& e) {
638 (void)e;
639#if PLATFORM_ANDROID
640 // Android SDL port sends a (un)focus event when an app is put in background
641 // by the OS for whatever reason (like an incoming phone call) and all screen
642 // resources are taken away from the app.
643 // In such case the app is supposed to behave as a good citizen
644 // and minimize its resource usage and related battery drain.
645 // The SDL Android port already takes care of halting the Java
646 // part of the sound processing. The Display class makes sure that it wont try
647 // to render anything to the (temporary missing) graphics resources but the
648 // main emulation should also be temporary stopped, in order to minimize CPU usage
649 if (e.getGain()) {
650 unblock();
651 } else {
652 block();
653 }
654#endif
655 },
656 [](const EventBase /*e*/) {
657 UNREACHABLE; // we didn't subscribe to this event...
658 }
659 }, event);
660 return 0;
661}
662
663
664// class ExitCommand
665
667 EventDistributor& distributor_)
668 : Command(commandController_, "exit")
669 , distributor(distributor_)
670{
671}
672
673void ExitCommand::execute(std::span<const TclObject> tokens, TclObject& /*result*/)
674{
675 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?exitcode?");
676 switch (tokens.size()) {
677 case 1:
678 exitCode = 0;
679 break;
680 case 2:
681 exitCode = tokens[1].getInt(getInterpreter());
682 break;
683 }
684 distributor.distributeEvent(Event::create<QuitEvent>());
685}
686
687string ExitCommand::help(std::span<const TclObject> /*tokens*/) const
688{
689 return "Use this command to stop the emulator.\n"
690 "Optionally you can pass an exit-code.\n";
691}
692
693
694// class MachineCommand
695
697 Reactor& reactor_)
698 : Command(commandController_, "machine")
699 , reactor(reactor_)
700{
701}
702
703void MachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
704{
705 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?machinetype?");
706 switch (tokens.size()) {
707 case 1: // get current machine
708 // nothing
709 break;
710 case 2:
711 try {
712 reactor.switchMachine(string(tokens[1].getString()));
713 } catch (MSXException& e) {
714 throw CommandException("Machine switching failed: ",
715 e.getMessage());
716 }
717 break;
718 }
719 // Always return machineID (of current or of new machine).
720 result = reactor.getMachineID();
721}
722
723string MachineCommand::help(std::span<const TclObject> /*tokens*/) const
724{
725 return "Switch to a different MSX machine.";
726}
727
728void MachineCommand::tabCompletion(vector<string>& tokens) const
729{
730 completeString(tokens, Reactor::getHwConfigs("machines"));
731}
732
733
734// class TestMachineCommand
735
737 Reactor& reactor_)
738 : Command(commandController_, "test_machine")
739 , reactor(reactor_)
740{
741}
742
743void TestMachineCommand::execute(std::span<const TclObject> tokens,
744 TclObject& result)
745{
746 checkNumArgs(tokens, 2, "machinetype");
747 try {
748 MSXMotherBoard mb(reactor);
749 mb.loadMachine(string(tokens[1].getString()));
750 } catch (MSXException& e) {
751 result = e.getMessage(); // error
752 }
753}
754
755string TestMachineCommand::help(std::span<const TclObject> /*tokens*/) const
756{
757 return "Test the configuration for the given machine. "
758 "Returns an error message explaining why the configuration is "
759 "invalid or an empty string in case of success.";
760}
761
762void TestMachineCommand::tabCompletion(vector<string>& tokens) const
763{
764 completeString(tokens, Reactor::getHwConfigs("machines"));
765}
766
767
768// class CreateMachineCommand
769
771 CommandController& commandController_, Reactor& reactor_)
772 : Command(commandController_, "create_machine")
773 , reactor(reactor_)
774{
775}
776
777void CreateMachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
778{
779 checkNumArgs(tokens, 1, Prefix{1}, nullptr);
780 auto newBoard = reactor.createEmptyMotherBoard();
781 result = newBoard->getMachineID();
782 reactor.boards.push_back(std::move(newBoard));
783}
784
785string CreateMachineCommand::help(std::span<const TclObject> /*tokens*/) const
786{
787 return "Creates a new (empty) MSX machine. Returns the ID for the new "
788 "machine.\n"
789 "Use 'load_machine' to actually load a machine configuration "
790 "into this new machine.\n"
791 "The main reason create_machine and load_machine are two "
792 "separate commands is that sometimes you already want to know "
793 "the ID of the machine before load_machine starts emitting "
794 "events for this machine.";
795}
796
797
798// class DeleteMachineCommand
799
801 CommandController& commandController_, Reactor& reactor_)
802 : Command(commandController_, "delete_machine")
803 , reactor(reactor_)
804{
805}
806
807void DeleteMachineCommand::execute(std::span<const TclObject> tokens,
808 TclObject& /*result*/)
809{
810 checkNumArgs(tokens, 2, "id");
811 reactor.deleteBoard(reactor.getMachine(tokens[1].getString()));
812}
813
814string DeleteMachineCommand::help(std::span<const TclObject> /*tokens*/) const
815{
816 return "Deletes the given MSX machine.";
817}
818
819void DeleteMachineCommand::tabCompletion(vector<string>& tokens) const
820{
821 completeString(tokens, reactor.getMachineIDs());
822}
823
824
825// class ListMachinesCommand
826
828 CommandController& commandController_, Reactor& reactor_)
829 : Command(commandController_, "list_machines")
830 , reactor(reactor_)
831{
832}
833
834void ListMachinesCommand::execute(std::span<const TclObject> /*tokens*/,
835 TclObject& result)
836{
837 result.addListElements(reactor.getMachineIDs());
838}
839
840string ListMachinesCommand::help(std::span<const TclObject> /*tokens*/) const
841{
842 return "Returns a list of all machine IDs.";
843}
844
845
846// class ActivateMachineCommand
847
849 CommandController& commandController_, Reactor& reactor_)
850 : Command(commandController_, "activate_machine")
851 , reactor(reactor_)
852{
853}
854
855void ActivateMachineCommand::execute(std::span<const TclObject> tokens,
856 TclObject& result)
857{
858 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "id");
859 switch (tokens.size()) {
860 case 1:
861 break;
862 case 2:
863 reactor.switchBoard(reactor.getMachine(tokens[1].getString()));
864 break;
865 }
866 result = reactor.getMachineID();
867}
868
869string ActivateMachineCommand::help(std::span<const TclObject> /*tokens*/) const
870{
871 return "Make another machine the active msx machine.\n"
872 "Or when invoked without arguments, query the ID of the "
873 "active msx machine.";
874}
875
876void ActivateMachineCommand::tabCompletion(vector<string>& tokens) const
877{
878 completeString(tokens, reactor.getMachineIDs());
879}
880
881
882// class StoreMachineCommand
883
885 CommandController& commandController_, Reactor& reactor_)
886 : Command(commandController_, "store_machine")
887 , reactor(reactor_)
888{
889}
890
891void StoreMachineCommand::execute(std::span<const TclObject> tokens, TclObject& result)
892{
893 checkNumArgs(tokens, Between{1, 3}, Prefix{1}, "?id? ?filename?");
894 string filename;
895 string_view machineID;
896 switch (tokens.size()) {
897 case 1:
898 machineID = reactor.getMachineID();
899 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
900 break;
901 case 2:
902 machineID = tokens[1].getString();
903 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
904 break;
905 case 3:
906 machineID = tokens[1].getString();
907 filename = tokens[2].getString();
908 break;
909 }
910
911 auto& board = *reactor.getMachine(machineID);
912
913 XmlOutputArchive out(filename);
914 out.serialize("machine", board);
915 out.close();
916 result = filename;
917}
918
919string StoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
920{
921 return
922 "store_machine Save state of current machine to file \"openmsxNNNN.xml.gz\"\n"
923 "store_machine machineID Save state of machine \"machineID\" to file \"openmsxNNNN.xml.gz\"\n"
924 "store_machine machineID <filename> Save state of machine \"machineID\" to indicated file\n"
925 "\n"
926 "This is a low-level command, the 'savestate' script is easier to use.";
927}
928
929void StoreMachineCommand::tabCompletion(vector<string>& tokens) const
930{
931 completeString(tokens, reactor.getMachineIDs());
932}
933
934
935// class RestoreMachineCommand
936
938 CommandController& commandController_, Reactor& reactor_)
939 : Command(commandController_, "restore_machine")
940 , reactor(reactor_)
941{
942}
943
944void RestoreMachineCommand::execute(std::span<const TclObject> tokens,
945 TclObject& result)
946{
947 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
948 auto newBoard = reactor.createEmptyMotherBoard();
949
950 string filename;
951 switch (tokens.size()) {
952 case 1: {
953 // load last saved entry
954 string lastEntry;
955 time_t lastTime = 0;
957 FileOperations::getUserOpenMSXDir() + "/savestates",
958 [&](const string& path, const FileOperations::Stat& st) {
959 time_t modTime = st.st_mtime;
960 if (modTime > lastTime) {
961 filename = path;
962 lastTime = modTime;
963 }
964 });
965 if (filename.empty()) {
966 throw CommandException("Can't find last saved state.");
967 }
968 break;
969 }
970 case 2:
971 filename = FileOperations::expandTilde(string(tokens[1].getString()));
972 break;
973 }
974
975 //std::cerr << "Loading " << filename << '\n';
976 try {
977 XmlInputArchive in(filename);
978 in.serialize("machine", *newBoard);
979 } catch (XMLException& e) {
980 throw CommandException("Cannot load state, bad file format: ",
981 e.getMessage());
982 } catch (MSXException& e) {
983 throw CommandException("Cannot load state: ", e.getMessage());
984 }
985
986 // Savestate also contains stuff like the keyboard state at the moment
987 // the snapshot was created (this is required for reverse/replay). But
988 // now we want the MSX to see the actual host keyboard state.
989 newBoard->getStateChangeDistributor().stopReplay(newBoard->getCurrentTime());
990
991 result = newBoard->getMachineID();
992 reactor.boards.push_back(std::move(newBoard));
993}
994
995string RestoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
996{
997 return "restore_machine Load state from last saved state in default directory\n"
998 "restore_machine <filename> Load state from indicated file\n"
999 "\n"
1000 "This is a low-level command, the 'loadstate' script is easier to use.";
1001}
1002
1003void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
1004{
1005 // TODO: add the default files (state files in user's savestates dir)
1007}
1008
1009
1010// class GetClipboardCommand
1011
1013 CommandController& commandController_, Reactor& reactor_)
1014 : Command(commandController_, "get_clipboard_text")
1015 , reactor(reactor_)
1016{
1017 // Note: cannot yet call getReactor().getDisplay() (e.g. to cache it)
1018 // display may not yet be initialized.
1019}
1020
1021void GetClipboardCommand::execute(std::span<const TclObject> tokens, TclObject& result)
1022{
1023 checkNumArgs(tokens, 1, Prefix{1}, nullptr);
1024 result = reactor.getDisplay().getVideoSystem().getClipboardText();
1025}
1026
1027string GetClipboardCommand::help(std::span<const TclObject> /*tokens*/) const
1028{
1029 return "Returns the (text) content of the clipboard as a string.";
1030}
1031
1032
1033// class SetClipboardCommand
1034
1036 CommandController& commandController_, Reactor& reactor_)
1037 : Command(commandController_, "set_clipboard_text")
1038 , reactor(reactor_)
1039{
1040}
1041
1042void SetClipboardCommand::execute(std::span<const TclObject> tokens, TclObject& /*result*/)
1043{
1044 checkNumArgs(tokens, 2, "text");
1045 reactor.getDisplay().getVideoSystem().setClipboardText(tokens[1].getString());
1046}
1047
1048string SetClipboardCommand::help(std::span<const TclObject> /*tokens*/) const
1049{
1050 return "Send the given string to the clipboard.";
1051}
1052
1053
1054// class ConfigInfo
1055
1057 const string& configName_)
1058 : InfoTopic(openMSXInfoCommand, configName_)
1059 , configName(configName_)
1060{
1061}
1062
1063void ConfigInfo::execute(std::span<const TclObject> tokens, TclObject& result) const
1064{
1065 // TODO make meta info available through this info topic
1066 switch (tokens.size()) {
1067 case 2: {
1068 result.addListElements(Reactor::getHwConfigs(configName));
1069 break;
1070 }
1071 case 3: {
1072 try {
1073 std::array<char, 8192> allocBuffer; // tweak
1074 XMLDocument doc{allocBuffer.data(), sizeof(allocBuffer)};
1076 doc, configName, tokens[2].getString());
1077 if (const auto* info = doc.getRoot()->findChild("info")) {
1078 for (const auto& c : info->getChildren()) {
1079 result.addDictKeyValue(c.getName(), c.getData());
1080 }
1081 }
1082 } catch (MSXException& e) {
1083 throw CommandException(
1084 "Couldn't get config info: ", e.getMessage());
1085 }
1086 break;
1087 }
1088 default:
1089 throw CommandException("Too many parameters");
1090 }
1091}
1092
1093string ConfigInfo::help(std::span<const TclObject> /*tokens*/) const
1094{
1095 return strCat("Shows a list of available ", configName, ", "
1096 "or get meta information about the selected item.\n");
1097}
1098
1099void ConfigInfo::tabCompletion(vector<string>& tokens) const
1100{
1101 completeString(tokens, Reactor::getHwConfigs(configName));
1102}
1103
1104
1105// class RealTimeInfo
1106
1108 : InfoTopic(openMSXInfoCommand, "realtime")
1109 , reference(Timer::getTime())
1110{
1111}
1112
1113void RealTimeInfo::execute(std::span<const TclObject> /*tokens*/,
1114 TclObject& result) const
1115{
1116 auto delta = Timer::getTime() - reference;
1117 result = narrow_cast<double>(delta) / 1000000.0;
1118}
1119
1120string RealTimeInfo::help(std::span<const TclObject> /*tokens*/) const
1121{
1122 return "Returns the time in seconds since openMSX was started.";
1123}
1124
1125
1126// SoftwareInfoTopic
1127
1129 : InfoTopic(openMSXInfoCommand, "software")
1130 , reactor(reactor_)
1131{
1132}
1133
1135 std::span<const TclObject> tokens, TclObject& result) const
1136{
1137 if (tokens.size() != 3) {
1138 throw CommandException("Wrong number of parameters");
1139 }
1140
1141 Sha1Sum sha1sum(tokens[2].getString());
1142 auto& romDatabase = reactor.getSoftwareDatabase();
1143 const RomInfo* romInfo = romDatabase.fetchRomInfo(sha1sum);
1144 if (!romInfo) {
1145 // no match found
1146 throw CommandException(
1147 "Software with sha1sum ", sha1sum.toString(), " not found");
1148 }
1149
1150 const char* bufStart = romDatabase.getBufferStart();
1151 result.addDictKeyValues("title", romInfo->getTitle(bufStart),
1152 "year", romInfo->getYear(bufStart),
1153 "company", romInfo->getCompany(bufStart),
1154 "country", romInfo->getCountry(bufStart),
1155 "orig_type", romInfo->getOrigType(bufStart),
1156 "remark", romInfo->getRemark(bufStart),
1157 "original", romInfo->getOriginal(),
1158 "mapper_type_name", RomInfo::romTypeToName(romInfo->getRomType()),
1159 "genmsxid", romInfo->getGenMSXid());
1160}
1161
1162string SoftwareInfoTopic::help(std::span<const TclObject> /*tokens*/) const
1163{
1164 return "Returns information about the software "
1165 "given its sha1sum, in a paired list.";
1166}
1167
1168} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:28
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:855
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:869
ActivateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:848
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:876
Interpreter & getInterpreter() const final
Definition: Command.cc: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:1099
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1093
ConfigInfo(InfoCommand &openMSXInfoCommand, const string &configName)
Definition: Reactor.cc:1056
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1063
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:785
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:777
CreateMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:770
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:807
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:819
DeleteMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:800
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:814
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:687
ExitCommand(CommandController &commandController, EventDistributor &distributor)
Definition: Reactor.cc:666
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:673
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:1021
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1027
GetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:1012
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:834
ListMachinesCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:827
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:840
std::string loadMachine(const std::string &machine)
std::string_view getMachineID() const
MachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:696
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:703
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:728
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:723
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:608
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:1107
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition: Reactor.cc:1120
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1113
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:995
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:1003
RestoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:937
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:944
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:1042
SetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:1035
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:1048
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh: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:1162
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition: Reactor.cc:1134
SoftwareInfoTopic(InfoCommand &openMSXInfoCommand, Reactor &reactor)
Definition: Reactor.cc:1128
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: Reactor.cc:929
StoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:884
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:919
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:891
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:762
TestMachineCommand(CommandController &commandController, Reactor &reactor)
Definition: Reactor.cc:736
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: Reactor.cc:743
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition: Reactor.cc:755
virtual void setClipboardText(zstring_view text)=0
virtual std::string getClipboardText()=0
ALWAYS_INLINE void serialize(const char *tag, T &t, Args &&...args)
Definition: serialize.hh: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:21
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:655
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)