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 "StringOp.hh"
53#include "unreachable.hh"
54#include "build-info.hh"
55
56#include <array>
57#include <cassert>
58#include <memory>
59
60using std::make_unique;
61using std::string;
62using std::string_view;
63using std::vector;
64
65namespace openmsx {
66
67// global variable to communicate the exit-code from the 'exit' command to main()
68int exitCode = 0;
69
70class ExitCommand final : public Command
71{
72public:
73 ExitCommand(CommandController& commandController, EventDistributor& distributor);
74 void execute(std::span<const TclObject> tokens, TclObject& result) override;
75 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
76private:
77 EventDistributor& distributor;
78};
79
80class MachineCommand final : public Command
81{
82public:
83 MachineCommand(CommandController& commandController, Reactor& reactor);
84 void execute(std::span<const TclObject> tokens, TclObject& result) override;
85 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
86 void tabCompletion(vector<string>& tokens) const override;
87private:
88 Reactor& reactor;
89};
90
91class TestMachineCommand final : public Command
92{
93public:
94 TestMachineCommand(CommandController& commandController, Reactor& reactor);
95 void execute(std::span<const TclObject> tokens, TclObject& result) override;
96 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
97 void tabCompletion(vector<string>& tokens) const override;
98private:
99 Reactor& reactor;
100};
101
102class CreateMachineCommand final : public Command
103{
104public:
105 CreateMachineCommand(CommandController& commandController, Reactor& reactor);
106 void execute(std::span<const TclObject> tokens, TclObject& result) override;
107 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
108private:
109 Reactor& reactor;
110};
111
112class DeleteMachineCommand final : public Command
113{
114public:
115 DeleteMachineCommand(CommandController& commandController, Reactor& reactor);
116 void execute(std::span<const TclObject> tokens, TclObject& result) override;
117 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
118 void tabCompletion(vector<string>& tokens) const override;
119private:
120 Reactor& reactor;
121};
122
123class ListMachinesCommand final : public Command
124{
125public:
126 ListMachinesCommand(CommandController& commandController, Reactor& reactor);
127 void execute(std::span<const TclObject> tokens, TclObject& result) override;
128 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
129private:
130 Reactor& reactor;
131};
132
133class ActivateMachineCommand final : public Command
134{
135public:
136 ActivateMachineCommand(CommandController& commandController, Reactor& reactor);
137 void execute(std::span<const TclObject> tokens, TclObject& result) override;
138 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
139 void tabCompletion(vector<string>& tokens) const override;
140private:
141 Reactor& reactor;
142};
143
144class StoreMachineCommand final : public Command
145{
146public:
147 StoreMachineCommand(CommandController& commandController, Reactor& reactor);
148 void execute(std::span<const TclObject> tokens, TclObject& result) override;
149 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
150 void tabCompletion(vector<string>& tokens) const override;
151private:
152 Reactor& reactor;
153};
154
155class RestoreMachineCommand final : public Command
156{
157public:
158 RestoreMachineCommand(CommandController& commandController, Reactor& reactor);
159 void execute(std::span<const TclObject> tokens, TclObject& result) override;
160 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
161 void tabCompletion(vector<string>& tokens) const override;
162private:
163 Reactor& reactor;
164};
165
166class GetClipboardCommand final : public Command
167{
168public:
169 GetClipboardCommand(CommandController& commandController, Reactor& reactor);
170 void execute(std::span<const TclObject> tokens, TclObject& result) override;
171 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
172private:
173 Reactor& reactor;
174};
175
176class SetClipboardCommand final : public Command
177{
178public:
179 SetClipboardCommand(CommandController& commandController, Reactor& reactor);
180 void execute(std::span<const TclObject> tokens, TclObject& result) override;
181 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
182private:
183 Reactor& reactor;
184};
185
186class ConfigInfo final : public InfoTopic
187{
188public:
189 ConfigInfo(InfoCommand& openMSXInfoCommand, const string& configName);
190 void execute(std::span<const TclObject> tokens,
191 TclObject& result) const override;
192 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
193 void tabCompletion(vector<string>& tokens) const override;
194private:
195 const string configName;
196};
197
198class RealTimeInfo final : public InfoTopic
199{
200public:
201 explicit RealTimeInfo(InfoCommand& openMSXInfoCommand);
202 void execute(std::span<const TclObject> tokens,
203 TclObject& result) const override;
204 [[nodiscard]] string help(std::span<const TclObject> tokens) const override;
205private:
206 const uint64_t reference;
207};
208
210{
211public:
212 SoftwareInfoTopic(InfoCommand& openMSXInfoCommand, Reactor& reactor);
213 void execute(std::span<const TclObject> tokens,
214 TclObject& result) const override;
215 [[nodiscard]] std::string help(std::span<const TclObject> tokens) const override;
216private:
217 Reactor& reactor;
218};
219
220
221Reactor::Reactor() = default;
222
224{
225 rtScheduler = make_unique<RTScheduler>();
226 eventDistributor = make_unique<EventDistributor>(*this);
227 globalCliComm = make_unique<GlobalCliComm>();
228 globalCommandController = make_unique<GlobalCommandController>(
229 *eventDistributor, *globalCliComm, *this);
230 globalSettings = make_unique<GlobalSettings>(
231 *globalCommandController);
232 inputEventGenerator = make_unique<InputEventGenerator>(
233 *globalCommandController, *eventDistributor, *globalSettings);
234 symbolManager = make_unique<SymbolManager>(
235 *globalCommandController);
236 imGuiManager = make_unique<ImGuiManager>(*this);
237 diskFactory = make_unique<DiskFactory>(
238 *this);
239 diskManipulator = make_unique<DiskManipulator>(
240 *globalCommandController, *this);
241 virtualDrive = make_unique<DiskChanger>(
242 *this, "virtual_drive");
243 filePool = make_unique<FilePool>(*globalCommandController, *this);
244 userSettings = make_unique<UserSettings>(
245 *globalCommandController);
246 afterCommand = make_unique<AfterCommand>(
247 *this, *eventDistributor, *globalCommandController);
248 exitCommand = make_unique<ExitCommand>(
249 *globalCommandController, *eventDistributor);
250 messageCommand = make_unique<MessageCommand>(
251 *globalCommandController);
252 machineCommand = make_unique<MachineCommand>(
253 *globalCommandController, *this);
254 testMachineCommand = make_unique<TestMachineCommand>(
255 *globalCommandController, *this);
256 createMachineCommand = make_unique<CreateMachineCommand>(
257 *globalCommandController, *this);
258 deleteMachineCommand = make_unique<DeleteMachineCommand>(
259 *globalCommandController, *this);
260 listMachinesCommand = make_unique<ListMachinesCommand>(
261 *globalCommandController, *this);
262 activateMachineCommand = make_unique<ActivateMachineCommand>(
263 *globalCommandController, *this);
264 storeMachineCommand = make_unique<StoreMachineCommand>(
265 *globalCommandController, *this);
266 restoreMachineCommand = make_unique<RestoreMachineCommand>(
267 *globalCommandController, *this);
268 getClipboardCommand = make_unique<GetClipboardCommand>(
269 *globalCommandController, *this);
270 setClipboardCommand = make_unique<SetClipboardCommand>(
271 *globalCommandController, *this);
272 aviRecordCommand = make_unique<AviRecorder>(*this);
273 extensionInfo = make_unique<ConfigInfo>(
274 getOpenMSXInfoCommand(), "extensions");
275 machineInfo = make_unique<ConfigInfo>(
276 getOpenMSXInfoCommand(), "machines");
277 realTimeInfo = make_unique<RealTimeInfo>(
279 softwareInfoTopic = make_unique<SoftwareInfoTopic>(
280 getOpenMSXInfoCommand(), *this);
281 tclCallbackMessages = make_unique<TclCallbackMessages>(
282 *globalCliComm, *globalCommandController);
283
284 createMachineSetting();
285
287
288 eventDistributor->registerEventListener(EventType::QUIT, *this);
289#if PLATFORM_ANDROID
290 eventDistributor->registerEventListener(EventType::WINDOW, *this);
291#endif
292 isInit = true;
293}
294
296{
297 if (!isInit) return;
298 deleteBoard(activeBoard);
299
300 eventDistributor->unregisterEventListener(EventType::QUIT, *this);
301#if PLATFORM_ANDROID
302 eventDistributor->unregisterEventListener(EventType::WINDOW, *this);
303#endif
304
306}
307
309{
310 if (!mixer) {
311 mixer = make_unique<Mixer>(*this, *globalCommandController);
312 }
313 return *mixer;
314}
315
317{
318 if (!softwareDatabase) {
319 softwareDatabase = make_unique<RomDatabase>(*globalCliComm);
320 }
321 return *softwareDatabase;
322}
323
325{
326 return *globalCliComm;
327}
328
333
335{
336 return *globalCommandController;
337}
338
340{
341 return globalCommandController->getOpenMSXInfoCommand();
342}
343
345{
346 return globalCommandController->getHotKey();
347}
348
349vector<string> Reactor::getHwConfigs(string_view type)
350{
351 vector<string> result;
352 for (const auto& p : systemFileContext().getPaths()) {
353 auto fileAction = [&](const std::string& /*path*/, std::string_view name) {
354 if (name.ends_with(".xml")) {
355 name.remove_suffix(4);
356 result.emplace_back(name);
357 }
358 };
359 auto dirAction = [&](std::string& path, std::string_view name) {
360 auto size = path.size();
361 path += "/hardwareconfig.xml";
363 result.emplace_back(name);
364 }
365 path.resize(size);
366 };
367 foreach_file_and_directory(FileOperations::join(p, type), fileAction, dirAction);
368 }
369 // remove duplicates
370 ranges::sort(result);
371 result.erase(ranges::unique(result), end(result));
372 return result;
373}
374
376{
377 // TODO cleanup this code. It should be easier to get a hold of the
378 // 'MsxChar2Unicode' object. Probably the 'Keyboard' class is not the
379 // right location to store it.
380 try {
381 if (MSXMotherBoard* board = getMotherBoard()) {
382 if (MSXPPI* ppi = dynamic_cast<MSXPPI*>(board->findDevice("ppi"))) {
383 return ppi->getKeyboard().getMsxChar2Unicode();
384 }
385 }
386 } catch (MSXException&) {
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::lock_guard<std::mutex> 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::lock_guard<std::mutex> 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, Between{1, 3}, Prefix{1}, "?id? ?filename?");
929 string filename;
930 string_view machineID;
931 switch (tokens.size()) {
932 case 1:
933 machineID = reactor.getMachineID();
934 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
935 break;
936 case 2:
937 machineID = tokens[1].getString();
938 filename = FileOperations::getNextNumberedFileName("savestates", "openmsxstate", ".xml.gz");
939 break;
940 case 3:
941 machineID = tokens[1].getString();
942 filename = tokens[2].getString();
943 break;
944 }
945
946 auto& board = *reactor.getMachine(machineID);
947
948 XmlOutputArchive out(filename);
949 out.serialize("machine", board);
950 out.close();
951 result = filename;
952}
953
954string StoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
955{
956 return
957 "store_machine Save state of current machine to file \"openmsxNNNN.xml.gz\"\n"
958 "store_machine machineID Save state of machine \"machineID\" to file \"openmsxNNNN.xml.gz\"\n"
959 "store_machine machineID <filename> Save state of machine \"machineID\" to indicated file\n"
960 "\n"
961 "This is a low-level command, the 'savestate' script is easier to use.";
962}
963
964void StoreMachineCommand::tabCompletion(vector<string>& tokens) const
965{
966 completeString(tokens, reactor.getMachineIDs());
967}
968
969
970// class RestoreMachineCommand
971
973 CommandController& commandController_, Reactor& reactor_)
974 : Command(commandController_, "restore_machine")
975 , reactor(reactor_)
976{
977}
978
979void RestoreMachineCommand::execute(std::span<const TclObject> tokens,
980 TclObject& result)
981{
982 checkNumArgs(tokens, Between{1, 2}, Prefix{1}, "?filename?");
983 auto newBoard = reactor.createEmptyMotherBoard();
984
985 string filename;
986 switch (tokens.size()) {
987 case 1: {
988 // load last saved entry
989 string lastEntry;
990 time_t lastTime = 0;
992 FileOperations::getUserOpenMSXDir() + "/savestates",
993 [&](const string& path, const FileOperations::Stat& st) {
994 time_t modTime = st.st_mtime;
995 if (modTime > lastTime) {
996 filename = path;
997 lastTime = modTime;
998 }
999 });
1000 if (filename.empty()) {
1001 throw CommandException("Can't find last saved state.");
1002 }
1003 break;
1004 }
1005 case 2:
1006 filename = FileOperations::expandTilde(string(tokens[1].getString()));
1007 break;
1008 }
1009
1010 //std::cerr << "Loading " << filename << '\n';
1011 try {
1012 XmlInputArchive in(filename);
1013 in.serialize("machine", *newBoard);
1014 } catch (XMLException& e) {
1015 throw CommandException("Cannot load state, bad file format: ",
1016 e.getMessage());
1017 } catch (MSXException& e) {
1018 throw CommandException("Cannot load state: ", e.getMessage());
1019 }
1020
1021 // Savestate also contains stuff like the keyboard state at the moment
1022 // the snapshot was created (this is required for reverse/replay). But
1023 // now we want the MSX to see the actual host keyboard state.
1024 newBoard->getStateChangeDistributor().stopReplay(newBoard->getCurrentTime());
1025
1026 result = newBoard->getMachineID();
1027 reactor.boards.push_back(std::move(newBoard));
1028}
1029
1030string RestoreMachineCommand::help(std::span<const TclObject> /*tokens*/) const
1031{
1032 return "restore_machine Load state from last saved state in default directory\n"
1033 "restore_machine <filename> Load state from indicated file\n"
1034 "\n"
1035 "This is a low-level command, the 'loadstate' script is easier to use.";
1036}
1037
1038void RestoreMachineCommand::tabCompletion(vector<string>& tokens) const
1039{
1040 // TODO: add the default files (state files in user's savestates dir)
1042}
1043
1044
1045// class GetClipboardCommand
1046
1048 CommandController& commandController_, Reactor& reactor_)
1049 : Command(commandController_, "get_clipboard_text")
1050 , reactor(reactor_)
1051{
1052 // Note: cannot yet call getReactor().getDisplay() (e.g. to cache it)
1053 // display may not yet be initialized.
1054}
1055
1056void GetClipboardCommand::execute(std::span<const TclObject> tokens, TclObject& result)
1057{
1058 checkNumArgs(tokens, 1, Prefix{1}, nullptr);
1059 result = reactor.getDisplay().getVideoSystem().getClipboardText();
1060}
1061
1062string GetClipboardCommand::help(std::span<const TclObject> /*tokens*/) const
1063{
1064 return "Returns the (text) content of the clipboard as a string.";
1065}
1066
1067
1068// class SetClipboardCommand
1069
1071 CommandController& commandController_, Reactor& reactor_)
1072 : Command(commandController_, "set_clipboard_text")
1073 , reactor(reactor_)
1074{
1075}
1076
1077void SetClipboardCommand::execute(std::span<const TclObject> tokens, TclObject& /*result*/)
1078{
1079 checkNumArgs(tokens, 2, "text");
1080 reactor.getDisplay().getVideoSystem().setClipboardText(tokens[1].getString());
1081}
1082
1083string SetClipboardCommand::help(std::span<const TclObject> /*tokens*/) const
1084{
1085 return "Send the given string to the clipboard.";
1086}
1087
1088
1089// class ConfigInfo
1090
1092 const string& configName_)
1093 : InfoTopic(openMSXInfoCommand, configName_)
1094 , configName(configName_)
1095{
1096}
1097
1098void ConfigInfo::execute(std::span<const TclObject> tokens, TclObject& result) const
1099{
1100 // TODO make meta info available through this info topic
1101 switch (tokens.size()) {
1102 case 2: {
1103 result.addListElements(Reactor::getHwConfigs(configName));
1104 break;
1105 }
1106 case 3: {
1107 try {
1108 std::array<char, 8192> allocBuffer; // tweak
1109 XMLDocument doc{allocBuffer.data(), sizeof(allocBuffer)};
1111 doc, configName, tokens[2].getString());
1112 if (const auto* info = doc.getRoot()->findChild("info")) {
1113 for (const auto& c : info->getChildren()) {
1114 result.addDictKeyValue(c.getName(), c.getData());
1115 }
1116 }
1117 } catch (MSXException& e) {
1118 throw CommandException(
1119 "Couldn't get config info: ", e.getMessage());
1120 }
1121 break;
1122 }
1123 default:
1124 throw CommandException("Too many parameters");
1125 }
1126}
1127
1128string ConfigInfo::help(std::span<const TclObject> /*tokens*/) const
1129{
1130 return strCat("Shows a list of available ", configName, ", "
1131 "or get meta information about the selected item.\n");
1132}
1133
1134void ConfigInfo::tabCompletion(vector<string>& tokens) const
1135{
1136 completeString(tokens, Reactor::getHwConfigs(configName));
1137}
1138
1139
1140// class RealTimeInfo
1141
1143 : InfoTopic(openMSXInfoCommand, "realtime")
1144 , reference(Timer::getTime())
1145{
1146}
1147
1148void RealTimeInfo::execute(std::span<const TclObject> /*tokens*/,
1149 TclObject& result) const
1150{
1151 auto delta = Timer::getTime() - reference;
1152 result = narrow_cast<double>(delta) * (1.0 / 1000000.0);
1153}
1154
1155string RealTimeInfo::help(std::span<const TclObject> /*tokens*/) const
1156{
1157 return "Returns the time in seconds since openMSX was started.";
1158}
1159
1160
1161// SoftwareInfoTopic
1162
1164 : InfoTopic(openMSXInfoCommand, "software")
1165 , reactor(reactor_)
1166{
1167}
1168
1170 std::span<const TclObject> tokens, TclObject& result) const
1171{
1172 if (tokens.size() != 3) {
1173 throw CommandException("Wrong number of parameters");
1174 }
1175
1176 Sha1Sum sha1sum(tokens[2].getString());
1177 auto& romDatabase = reactor.getSoftwareDatabase();
1178 const RomInfo* romInfo = romDatabase.fetchRomInfo(sha1sum);
1179 if (!romInfo) {
1180 // no match found
1181 throw CommandException(
1182 "Software with sha1sum ", sha1sum.toString(), " not found");
1183 }
1184
1185 const char* bufStart = romDatabase.getBufferStart();
1186 result.addDictKeyValues("title", romInfo->getTitle(bufStart),
1187 "year", romInfo->getYear(bufStart),
1188 "company", romInfo->getCompany(bufStart),
1189 "country", romInfo->getCountry(bufStart),
1190 "orig_type", romInfo->getOrigType(bufStart),
1191 "remark", romInfo->getRemark(bufStart),
1192 "original", romInfo->getOriginal(),
1193 "mapper_type_name", RomInfo::romTypeToName(romInfo->getRomType()),
1194 "genmsxid", romInfo->getGenMSXid());
1195}
1196
1197string SoftwareInfoTopic::help(std::span<const TclObject> /*tokens*/) const
1198{
1199 return "Returns information about the software "
1200 "given its sha1sum, in a paired list.";
1201}
1202
1203} // 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: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 command.
Definition Reactor.cc:1134
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition Reactor.cc:1128
ConfigInfo(InfoCommand &openMSXInfoCommand, const string &configName)
Definition Reactor.cc:1091
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition Reactor.cc:1098
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:98
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:1056
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition Reactor.cc:1062
GetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition Reactor.cc:1047
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:115
void unmute()
Definition Mixer.cc:122
Contains the main loop of openMSX.
Definition Reactor.hh:72
GlobalSettings & getGlobalSettings()
Definition Reactor.hh:114
MSXMotherBoard * getMotherBoard() const
Definition Reactor.cc:409
std::shared_ptr< MSXMotherBoard > Board
Definition Reactor.hh:121
CommandController & getCommandController()
Definition Reactor.cc:334
void enterMainLoop()
Definition Reactor.cc:520
GlobalCommandController & getGlobalCommandController()
Definition Reactor.hh:88
InfoCommand & getOpenMSXInfoCommand()
Definition Reactor.cc:339
void switchMachine(const std::string &machine)
Definition Reactor.cc:454
Display & getDisplay()
Definition Reactor.hh:90
CliComm & getCliComm()
Definition Reactor.cc:324
void run(CommandLineParser &parser)
Main loop.
Definition Reactor.cc:536
GlobalCliComm & getGlobalCliComm()
Definition Reactor.hh:87
Board createEmptyMotherBoard()
Definition Reactor.cc:429
Interpreter & getInterpreter()
Definition Reactor.cc:329
const HotKey & getHotKey() const
Definition Reactor.cc:344
void replaceBoard(MSXMotherBoard &oldBoard, Board newBoard)
Definition Reactor.cc:434
std::string_view getMachineID() const
Definition Reactor.cc:415
Mixer & getMixer()
Definition Reactor.cc:308
const MsxChar2Unicode & getMsxChar2Unicode() const
Definition Reactor.cc:375
static std::vector< std::string > getHwConfigs(std::string_view type)
Definition Reactor.cc:349
RomDatabase & getSoftwareDatabase()
Definition Reactor.cc:316
Board getMachine(std::string_view machineID) const
Definition Reactor.cc:420
auto getMachineIDs() const
Definition Reactor.hh:128
RealTimeInfo(InfoCommand &openMSXInfoCommand)
Definition Reactor.cc:1142
string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition Reactor.cc:1155
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition Reactor.cc:1148
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition Reactor.cc:1030
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition Reactor.cc:1038
RestoreMachineCommand(CommandController &commandController, Reactor &reactor)
Definition Reactor.cc:972
void execute(std::span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition Reactor.cc:979
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:189
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:1077
SetClipboardCommand(CommandController &commandController, Reactor &reactor)
Definition Reactor.cc:1070
string help(std::span< const TclObject > tokens) const override
Print help for this command.
Definition Reactor.cc:1083
This class represents the result of a sha1 calculation (a 160-bit value).
Definition sha1.hh:23
std::string toString() const
std::string help(std::span< const TclObject > tokens) const override
Print help for this topic.
Definition Reactor.cc:1197
void execute(std::span< const TclObject > tokens, TclObject &result) const override
Show info on this topic.
Definition Reactor.cc:1169
SoftwareInfoTopic(InfoCommand &openMSXInfoCommand, Reactor &reactor)
Definition Reactor.cc:1163
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition Reactor.cc:964
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:954
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:56
void attach(Observer< T > &observer)
Definition Subject.hh:50
void addListElements(ITER first, ITER last)
Definition TclObject.hh:128
void addDictKeyValue(const Key &key, const Value &value)
Definition TclObject.hh:141
void addDictKeyValues(Args &&... args)
Definition TclObject.hh:144
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.
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension, bool addSeparator)
Gets the next numbered file name with the specified prefix in the specified directory,...
bool isRegularFile(const Stat &st)
const string & getUserOpenMSXDir()
Get the openMSX dir in the user's home 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()
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:454
bool foreach_file(std::string path, FileAction fileAction)
int exitCode
Definition Reactor.cc:68
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)