1 //
2 // Copyright (c) 2010-2018 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.Migrant;
10 using Xwt;
11 using Antmicro.Renode.Backends.Display;
12 using Xwt.Drawing;
13 using Antmicro.Renode.Peripherals.Input;
14 using Antmicro.Renode.Core;
15 using Antmicro.Renode.Peripherals;
16 using ELFSharp.ELF;
17 using Antmicro.Renode.Extensions.Analyzers.Video.Handlers;
18 using Antmicro.Renode.UI;
19 using Antmicro.Renode.Utilities;
20 
21 namespace Antmicro.Renode.Extensions.Analyzers.Video
22 {
23     [Transient]
24     public class FrameBufferDisplayWidget : Canvas, IExternal, IConnectable<IKeyboard>, IConnectable<IPointerInput>
25     {
FrameBufferDisplayWidget()26         public FrameBufferDisplayWidget()
27         {
28             BoundsChanged += (sender, e) =>
29             {
30                 drawMethod = CalculateDrawMethod();
31                 ActualImageArea = CalculateActualImageRectangle();
32             };
33             handler = new IOHandler(this);
34             handler.GrabConfirm += ShowGrabConfirmationDialog;
35             handler.PointerInputAttached += HandleNewPointerDevice;
36         }
37 
SaveCurrentFrameToFile(string filename)38         public void SaveCurrentFrameToFile(string filename)
39         {
40             lock(imgLock)
41             {
42                 img.Save(filename, ImageFileType.Png);
43             }
44         }
45 
46         /// <summary>
47         /// Draws the frame.
48         /// </summary>
49         /// <param name="frame">Frame represented as array of bytes. If this parameter is omitted previous frame is redrawn.</param>
DrawFrame(byte[] frame = null)50         public void DrawFrame(byte[] frame = null)
51         {
52             if(!drawQueued)
53             {
54                 lock(imgLock)
55                 {
56                     if(img == null)
57                     {
58                         return;
59                     }
60 
61                     if(frame != null)
62                     {
63                         converter.Convert(frame, ref outBuffer);
64                         img.Copy(outBuffer);
65                         cursorDrawn = false;
66                     }
67 
68                     if(!anythingDrawnAfterLastReconfiguration && frame != null)
69                     {
70                         anythingDrawnAfterLastReconfiguration = true;
71                         handler.Init();
72                     }
73 
74                     ApplicationExtensions.InvokeInUIThread(QueueDraw);
75                     drawQueued = true;
76                 }
77             }
78         }
79 
OnDisplayParametersChanged(int width, int height, PixelFormat format)80         public void OnDisplayParametersChanged(int width, int height, PixelFormat format)
81         {
82             var rc = DisplayParametersChanged;
83             if(rc != null)
84             {
85                 rc(width, height, format);
86             }
87         }
88 
SetDisplayParameters(int desiredWidth, int desiredHeight, PixelFormat colorFormat, Endianess endianess)89         public void SetDisplayParameters(int desiredWidth, int desiredHeight, PixelFormat colorFormat, Endianess endianess)
90         {
91             if(desiredWidth == 0 && desiredHeight == 0)
92             {
93                 return;
94             }
95 
96             DesiredDisplayWidth = desiredWidth;
97             DesiredDisplayHeight = desiredHeight;
98 
99             lock(imgLock)
100             {
101                 var pixelFormat = PixelFormat.RGBA8888;
102 #if PLATFORM_WINDOWS
103                 pixelFormat = PixelFormat.BGRA8888;
104 #endif
105                 converter = PixelManipulationTools.GetConverter(colorFormat, endianess, pixelFormat, Endianess.BigEndian);
106                 outBuffer = new byte[desiredWidth * desiredHeight * pixelFormat.GetColorDepth()];
107 
108                 img = new ImageBuilder(DesiredDisplayWidth, DesiredDisplayHeight).ToBitmap();
109                 drawMethod = CalculateDrawMethod();
110                 ActualImageArea = CalculateActualImageRectangle();
111 
112                 anythingDrawnAfterLastReconfiguration = false;
113             }
114 
115             OnDisplayParametersChanged(DesiredDisplayWidth, DesiredDisplayHeight, colorFormat);
116         }
117 
AttachTo(IKeyboard keyboardToAttach)118         public void AttachTo(IKeyboard keyboardToAttach)
119         {
120             handler.Attach(keyboard: keyboardToAttach);
121             var ia = InputAttached;
122             if(ia != null)
123             {
124                 ia(keyboardToAttach);
125             }
126         }
127 
AttachTo(IPointerInput inputToAttach)128         public void AttachTo(IPointerInput inputToAttach)
129         {
130             handler.Attach(pointer: inputToAttach);
131             var ia = InputAttached;
132             if(ia != null)
133             {
134                 ia(inputToAttach);
135             }
136         }
137 
DetachFrom(IPointerInput inputToDetach)138         public void DetachFrom(IPointerInput inputToDetach)
139         {
140             handler.Detach(pointer: true);
141         }
142 
DetachFrom(IKeyboard keyboardToDetach)143         public void DetachFrom(IKeyboard keyboardToDetach)
144         {
145             handler.Detach(keyboard: true);
146         }
147 
148         public event Action<int, int> PointerMoved;
149         public Action<IPeripheral> InputAttached;
150         public Action<int, int, PixelFormat> DisplayParametersChanged;
151         public Action FrameDrawn;
152 
153         public int DesiredDisplayWidth { get; private set; }
154         public int DesiredDisplayHeight { get; private set; }
155         public Rectangle ActualImageArea { get; private set; }
156         public Image Image
157         {
158             get
159             {
160                 lock(imgLock)
161                 {
162                     return img != null ? new Image(img) : null;
163                 }
164             }
165         }
166 
167         public DisplayMode Mode
168         {
169             get { return mode; }
170             set
171             {
172                 mode = value;
173                 drawMethod = CalculateDrawMethod();
174                 ActualImageArea = CalculateActualImageRectangle();
175                 DrawFrame();
176             }
177         }
178 
OnDraw(Context ctx, Rectangle dirtyRect)179         protected override void OnDraw(Context ctx, Rectangle dirtyRect)
180         {
181             var dmc = drawMethod;
182             if(img == null || dmc == null)
183             {
184                 return;
185             }
186 
187             IOHandler.Position current, previous;
188             handler.GetPosition(out current, out previous);
189 
190             if(cursorDrawn && previous != null)
191             {
192                 // drawing a cursor for the second time will effectively remove it
193                 img.DrawCursor(previous.X, previous.Y);
194             }
195 
196             if(current != null)
197             {
198                 img.DrawCursor(current.X, current.Y);
199                 cursorDrawn = true;
200             }
201 
202             dmc(ctx);
203 
204             var fd = FrameDrawn;
205             if(fd != null && drawQueued)
206             {
207                 fd();
208             }
209 
210             lock(imgLock)
211             {
212                 drawQueued = false;
213             }
214         }
215 
CalculateDrawMethod()216         private Action<Context> CalculateDrawMethod()
217         {
218             var bounds = Bounds;
219 
220             if(img == null)
221             {
222                 return ctx =>
223                 {
224                 };
225             }
226             else if(Mode == DisplayMode.Stretch)
227             {
228                 return ctx =>
229                 {
230                     lock(imgLock)
231                     {
232                         ctx.DrawImage(img, bounds.Inflate(-1, -1));
233 
234                         // draw frame
235                         ctx.Rectangle(bounds);
236                         ctx.SetColor(new Color(0.643, 0.623, 0.616));
237                         ctx.SetLineWidth(1);
238                         ctx.Stroke();
239                     }
240                 };
241             }
242             else if(Mode == DisplayMode.Fit)
243             {
244                 Image fitImg;
245                 lock(imgLock)
246                 {
247                     fitImg = img.WithBoxSize(bounds.Size);
248                 }
249                 var posx = (bounds.Size.Width - fitImg.Width) / 2;
250                 var posy = (bounds.Size.Height - fitImg.Height) / 2;
251                 var point = new Point(posx, posy);
252 
253                 return ctx =>
254                 {
255                     lock(imgLock)
256                     {
257                         fitImg = img.WithBoxSize(bounds.Size);
258                     }
259 
260                     var rect = new Rectangle(point, fitImg.Size);
261                     ctx.DrawImage(fitImg, rect.Inflate(-1, -1));
262 
263                     // draw frame
264                     ctx.Rectangle(rect);
265                     ctx.SetColor(new Color(0.643, 0.623, 0.616));
266                     ctx.SetLineWidth(1);
267                     ctx.Stroke();
268                 };
269             }
270             else
271             {
272                 var posx = (bounds.Size.Width - img.Width) / 2;
273                 var posy = (bounds.Size.Height - img.Height) / 2;
274                 var point = new Point(posx, posy);
275 
276                 return ctx =>
277                 {
278                     lock(imgLock)
279                     {
280                         ctx.DrawImage(img, point);
281 
282                         // draw frame
283                         ctx.Rectangle(new Rectangle(point.X - 1, point.Y - 1, img.Width + 2, img.Height + 2));
284                         ctx.SetColor(new Color(0.643, 0.623, 0.616));
285                         ctx.SetLineWidth(1);
286                         ctx.Stroke();
287                     }
288                 };
289             }
290         }
291 
ShowGrabConfirmationDialog()292         private bool ShowGrabConfirmationDialog()
293         {
294             if(dontShowGrabConfirmationDialog)
295             {
296                 return true;
297             }
298 
299             var dialog = new Dialog();
300             dialog.Title = "Grabbing mouse&keyboard";
301 
302             var dialogContent = new VBox();
303             CheckBox checkBox;
304             dialogContent.PackStart(new Label { Markup = "Frame buffer analyser is about to grab your mouse and keyboard.\nTo ungrab it press <b>Left-Ctrl + Left-Alt + Left-Shift</b> combination." });
305             dialogContent.PackStart((checkBox = new CheckBox("Don't show this message again")));
306             dialog.Content = dialogContent;
307             dialog.Buttons.Add(new DialogButton(Command.Ok));
308             dialog.Buttons.Add(new DialogButton(Command.Cancel));
309 
310             var result = dialog.Run();
311             dialog.Dispose();
312             if(result == Command.Ok)
313             {
314                 dontShowGrabConfirmationDialog = checkBox.Active;
315                 return true;
316             }
317 
318             return false;
319         }
320 
HandleNewPointerDevice(PointerHandler pointerHandler, PointerHandler previousPointerHandler)321         private void HandleNewPointerDevice(PointerHandler pointerHandler, PointerHandler previousPointerHandler)
322         {
323             var previousAbsolutePointerHandler = previousPointerHandler as AbsolutePointerHandler;
324             if(previousAbsolutePointerHandler != null)
325             {
326                 previousAbsolutePointerHandler.OnPointerMoved -= HandlePointerMoved;
327             }
328 
329             var absolutePointerHandler = pointerHandler as AbsolutePointerHandler;
330             if(absolutePointerHandler == null)
331             {
332                 HandlePointerMoved(-1, -1);
333                 return;
334             }
335 
336             absolutePointerHandler.OnPointerMoved += HandlePointerMoved;
337         }
338 
HandlePointerMoved(int x, int y)339         private void HandlePointerMoved(int x, int y)
340         {
341             var pm = PointerMoved;
342             if(pm != null)
343             {
344                 pm(x, y);
345             }
346             DrawFrame();
347         }
348 
349         /// <summary>
350         /// Gets actual image rectangle relative to canvas
351         /// </summary>
352         /// <returns>The actual image rectangle.</returns>
CalculateActualImageRectangle()353         private Rectangle CalculateActualImageRectangle()
354         {
355             var canvasRect = ScreenBounds;
356             var image = Image;
357             var imgRect = new Rectangle();
358 
359             if(image == null)
360             {
361                 return imgRect;
362             }
363 
364             switch(Mode)
365             {
366             case DisplayMode.Center:
367                 imgRect.Width = image.Width < canvasRect.Width ? image.Width : canvasRect.Width;
368                 imgRect.Height = image.Height < canvasRect.Height ? image.Height : canvasRect.Height;
369                 // if image is bigger than canvas, don't set margin
370                 imgRect.X = image.Width < canvasRect.Width ? (canvasRect.Width - image.Width) / 2 : 0;
371                 imgRect.Y = image.Height < canvasRect.Height ? (canvasRect.Height - image.Height) / 2 : 0;
372 
373                 break;
374             case DisplayMode.Fit:
375                 //check where the margin is
376                 var fitImg = image.WithBoxSize(canvasRect.Width, canvasRect.Height);
377                 imgRect.Width = fitImg.Width;
378                 imgRect.Height = fitImg.Height;
379                 //margin is on the left and right
380                 if(fitImg.Width < canvasRect.Width)
381                 {
382                     imgRect.X = (canvasRect.Width - fitImg.Width) / 2;
383                     imgRect.Y = 0;
384                 }
385                 else
386                 {
387                     imgRect.X = 0;
388                     imgRect.Y = (canvasRect.Height - fitImg.Height) / 2;
389                 }
390                 break;
391             case DisplayMode.Stretch:
392                 imgRect = new Rectangle(0, 0, canvasRect.Width, canvasRect.Height);
393                 break;
394             }
395             return imgRect;
396         }
397 
398         private IPixelConverter converter;
399         [Transient]
400         private bool dontShowGrabConfirmationDialog;
401         private bool anythingDrawnAfterLastReconfiguration;
402         private Action<Context> drawMethod;
403         private bool drawQueued;
404         private IOHandler handler;
405         private BitmapImage img;
406         private DisplayMode mode;
407         private byte[] outBuffer;
408         private bool cursorDrawn;
409 
410         private readonly object imgLock = new object();
411     }
412 }
413 
414