1 //
2 // Copyright (c) 2010-2025 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using Antmicro.Renode.Logging;
10 using Antmicro.Renode.Logging.Backends;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.UserInterface;
13 using Antmicro.Renode.UserInterface.Tokenizer;
14 using Antmicro.Renode.Backends.Terminals;
15 using AntShell;
16 using AntShell.Terminal;
17 using Antmicro.Renode.Exceptions;
18 using Antmicro.Renode.Utilities;
19 using Antmicro.Renode.Peripherals.UART;
20 using System.Linq;
21 using Antmicro.OptionsParser;
22 using System.IO;
23 using System.Diagnostics;
24 using Antmicro.Renode.Analyzers;
25 using Antmicro.Renode.Extensions.Analyzers.Video;
26 using Antmicro.Renode.Backends.Video;
27 
28 namespace Antmicro.Renode.UI
29 {
30     public static class CommandLineInterface
31     {
Run(Options options, Action<ObjectCreator.Context> beforeRun = null)32         public static void Run(Options options, Action<ObjectCreator.Context> beforeRun = null)
33         {
34             AppDomain.CurrentDomain.UnhandledException += (sender, e) => CrashHandler.HandleCrash((Exception)e.ExceptionObject);
35 
36             if(options.KeepTemporaryFiles)
37             {
38                 EmulationManager.DisableEmulationFilesCleanup = true;
39             }
40 
41             if(!options.HideLog)
42             {
43                 Logger.AddBackend(ConsoleBackend.Instance, "console");
44                 if(options.Plain)
45                 {
46                     //This is set in Program.cs already, but we leave it here in case CommandLineInterface is reused,
47                     //to prevent hard to trace bugs
48                     ConsoleBackend.Instance.PlainMode = true;
49                 }
50             }
51 
52             Logger.AddBackend(new MemoryBackend(), "memory");
53             Emulator.ShowAnalyzers = !options.HideAnalyzers;
54             XwtProvider xwt = null;
55             if(options.PidFile != null)
56             {
57                 var pid = Process.GetCurrentProcess().Id;
58                 File.WriteAllText(options.PidFile, pid.ToString());
59             }
60 
61             if(!options.DisableXwt || options.RobotDebug)
62             {
63                 xwt = XwtProvider.Create(new WindowedUserInterfaceProvider());
64             }
65 
66             if(xwt == null && options.RobotFrameworkRemoteServerPort == -1 && !options.Console)
67             {
68                 if(options.Port == -1)
69                 {
70                     options.Port = 1234;
71                 }
72 
73                 if(!options.DisableXwt)
74                 {
75                     Logger.Log(LogLevel.Warning, "Couldn't start UI - falling back to console mode");
76                     options.Console = true;
77                 }
78             }
79 
80             using(var context = ObjectCreator.Instance.OpenContext())
81             {
82                 var monitor = new Antmicro.Renode.UserInterface.Monitor();
83                 context.RegisterSurrogate(typeof(Antmicro.Renode.UserInterface.Monitor), monitor);
84 
85                 // we must initialize plugins AFTER registering monitor surrogate
86                 // as some plugins might need it for construction
87                 TypeManager.Instance.PluginManager.Init("CLI");
88 
89                 EmulationManager.Instance.ProgressMonitor.Handler = new CLIProgressMonitor();
90 
91                 var uartAnalyzerType = (xwt == null || options.RobotDebug) ? typeof(LoggingUartAnalyzer) : typeof(ConsoleWindowBackendAnalyzer);
92                 var videoAnalyzerType = (xwt == null || options.RobotDebug) ? typeof(DummyVideoAnalyzer) : typeof(VideoAnalyzer);
93 
94                 EmulationManager.Instance.CurrentEmulation.BackendManager.SetPreferredAnalyzer(typeof(UARTBackend), uartAnalyzerType);
95                 EmulationManager.Instance.CurrentEmulation.BackendManager.SetPreferredAnalyzer(typeof(VideoBackend), videoAnalyzerType);
96                 EmulationManager.Instance.EmulationChanged += () =>
97                 {
98                     EmulationManager.Instance.CurrentEmulation.BackendManager.SetPreferredAnalyzer(typeof(UARTBackend), uartAnalyzerType);
99                     EmulationManager.Instance.CurrentEmulation.BackendManager.SetPreferredAnalyzer(typeof(VideoBackend), videoAnalyzerType);
100                 };
101 
102                 var shell = PrepareShell(options, monitor);
103                 new System.Threading.Thread(x => shell.Start(true))
104                 {
105                     IsBackground = true,
106                     Name = "Shell thread"
107                 }.Start();
108 
109                 Emulator.BeforeExit += () =>
110                 {
111                     Emulator.DisposeAll();
112                     xwt?.Dispose();
113                     xwt = null;
114                 };
115 
116                 if(beforeRun != null)
117                 {
118                     beforeRun(context);
119                 }
120 
121                 if(options.RobotDebug)
122                 {
123                     ConsoleWindowBackendAnalyzer terminal = null;
124 
125                     Emulator.EnableGUI += () =>
126                     {
127                         Logger.AddBackend(ConsoleBackend.Instance, "console", true);
128                         terminal = new ConsoleWindowBackendAnalyzer(true);
129                         terminal.Show();
130                         shell.Terminal = new NavigableTerminalEmulator(terminal.IO);
131                         shell.Terminal.PlainMode = options.Plain;
132 
133                         new System.Threading.Thread(x => shell.Start(true))
134                         {
135                             IsBackground = true,
136                             Name = "Shell thread"
137                         }.Start();
138                     };
139 
140                     Emulator.DisableGUI += () =>
141                     {
142                         if(options.HideLog)
143                         {
144                             Logger.RemoveBackend(ConsoleBackend.Instance);
145                         }
146                         terminal?.Hide();
147                         terminal = null;
148                     };
149                 }
150 
151                 Emulator.WaitForExit();
152             }
153         }
154 
PrepareShell(Options options, Monitor monitor)155         private static Shell PrepareShell(Options options, Monitor monitor)
156         {
157             Shell shell = null;
158             if(options.Console)
159             {
160                 var io = new IOProvider()
161                 {
162                     Backend = new ConsoleIOSource()
163                 };
164                 shell = ShellProvider.GenerateShell(monitor, true);
165                 shell.Terminal = new NavigableTerminalEmulator(io, true);
166             }
167             else if(options.Port >= 0)
168             {
169                 var io = new IOProvider()
170                 {
171                     Backend = new SocketIOSource(options.Port)
172                 };
173                 shell = ShellProvider.GenerateShell(monitor, true);
174                 shell.Terminal = new NavigableTerminalEmulator(io, true);
175 
176                 Logger.Log(LogLevel.Info, "Monitor available in telnet mode on port {0}", options.Port);
177             }
178             else
179             {
180                 ConsoleWindowBackendAnalyzer terminal = null;
181                 IOProvider io;
182                 if(options.HideMonitor)
183                 {
184                     io = new IOProvider { Backend = new DummyIOSource() };
185                 }
186                 else
187                 {
188                     terminal = new ConsoleWindowBackendAnalyzer(true);
189                     io = terminal.IO;
190                 }
191 
192                 // forcing vcursor is necessary, because calibrating will never end if the window is not shown
193                 shell = ShellProvider.GenerateShell(monitor, forceVCursor: options.HideMonitor);
194                 shell.Terminal = new NavigableTerminalEmulator(io, options.HideMonitor);
195 
196                 if(terminal != null)
197                 {
198                     try
199                     {
200                         Emulator.BeforeExit += shell.Stop;
201                         terminal.Quitted += Emulator.Exit;
202                         terminal.Show();
203                     }
204                     catch(InvalidOperationException ex)
205                     {
206                         Console.ForegroundColor = ConsoleColor.Red;
207                         Console.Error.WriteLine(ex.Message);
208                         Emulator.Exit();
209                     }
210                 }
211             }
212             monitor.Quitted += shell.Stop;
213             shell.Quitted += Emulator.Exit;
214 
215             monitor.Interaction = shell.Writer;
216             monitor.MachineChanged += emu => shell.SetPrompt(emu != null ? new Prompt(string.Format("({0}) ", emu), ConsoleColor.DarkYellow) : null);
217 
218             if(!string.IsNullOrEmpty(options.FilePath))
219             {
220                 var filePath = string.Format("{0}{1}",
221                     Uri.IsWellFormedUriString(options.FilePath, UriKind.Absolute) || Path.IsPathRooted(options.FilePath) ? "@" : "$CWD/",
222                     options.FilePath);
223                 String commandToInject;
224                 switch(Path.GetExtension(filePath))
225                 {
226                     case ".save":
227                     case ".gz":
228                         commandToInject = string.Format("Load {0}\n", filePath);
229                         break;
230                     default:
231                         commandToInject = string.Format("i {0}\n", filePath);
232                         break;
233                 }
234                 shell.Started += s => s.InjectInput(commandToInject);
235             }
236             if(options.Execute != null)
237             {
238                 shell.Started += s => s.InjectInput(string.Format("{0}\n", string.Join("; ", options.Execute)));
239             }
240 
241             shell.Terminal.PlainMode = options.Plain;
242 
243             return shell;
244         }
245     }
246 }
247