Skip to content

Add screen orientation support for Linux frame buffer and DRM applications. #18658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Avalonia.Media;
using Avalonia.Skia;

namespace Avalonia.LinuxFramebuffer
{
Expand All @@ -12,7 +13,13 @@ public class DrmOutputOptions
/// Default: 1.0
/// </summary>
public double Scaling { get; set; } = 1.0;


/// <summary>
/// The orientation of the screen relative to the frame buffer memory orientation
/// Default: Normal
/// </summary>
public SurfaceOrientation Orientation { get; set; } = SurfaceOrientation.Normal;

/// <summary>
/// If true an two cycle buffer swapping is processed at init.
/// Default: True
Expand Down
26 changes: 23 additions & 3 deletions src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Skia;
using Avalonia.Threading;

namespace Avalonia.LinuxFramebuffer
namespace Avalonia.LinuxFramebuffer
{
class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider
{
Expand Down Expand Up @@ -71,7 +72,14 @@ public void SetCursor(ICursorImpl? cursor)
public Action? Closed { get; set; }
public Action? LostFocus { get; set; }

public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling);
public PixelSize RotatedSize => Orientation switch
{
SurfaceOrientation.Rotated90 => new PixelSize(_outputBackend.PixelSize.Height, _outputBackend.PixelSize.Width),
SurfaceOrientation.Rotated270 => new PixelSize(_outputBackend.PixelSize.Height, _outputBackend.PixelSize.Width),
_ => _outputBackend.PixelSize,
};

public Size ScaledSize => RotatedSize.ToSize(RenderScaling);

public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel) { }

Expand All @@ -80,6 +88,18 @@ public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> tran
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }

public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);

// implements ISurfaceOrientation
public SurfaceOrientation Orientation
{
get => _outputBackend.Orientation;
set
{
_outputBackend.Orientation = value;
Resized?.Invoke(ScaledSize, WindowResizeReason.Unspecified);
}
}

public object? TryGetFeature(Type featureType) => null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Avalonia.Skia;

