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