namespace Avalonia.LinuxFramebuffer.Input
{
public interface IScreenInfoProvider
public interface IScreenInfoProvider : ISurfaceOrientation
{
Size ScaledSize { get; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Skia;
using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
namespace Avalonia.LinuxFramebuffer.Input.LibInput
{
Expand Down Expand Up @@ -30,12 +32,31 @@ private IInputRoot InputRoot
private unsafe void InputThread(IntPtr ctx, LibInputBackendOptions options)
{
var fd = libinput_get_fd(ctx);
IntPtr[] devices = [.. options.Events!.Select(f => libinput_path_add_device(ctx, f))];
SurfaceOrientation screenOrientation = SurfaceOrientation.Unknown;

foreach (var f in options.Events!)
libinput_path_add_device(ctx, f);
while (true)
{
IntPtr ev;

if (_screen!.Orientation != screenOrientation)
{
screenOrientation = _screen.Orientation;

float[] matrix = screenOrientation switch
{
SurfaceOrientation.Rotated90 => [0, 1, 0, -1, 0, 1],
SurfaceOrientation.Rotated180 => [-1, 0, 1, 0, -1, 1],
SurfaceOrientation.Rotated270 => [0, -1, 1, 1, 0, 0],
_ => [1, 0, 0, 0, 1, 0], // Normal
};

foreach (var device in devices)
{
libinput_device_config_calibration_set_matrix(device, matrix);
}
}

libinput_dispatch(ctx);
while ((ev = libinput_get_event(ctx)) != IntPtr.Zero)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public static IntPtr libinput_path_create_context() =>
[DllImport(LibInput)]
public extern static IntPtr libinput_path_remove_device(IntPtr device);

[DllImport(LibInput)]
public extern static int libinput_device_config_calibration_set_matrix(IntPtr device, float[] matrix);

[DllImport(LibInput)]
public extern static int libinput_get_fd(IntPtr ctx);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ public TopLevel? TopLevel
{
get
{
EnsureTopLevel();
if (_topLevel == null)
{
EnsureTopLevel();
}
return _topLevel;
}
}
Expand Down
15 changes: 13 additions & 2 deletions src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Skia;
using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
using static Avalonia.LinuxFramebuffer.Output.LibDrm;

Expand All @@ -17,14 +18,22 @@ public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface
{
private DrmOutputOptions _outputOptions = new();
private DrmCard _card;
public PixelSize PixelSize => _mode.Resolution;
public PixelSize PixelSize => _mode.Resolution;

public double Scaling
{
get => _outputOptions.Scaling;
set => _outputOptions.Scaling = value;
}

// implements ISurfaceOrientation
public SurfaceOrientation Orientation
{
get => _outputOptions.Orientation;
set => _outputOptions.Orientation = value;
}


class SharedContextGraphics : IPlatformGraphics
{
private readonly IPlatformGraphicsContext _context;
Expand Down Expand Up @@ -275,7 +284,7 @@ public void Dispose()
// We are wrapping GBM buffer chain associated with CRTC, and don't free it on a whim
}

class RenderSession : IGlPlatformSurfaceRenderingSession
class RenderSession : IGlPlatformSurfaceRenderingSession, ISurfaceOrientation
{
private readonly DrmOutput _parent;
private readonly IDisposable _clearContext;
Expand Down Expand Up @@ -338,6 +347,8 @@ public void Dispose()
public double Scaling => _parent.Scaling;

public bool IsYFlipped => false;

public SurfaceOrientation Orientation { get => _parent.Orientation; set => _parent.Orientation = value; }
}

public IGlPlatformSurfaceRenderingSession BeginDraw()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Avalonia.Platform;
using Avalonia.Skia;

namespace Avalonia.LinuxFramebuffer.Output;

Expand Down Expand Up @@ -28,6 +29,12 @@ public class FbDevOutputOptions
/// </summary>
public double Scaling { get; set; } = 1;

/// <summary>
/// The orientation of the screen relative to the frame buffer memory orientation
/// Default: Normal
/// </summary>
public SurfaceOrientation Orientation { get; set; } = SurfaceOrientation.Normal;

/// <summary>
/// If set to true, FBIO_WAITFORVSYNC ioctl and following memcpy call will run on a dedicated thread
/// saving current one from doing nothing in a blocking call
Expand Down
6 changes: 6 additions & 0 deletions src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Platform;
using Avalonia.Skia;

namespace Avalonia.LinuxFramebuffer
{
Expand All @@ -20,6 +21,9 @@ public sealed unsafe class FbdevOutput : IFramebufferPlatformSurface, IDisposabl
private bool _lockedAtLeastOnce;
public double Scaling { get; set; }

// implements ISurfaceOrientation
public SurfaceOrientation Orientation { get; set; }

/// <summary>
/// Create a Linux frame buffer device output
/// </summary>
Expand Down Expand Up @@ -58,6 +62,8 @@ public FbdevOutput(FbDevOutputOptions options)
throw new Exception("Error: " + Marshal.GetLastWin32Error());
_options = options;
Scaling = options.Scaling;
Orientation = options.Orientation;

try
{
Init(options.PixelFormat);
Expand Down
4 changes: 3 additions & 1 deletion src/Linux/Avalonia.LinuxFramebuffer/Output/IOutputBackend.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Avalonia.Skia;

namespace Avalonia.LinuxFramebuffer.Output
{
public interface IOutputBackend
public interface IOutputBackend : ISurfaceOrientation
{
PixelSize PixelSize { get; }
double Scaling { get; set; }
Expand Down
14 changes: 8 additions & 6 deletions src/Skia/Avalonia.Skia/DrawingContextImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal partial class DrawingContextImpl : IDrawingContextImpl,
private readonly Matrix? _postTransform;
private double _currentOpacity = 1.0f;
private readonly bool _disableSubpixelTextRendering;
private Matrix _baseTransform; // default canvas rotation
private Matrix? _currentTransform;
private bool _disposed;
private GRContext? _grContext;
Expand Down Expand Up @@ -187,6 +188,7 @@ public DrawingContextImpl(CreateInfo createInfo, params IDisposable?[]? disposab
Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas
?? throw new ArgumentException("Invalid create info - no Canvas provided", nameof(createInfo));

_baseTransform = Canvas.TotalMatrix.ToAvaloniaMatrix();
_intermediateSurfaceDpi = createInfo.Dpi;
_disposables = disposables;
_disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering;
Expand Down Expand Up @@ -374,7 +376,7 @@ public void DrawRectangle(IExperimentalAcrylicMaterial? material, RoundedRect re
if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
return;
CheckLease();

var rc = rect.Rect.ToSKRect();
SKRoundRect? skRoundRect = null;

Expand Down Expand Up @@ -542,7 +544,7 @@ public void DrawRegion(IBrush? brush, IPen? pen, IPlatformRenderInterfaceRegion
if(r.IsEmpty)
return;
CheckLease();

if (brush != null)
{
using (var fill = CreatePaint(_fillPaint, brush, r.Bounds.ToRectUnscaled()))
Expand All @@ -567,7 +569,7 @@ public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
if (rect.Height <= 0 || rect.Width <= 0)
return;
CheckLease();

var rc = rect.ToSKRect();

if (brush != null)
Expand Down Expand Up @@ -677,7 +679,7 @@ private void RestoreCanvas()
_currentTransform = null;
Canvas.Restore();
}

/// <inheritdoc />
public void PopClip()
{
Expand Down Expand Up @@ -844,7 +846,7 @@ public void PopOpacityMask()
/// <inheritdoc />
public Matrix Transform
{
get { return _currentTransform ??= Canvas.TotalMatrix.ToAvaloniaMatrix(); }
get { return _currentTransform ??= _baseTransform.Invert() * Canvas.TotalMatrix.ToAvaloniaMatrix(); }
set
{
CheckLease();
Expand All @@ -860,7 +862,7 @@ public Matrix Transform
transform *= _postTransform.Value;
}

Canvas.SetMatrix(transform.ToSKMatrix());
Canvas.SetMatrix((_baseTransform * transform).ToSKMatrix());
}
}

Expand Down
Loading
Loading