From 16ea1f37621b0400c96e5f4085195ce4a4573b53 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 7 Oct 2018 22:05:00 +0300 Subject: [PATCH 01/86] [X11] Platform threading interface and platform sanity checks --- Avalonia.sln | 54 ++++ samples/PlatformSanityChecks/App.xaml | 6 + samples/PlatformSanityChecks/App.xaml.cs | 13 + .../PlatformSanityChecks.csproj | 24 ++ samples/PlatformSanityChecks/Program.cs | 140 ++++++++++ src/Avalonia.X11/Avalonia.X11.csproj | 12 + src/Avalonia.X11/X11Exception.cs | 12 + src/Avalonia.X11/X11Platform.cs | 39 +++ src/Avalonia.X11/X11PlatformThreading.cs | 251 ++++++++++++++++++ src/Avalonia.X11/XLib.cs | 105 ++++++++ 10 files changed, 656 insertions(+) create mode 100644 samples/PlatformSanityChecks/App.xaml create mode 100644 samples/PlatformSanityChecks/App.xaml.cs create mode 100644 samples/PlatformSanityChecks/PlatformSanityChecks.csproj create mode 100644 samples/PlatformSanityChecks/Program.cs create mode 100644 src/Avalonia.X11/Avalonia.X11.csproj create mode 100644 src/Avalonia.X11/X11Exception.cs create mode 100644 src/Avalonia.X11/X11Platform.cs create mode 100644 src/Avalonia.X11/X11PlatformThreading.cs create mode 100644 src/Avalonia.X11/XLib.cs diff --git a/Avalonia.sln b/Avalonia.sln index f71a94888d..388dbb6e6a 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -188,6 +188,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{212D02D5-C873-469A-8E78-4A6350EC4A1A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{8B5768BB-71F9-4E23-89B5-DDBA6458B856}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -1689,6 +1693,54 @@ Global {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.Build.0 = Release|Any CPU {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhone.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhone.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|Any CPU.Build.0 = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhone.ActiveCfg = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhone.Build.0 = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhone.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhone.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|Any CPU.Build.0 = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhone.ActiveCfg = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhone.Build.0 = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1742,6 +1794,8 @@ Global {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2} {4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {212D02D5-C873-469A-8E78-4A6350EC4A1A} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} + {8B5768BB-71F9-4E23-89B5-DDBA6458B856} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/samples/PlatformSanityChecks/App.xaml b/samples/PlatformSanityChecks/App.xaml new file mode 100644 index 0000000000..25bab6ae35 --- /dev/null +++ b/samples/PlatformSanityChecks/App.xaml @@ -0,0 +1,6 @@ + + + + + + diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs new file mode 100644 index 0000000000..508fc1e34b --- /dev/null +++ b/samples/PlatformSanityChecks/App.xaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Markup.Xaml; + +namespace PlatformSanityChecks +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj new file mode 100644 index 0000000000..00b5b10106 --- /dev/null +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -0,0 +1,24 @@ + + + + Exe + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + diff --git a/samples/PlatformSanityChecks/Program.cs b/samples/PlatformSanityChecks/Program.cs new file mode 100644 index 0000000000..8a3aa82981 --- /dev/null +++ b/samples/PlatformSanityChecks/Program.cs @@ -0,0 +1,140 @@ +using System; +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Runtime.CompilerServices; +using System.Threading; +using Avalonia; +using Avalonia.Platform; +using Avalonia.Threading; +using Avalonia.X11; + +namespace PlatformSanityChecks +{ + public class Program + { + static Thread UiThread; + + static void Main(string[] args) + { + UiThread = Thread.CurrentThread; + AppBuilder.Configure().RuntimePlatformServicesInitializer(); + var app = new App(); + + new AvaloniaX11Platform().Initialize(); + + CheckPlatformThreading(); + + + + } + + static bool CheckAccess() => UiThread == Thread.CurrentThread; + + static void VerifyAccess() + { + if (!CheckAccess()) + Die("Call from invalid thread"); + } + + static Exception Die(string error) + { + Console.Error.WriteLine(error); + Console.Error.WriteLine(Environment.StackTrace); + Process.GetCurrentProcess().Kill(); + throw new Exception(error); + } + + static IDisposable Enter([CallerMemberName] string caller = null) + { + Console.WriteLine("Entering " + caller); + return Disposable.Create(() => { Console.WriteLine("Leaving " + caller); }); + } + + static void EnterLoop(Action cb, [CallerMemberName] string caller = null) + { + using (Enter(caller)) + { + var cts = new CancellationTokenSource(); + cb(cts); + Dispatcher.UIThread.MainLoop(cts.Token); + if (!cts.IsCancellationRequested) + Die("Unexpected loop exit"); + } + } + + static void CheckTimerOrdering() => EnterLoop(cts => + { + bool firstFired = false, secondFired = false; + DispatcherTimer.Run(() => + { + Console.WriteLine("Second tick"); + VerifyAccess(); + if (!firstFired) + throw Die("Invalid timer ordering"); + if (secondFired) + throw Die("Invocation of finished timer"); + secondFired = true; + cts.Cancel(); + return false; + }, TimeSpan.FromSeconds(2)); + DispatcherTimer.Run(() => + { + Console.WriteLine("First tick"); + VerifyAccess(); + if (secondFired) + throw Die("Invalid timer ordering"); + if (firstFired) + throw Die("Invocation of finished timer"); + firstFired = true; + return false; + }, TimeSpan.FromSeconds(1)); + }); + + static void CheckTimerTicking() => EnterLoop(cts => + { + int ticks = 0; + var st = Stopwatch.StartNew(); + DispatcherTimer.Run(() => + { + ticks++; + Console.WriteLine($"Tick {ticks} at {st.Elapsed}"); + if (ticks == 5) + { + if (st.Elapsed.TotalSeconds < 4.5) + Die("Timer is too fast"); + if (st.Elapsed.TotalSeconds > 6) + Die("Timer is too slow"); + cts.Cancel(); + return false; + } + + return true; + }, TimeSpan.FromSeconds(1)); + + + }); + + + static void CheckSignaling() => EnterLoop(cts => + { + ThreadPool.QueueUserWorkItem(_ => + { + Thread.Sleep(100); + Dispatcher.UIThread.Post(() => + { + VerifyAccess(); + cts.Cancel(); + }); + }); + }); + + static void CheckPlatformThreading() + { + CheckSignaling(); + CheckTimerOrdering(); + CheckTimerTicking(); + + } + + } +} diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj new file mode 100644 index 0000000000..a035febe5b --- /dev/null +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + true + + + + + + + diff --git a/src/Avalonia.X11/X11Exception.cs b/src/Avalonia.X11/X11Exception.cs new file mode 100644 index 0000000000..2ac5a31d9b --- /dev/null +++ b/src/Avalonia.X11/X11Exception.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.X11 +{ + public class X11Exception : Exception + { + public X11Exception(string message) : base(message) + { + + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs new file mode 100644 index 0000000000..c266433eef --- /dev/null +++ b/src/Avalonia.X11/X11Platform.cs @@ -0,0 +1,39 @@ +using System; +using Avalonia.Controls; +using Avalonia.Platform; +using Avalonia.X11; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + public class AvaloniaX11Platform + { + public void Initialize() + { + Display = XOpenDisplay(IntPtr.Zero); + DeferredDisplay = XOpenDisplay(IntPtr.Zero); + if (Display == IntPtr.Zero) + throw new Exception("XOpenDisplay failed"); + + + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(new X11PlatformThreading(Display)); + + } + + public IntPtr DeferredDisplay { get; set; } + public IntPtr Display { get; set; } + } +} + +namespace Avalonia +{ + public static class AvaloniaX11PlatformExtensions + { + public static T UseX11(this T builder) where T : AppBuilderBase, new() + { + builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); + return builder; + } + } + +} diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs new file mode 100644 index 0000000000..18b08085e3 --- /dev/null +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Avalonia.Platform; +using Avalonia.Threading; +using static Avalonia.X11.XLib; + +namespace Avalonia.X11 +{ + public unsafe class X11PlatformThreading : IPlatformThreadingInterface + { + private readonly IntPtr _display; + private Thread _mainThread; + + [StructLayout(LayoutKind.Explicit)] + struct epoll_data + { + [FieldOffset(0)] + public IntPtr ptr; + [FieldOffset(0)] + public int fd; + [FieldOffset(0)] + public uint u32; + [FieldOffset(0)] + public ulong u64; + } + + private const int EPOLLIN = 1; + private const int EPOLL_CTL_ADD = 1; + private const int O_NONBLOCK = 2048; + + [StructLayout(LayoutKind.Sequential)] + struct epoll_event + { + public uint events; + public epoll_data data; + } + + [DllImport("libc")] + extern static int epoll_create1(int size); + + [DllImport("libc")] + extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event); + + [DllImport("libc")] + extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout); + + [DllImport("libc")] + extern static int pipe2(int* fds, int flags); + [DllImport("libc")] + extern static IntPtr write(int fd, void* buf, IntPtr count); + + [DllImport("libc")] + extern static IntPtr read(int fd, void* buf, IntPtr count); + + enum EventCodes + { + X11 = 1, + Signal =2 + } + + private int _sigread, _sigwrite; + private object _lock = new object(); + private bool _signaled; + private DispatcherPriority _signaledPriority; + private int _epoll; + private Stopwatch _clock = Stopwatch.StartNew(); + + class X11Timer : IDisposable + { + private readonly X11PlatformThreading _parent; + + public X11Timer(X11PlatformThreading parent, DispatcherPriority prio, TimeSpan interval, Action tick) + { + _parent = parent; + Priority = prio; + Tick = tick; + Interval = interval; + Reschedule(); + } + + public DispatcherPriority Priority { get; } + public TimeSpan NextTick { get; private set; } + public TimeSpan Interval { get; } + public Action Tick { get; } + public bool Disposed { get; private set; } + + public void Reschedule() + { + NextTick = _parent._clock.Elapsed + Interval; + } + + public void Dispose() + { + Disposed = true; + lock (_parent._lock) + _parent._timers.Remove(this); + } + } + + List _timers = new List(); + + public X11PlatformThreading(IntPtr display) + { + _display = display; + _mainThread = Thread.CurrentThread; + var fd = XLib.XConnectionNumber(display); + var ev = new epoll_event() + { + events = EPOLLIN, + data = {u32 = (int)EventCodes.X11} + }; + _epoll = epoll_create1(0); + if (_epoll == -1) + throw new X11Exception("epoll_create1 failed"); + + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, ref ev) == -1) + throw new X11Exception("Unable to attach X11 connection handle to epoll"); + + var fds = stackalloc int[2]; + pipe2(fds, O_NONBLOCK); + _sigread = fds[0]; + _sigwrite = fds[1]; + + ev = new epoll_event + { + events = EPOLLIN, + data = {u32 = (int)EventCodes.Signal} + }; + if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1) + throw new X11Exception("Unable to attach signal pipe to epoll"); + } + + int TimerComparer(X11Timer t1, X11Timer t2) + { + return t2.Priority - t1.Priority; + } + + public void RunLoop(CancellationToken cancellationToken) + { + var readyTimers = new List(); + while (!cancellationToken.IsCancellationRequested) + { + var now = _clock.Elapsed; + TimeSpan? nextTick = null; + readyTimers.Clear(); + lock(_timers) + foreach (var t in _timers) + { + if (nextTick == null || t.NextTick < nextTick.Value) + nextTick = t.NextTick; + if (t.NextTick < now) + readyTimers.Add(t); + } + + readyTimers.Sort(TimerComparer); + + foreach (var t in readyTimers) + { + if (cancellationToken.IsCancellationRequested) + return; + t.Tick(); + if(!t.Disposed) + { + t.Reschedule(); + if (nextTick == null || t.NextTick < nextTick.Value) + nextTick = t.NextTick; + } + } + + if (cancellationToken.IsCancellationRequested) + return; + epoll_event ev; + var len = epoll_wait(_epoll, &ev, 1, + nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds)); + if (cancellationToken.IsCancellationRequested) + return; + if (len == 0) + { + // We handle timer-related stuff at the beginning of the loop + continue; + } + else + { + if (ev.data.u32 == (int)EventCodes.Signal) + { + int buf = 0; + while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0) + { + } + + DispatcherPriority prio; + lock (_lock) + { + _signaled = false; + prio = _signaledPriority; + _signaledPriority = DispatcherPriority.MinValue; + } + Signaled?.Invoke(prio); + } + else + { + while (XPending(_display)) + { + if (cancellationToken.IsCancellationRequested) + return; + XNextEvent(_display, out var xev); + } + } + } + } + } + + + + public void Signal(DispatcherPriority priority) + { + lock (_lock) + { + if (priority > _signaledPriority) + _signaledPriority = priority; + + if(_signaled) + return; + _signaled = true; + int buf = 0; + write(_sigwrite, &buf, new IntPtr(1)); + } + } + + public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread; + public event Action Signaled; + + public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) + { + if (_mainThread != Thread.CurrentThread) + throw new InvalidOperationException("StartTimer can be only called from UI thread"); + if (interval <= TimeSpan.Zero) + throw new ArgumentException("Interval must be positive", nameof(interval)); + + // We assume that we are on the main thread and outside of epoll_wait, so there is no need for wakeup signal + + var timer = new X11Timer(this, priority, interval, tick); + lock(_timers) + _timers.Add(timer); + return timer; + } + } +} diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs new file mode 100644 index 0000000000..88d36091ca --- /dev/null +++ b/src/Avalonia.X11/XLib.cs @@ -0,0 +1,105 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.X11 +{ + public static class XLib + { + + [DllImport("libX11.so.6")] + public static extern IntPtr XInitThreads(); + + [DllImport("libX11.so.6")] + public static extern IntPtr XOpenDisplay(IntPtr name); + + [DllImport("libX11.so.6")] + public static extern int XConnectionNumber(IntPtr display); + + [DllImport("libX11.so.6")] + public static extern IntPtr XLockDisplay(IntPtr display); + + [DllImport("libX11.so.6")] + public static extern IntPtr XUnlockDisplay(IntPtr display); + + [DllImport("libX11.so.6")] + public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); + + [DllImport("libX11.so.6")] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); + + [DllImport("libX11.so.6")] + public static extern int XInitImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern int XDestroyImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern IntPtr XSetErrorHandler(XErrorHandler handler); + + [DllImport("libX11.so.6")] + public static extern int XSync(IntPtr display, bool discard); + + [DllImport("libX11.so.6")] + public static extern int XNextEvent(IntPtr display, out XEvent ev); + + [DllImport("libX11.so.6")] + public static extern bool XPending(IntPtr display); + + [StructLayout(LayoutKind.Sequential)] + public struct XAnyEvent + { + + public int Type; + public ulong Serial; /* # of last request processed by server */ + public bool send_event; /* true if this came from a SendEvent request */ + public IntPtr display; /* Display the event was read from */ + public IntPtr window; /* window on which event was requested in event mask */ + } + + [StructLayout(LayoutKind.Explicit)] + public unsafe struct XEvent + { + [FieldOffset(0)] public int Type; + [FieldOffset(0)] public XAnyEvent XAny; + [FieldOffset(0)] private fixed int pad[40]; + } + + public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error); + + [DllImport("libX11.so.6")] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct XErrorEvent + { + public int type; + public IntPtr* display; /* Display the event was read from */ + public ulong serial; /* serial number of failed request */ + public byte error_code; /* error code of failed request */ + public byte request_code; /* Major op-code of failed request */ + public byte minor_code; /* Minor op-code of failed request */ + public IntPtr resourceid; /* resource id */ + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct XImage + { + public int width, height; /* size of image */ + public int xoffset; /* number of pixels offset in X direction */ + public int format; /* XYBitmap, XYPixmap, ZPixmap */ + public IntPtr data; /* pointer to image data */ + public int byte_order; /* data byte order, LSBFirst, MSBFirst */ + public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ + public int bitmap_bit_order; /* LSBFirst, MSBFirst */ + public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ + public int depth; /* depth of image */ + public int bytes_per_line; /* accelerator to next scanline */ + public int bits_per_pixel; /* bits per pixel (ZPixmap) */ + public ulong red_mask; /* bits in z arrangement */ + public ulong green_mask; + public ulong blue_mask; + private fixed byte funcs[128]; + } + } +} From 666efd311b4db57b2648725be64847230d5973c5 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 23 Oct 2018 18:06:26 +0300 Subject: [PATCH 02/86] [X11] Initial windowing and mouse events --- samples/PlatformSanityChecks/Program.cs | 2 +- src/Avalonia.Input/MouseDevice.cs | 4 +- src/Avalonia.Input/Raw/RawInputEventArgs.cs | 4 +- src/Avalonia.Input/Raw/RawKeyEventArgs.cs | 2 +- src/Avalonia.Input/Raw/RawMouseEventArgs.cs | 2 +- .../Raw/RawMouseWheelEventArgs.cs | 2 +- .../Raw/RawTextInputEventArgs.cs | 2 +- src/Avalonia.X11/Avalonia.X11.csproj | 1 + src/Avalonia.X11/Stubs.cs | 103 + src/Avalonia.X11/X11Atoms.cs | 341 ++++ src/Avalonia.X11/X11Enums.cs | 110 + src/Avalonia.X11/X11Framebuffer.cs | 57 + src/Avalonia.X11/X11FramebufferSurface.cs | 27 + src/Avalonia.X11/X11Info.cs | 35 + src/Avalonia.X11/X11Platform.cs | 47 +- src/Avalonia.X11/X11PlatformThreading.cs | 24 +- src/Avalonia.X11/X11Structs.cs | 1815 +++++++++++++++++ src/Avalonia.X11/X11Window.cs | 492 +++++ src/Avalonia.X11/XError.cs | 30 + src/Avalonia.X11/XLib.cs | 468 ++++- 20 files changed, 3474 insertions(+), 94 deletions(-) create mode 100644 src/Avalonia.X11/Stubs.cs create mode 100644 src/Avalonia.X11/X11Atoms.cs create mode 100644 src/Avalonia.X11/X11Enums.cs create mode 100644 src/Avalonia.X11/X11Framebuffer.cs create mode 100644 src/Avalonia.X11/X11FramebufferSurface.cs create mode 100644 src/Avalonia.X11/X11Info.cs create mode 100644 src/Avalonia.X11/X11Structs.cs create mode 100644 src/Avalonia.X11/X11Window.cs create mode 100644 src/Avalonia.X11/XError.cs diff --git a/samples/PlatformSanityChecks/Program.cs b/samples/PlatformSanityChecks/Program.cs index 8a3aa82981..3fdc9b1e77 100644 --- a/samples/PlatformSanityChecks/Program.cs +++ b/samples/PlatformSanityChecks/Program.cs @@ -20,7 +20,7 @@ namespace PlatformSanityChecks AppBuilder.Configure().RuntimePlatformServicesInitializer(); var app = new App(); - new AvaloniaX11Platform().Initialize(); + AvaloniaX11PlatformExtensions.InitializeX11Platform(); CheckPlatformThreading(); diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index e581772978..e01dedeede 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -18,7 +18,7 @@ namespace Avalonia.Input { private int _clickCount; private Rect _lastClickRect; - private uint _lastClickTime; + private ulong _lastClickTime; private IInputElement _captured; private IDisposable _capturedSubscription; @@ -152,7 +152,7 @@ namespace Avalonia.Input ClearPointerOver(this, root); } - private bool MouseDown(IMouseDevice device, uint timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) + private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) { Contract.Requires(device != null); Contract.Requires(root != null); diff --git a/src/Avalonia.Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Input/Raw/RawInputEventArgs.cs index 041c361d30..78c1b58624 100644 --- a/src/Avalonia.Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawInputEventArgs.cs @@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw /// /// The associated device. /// The event timestamp. - public RawInputEventArgs(IInputDevice device, uint timestamp) + public RawInputEventArgs(IInputDevice device, ulong timestamp) { Contract.Requires(device != null); @@ -47,6 +47,6 @@ namespace Avalonia.Input.Raw /// /// Gets the timestamp associated with the event. /// - public uint Timestamp { get; private set; } + public ulong Timestamp { get; private set; } } } diff --git a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs index 0c208b4596..044f244138 100644 --- a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input.Raw { public RawKeyEventArgs( IKeyboardDevice device, - uint timestamp, + ulong timestamp, RawKeyEventType type, Key key, InputModifiers modifiers) : base(device, timestamp) diff --git a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseEventArgs.cs index 914833624f..c5637d66cc 100644 --- a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawMouseEventArgs.cs @@ -35,7 +35,7 @@ namespace Avalonia.Input.Raw /// The input modifiers. public RawMouseEventArgs( IInputDevice device, - uint timestamp, + ulong timestamp, IInputRoot root, RawMouseEventType type, Point position, diff --git a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs index f4078af9e1..d2e5faab6c 100644 --- a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs @@ -8,7 +8,7 @@ namespace Avalonia.Input.Raw { public RawMouseWheelEventArgs( IInputDevice device, - uint timestamp, + ulong timestamp, IInputRoot root, Point position, Vector delta, InputModifiers inputModifiers) diff --git a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs index 6e427c3751..0d1e5d2422 100644 --- a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input.Raw { public string Text { get; set; } - public RawTextInputEventArgs(IKeyboardDevice device, uint timestamp, string text) : base(device, timestamp) + public RawTextInputEventArgs(IKeyboardDevice device, ulong timestamp, string text) : base(device, timestamp) { Text = text; } diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index a035febe5b..3f61961571 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs new file mode 100644 index 0000000000..ed14ab7878 --- /dev/null +++ b/src/Avalonia.X11/Stubs.cs @@ -0,0 +1,103 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +namespace Avalonia.X11 +{ + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + return new PlatformHandle(IntPtr.Zero, "FAKE"); + } + } + + class ClipboardStub : IClipboard + { + private string _text; + public Task GetTextAsync() + { + return Task.FromResult(_text); + } + + public Task SetTextAsync(string text) + { + _text = text; + return Task.CompletedTask; + } + + public Task ClearAsync() + { + _text = null; + return Task.CompletedTask; + } + } + + class PlatformSettingsStub : IPlatformSettings + { + public Size DoubleClickSize { get; } = new Size(2, 2); + public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + } + + class SystemDialogsStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + return Task.FromResult((string[])null); + } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + return Task.FromResult((string)null); + } + } + + class IconLoaderStub : IPlatformIconLoader + { + class FakeIcon : IWindowIconImpl + { + private readonly byte[] _data; + + public FakeIcon(byte[] data) + { + _data = data; + } + public void Save(Stream outputStream) + { + outputStream.Write(_data, 0, _data.Length); + } + } + + public IWindowIconImpl LoadIcon(string fileName) + { + return new FakeIcon(File.ReadAllBytes(fileName)); + } + + public IWindowIconImpl LoadIcon(Stream stream) + { + var ms = new MemoryStream(); + stream.CopyTo(ms); + return new FakeIcon(ms.ToArray()); + } + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms); + return new FakeIcon(ms.ToArray()); + } + } + + class ScreenStub : IScreenImpl + { + public int ScreenCount { get; } = 1; + + public Screen[] AllScreens { get; } = + {new Screen(new Rect(0, 0, 1920, 1280), new Rect(0, 0, 1920, 1280), true)}; + } +} diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs new file mode 100644 index 0000000000..e7b83f59d3 --- /dev/null +++ b/src/Avalonia.X11/X11Atoms.cs @@ -0,0 +1,341 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2006 Novell, Inc. (http://www.novell.com) +// +// + +using System; +using static Avalonia.X11.XLib; +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable IdentifierTypo +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable CommentTypo +// ReSharper disable ArrangeThisQualifier +// ReSharper disable NotAccessedField.Global +// ReSharper disable InconsistentNaming +// ReSharper disable StringLiteralTypo +#pragma warning disable 649 + +namespace Avalonia.X11 { + + internal class X11Atoms { + + // Our atoms + public readonly IntPtr AnyPropertyType = (IntPtr)0; + public readonly IntPtr XA_PRIMARY = (IntPtr)1; + public readonly IntPtr XA_SECONDARY = (IntPtr)2; + public readonly IntPtr XA_ARC = (IntPtr)3; + public readonly IntPtr XA_ATOM = (IntPtr)4; + public readonly IntPtr XA_BITMAP = (IntPtr)5; + public readonly IntPtr XA_CARDINAL = (IntPtr)6; + public readonly IntPtr XA_COLORMAP = (IntPtr)7; + public readonly IntPtr XA_CURSOR = (IntPtr)8; + public readonly IntPtr XA_CUT_BUFFER0 = (IntPtr)9; + public readonly IntPtr XA_CUT_BUFFER1 = (IntPtr)10; + public readonly IntPtr XA_CUT_BUFFER2 = (IntPtr)11; + public readonly IntPtr XA_CUT_BUFFER3 = (IntPtr)12; + public readonly IntPtr XA_CUT_BUFFER4 = (IntPtr)13; + public readonly IntPtr XA_CUT_BUFFER5 = (IntPtr)14; + public readonly IntPtr XA_CUT_BUFFER6 = (IntPtr)15; + public readonly IntPtr XA_CUT_BUFFER7 = (IntPtr)16; + public readonly IntPtr XA_DRAWABLE = (IntPtr)17; + public readonly IntPtr XA_FONT = (IntPtr)18; + public readonly IntPtr XA_INTEGER = (IntPtr)19; + public readonly IntPtr XA_PIXMAP = (IntPtr)20; + public readonly IntPtr XA_POINT = (IntPtr)21; + public readonly IntPtr XA_RECTANGLE = (IntPtr)22; + public readonly IntPtr XA_RESOURCE_MANAGER = (IntPtr)23; + public readonly IntPtr XA_RGB_COLOR_MAP = (IntPtr)24; + public readonly IntPtr XA_RGB_BEST_MAP = (IntPtr)25; + public readonly IntPtr XA_RGB_BLUE_MAP = (IntPtr)26; + public readonly IntPtr XA_RGB_DEFAULT_MAP = (IntPtr)27; + public readonly IntPtr XA_RGB_GRAY_MAP = (IntPtr)28; + public readonly IntPtr XA_RGB_GREEN_MAP = (IntPtr)29; + public readonly IntPtr XA_RGB_RED_MAP = (IntPtr)30; + public readonly IntPtr XA_STRING = (IntPtr)31; + public readonly IntPtr XA_VISUALID = (IntPtr)32; + public readonly IntPtr XA_WINDOW = (IntPtr)33; + public readonly IntPtr XA_WM_COMMAND = (IntPtr)34; + public readonly IntPtr XA_WM_HINTS = (IntPtr)35; + public readonly IntPtr XA_WM_CLIENT_MACHINE = (IntPtr)36; + public readonly IntPtr XA_WM_ICON_NAME = (IntPtr)37; + public readonly IntPtr XA_WM_ICON_SIZE = (IntPtr)38; + public readonly IntPtr XA_WM_NAME = (IntPtr)39; + public readonly IntPtr XA_WM_NORMAL_HINTS = (IntPtr)40; + public readonly IntPtr XA_WM_SIZE_HINTS = (IntPtr)41; + public readonly IntPtr XA_WM_ZOOM_HINTS = (IntPtr)42; + public readonly IntPtr XA_MIN_SPACE = (IntPtr)43; + public readonly IntPtr XA_NORM_SPACE = (IntPtr)44; + public readonly IntPtr XA_MAX_SPACE = (IntPtr)45; + public readonly IntPtr XA_END_SPACE = (IntPtr)46; + public readonly IntPtr XA_SUPERSCRIPT_X = (IntPtr)47; + public readonly IntPtr XA_SUPERSCRIPT_Y = (IntPtr)48; + public readonly IntPtr XA_SUBSCRIPT_X = (IntPtr)49; + public readonly IntPtr XA_SUBSCRIPT_Y = (IntPtr)50; + public readonly IntPtr XA_UNDERLINE_POSITION = (IntPtr)51; + public readonly IntPtr XA_UNDERLINE_THICKNESS = (IntPtr)52; + public readonly IntPtr XA_STRIKEOUT_ASCENT = (IntPtr)53; + public readonly IntPtr XA_STRIKEOUT_DESCENT = (IntPtr)54; + public readonly IntPtr XA_ITALIC_ANGLE = (IntPtr)55; + public readonly IntPtr XA_X_HEIGHT = (IntPtr)56; + public readonly IntPtr XA_QUAD_WIDTH = (IntPtr)57; + public readonly IntPtr XA_WEIGHT = (IntPtr)58; + public readonly IntPtr XA_POINT_SIZE = (IntPtr)59; + public readonly IntPtr XA_RESOLUTION = (IntPtr)60; + public readonly IntPtr XA_COPYRIGHT = (IntPtr)61; + public readonly IntPtr XA_NOTICE = (IntPtr)62; + public readonly IntPtr XA_FONT_NAME = (IntPtr)63; + public readonly IntPtr XA_FAMILY_NAME = (IntPtr)64; + public readonly IntPtr XA_FULL_NAME = (IntPtr)65; + public readonly IntPtr XA_CAP_HEIGHT = (IntPtr)66; + public readonly IntPtr XA_WM_CLASS = (IntPtr)67; + public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68; + + public readonly IntPtr WM_PROTOCOLS; + public readonly IntPtr WM_DELETE_WINDOW; + public readonly IntPtr WM_TAKE_FOCUS; + public readonly IntPtr _NET_SUPPORTED; + public readonly IntPtr _NET_CLIENT_LIST; + public readonly IntPtr _NET_NUMBER_OF_DESKTOPS; + public readonly IntPtr _NET_DESKTOP_GEOMETRY; + public readonly IntPtr _NET_DESKTOP_VIEWPORT; + public readonly IntPtr _NET_CURRENT_DESKTOP; + public readonly IntPtr _NET_DESKTOP_NAMES; + public readonly IntPtr _NET_ACTIVE_WINDOW; + public readonly IntPtr _NET_WORKAREA; + public readonly IntPtr _NET_SUPPORTING_WM_CHECK; + public readonly IntPtr _NET_VIRTUAL_ROOTS; + public readonly IntPtr _NET_DESKTOP_LAYOUT; + public readonly IntPtr _NET_SHOWING_DESKTOP; + public readonly IntPtr _NET_CLOSE_WINDOW; + public readonly IntPtr _NET_MOVERESIZE_WINDOW; + public readonly IntPtr _NET_WM_MOVERESIZE; + public readonly IntPtr _NET_RESTACK_WINDOW; + public readonly IntPtr _NET_REQUEST_FRAME_EXTENTS; + public readonly IntPtr _NET_WM_NAME; + public readonly IntPtr _NET_WM_VISIBLE_NAME; + public readonly IntPtr _NET_WM_ICON_NAME; + public readonly IntPtr _NET_WM_VISIBLE_ICON_NAME; + public readonly IntPtr _NET_WM_DESKTOP; + public readonly IntPtr _NET_WM_WINDOW_TYPE; + public readonly IntPtr _NET_WM_STATE; + public readonly IntPtr _NET_WM_ALLOWED_ACTIONS; + public readonly IntPtr _NET_WM_STRUT; + public readonly IntPtr _NET_WM_STRUT_PARTIAL; + public readonly IntPtr _NET_WM_ICON_GEOMETRY; + public readonly IntPtr _NET_WM_ICON; + public readonly IntPtr _NET_WM_PID; + public readonly IntPtr _NET_WM_HANDLED_ICONS; + public readonly IntPtr _NET_WM_USER_TIME; + public readonly IntPtr _NET_FRAME_EXTENTS; + public readonly IntPtr _NET_WM_PING; + public readonly IntPtr _NET_WM_SYNC_REQUEST; + public readonly IntPtr _NET_SYSTEM_TRAY_S; + public readonly IntPtr _NET_SYSTEM_TRAY_ORIENTATION; + public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; + public readonly IntPtr _NET_WM_STATE_MAXIMIZED_HORZ; + public readonly IntPtr _NET_WM_STATE_MAXIMIZED_VERT; + public readonly IntPtr _XEMBED; + public readonly IntPtr _XEMBED_INFO; + public readonly IntPtr _MOTIF_WM_HINTS; + public readonly IntPtr _NET_WM_STATE_SKIP_TASKBAR; + public readonly IntPtr _NET_WM_STATE_ABOVE; + public readonly IntPtr _NET_WM_STATE_MODAL; + public readonly IntPtr _NET_WM_STATE_HIDDEN; + public readonly IntPtr _NET_WM_CONTEXT_HELP; + public readonly IntPtr _NET_WM_WINDOW_OPACITY; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DESKTOP; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DOCK; + public readonly IntPtr _NET_WM_WINDOW_TYPE_TOOLBAR; + public readonly IntPtr _NET_WM_WINDOW_TYPE_MENU; + public readonly IntPtr _NET_WM_WINDOW_TYPE_UTILITY; + public readonly IntPtr _NET_WM_WINDOW_TYPE_SPLASH; + public readonly IntPtr _NET_WM_WINDOW_TYPE_DIALOG; + public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL; + public readonly IntPtr CLIPBOARD; + public readonly IntPtr PRIMARY; + public readonly IntPtr DIB; + public readonly IntPtr OEMTEXT; + public readonly IntPtr UNICODETEXT; + public readonly IntPtr TARGETS; + public readonly IntPtr PostAtom; + public readonly IntPtr HoverState; + public readonly IntPtr AsyncAtom; + + + public X11Atoms (IntPtr display) { + + // make sure this array stays in sync with the statements below + string [] atom_names = new string[] { + "WM_PROTOCOLS", + "WM_DELETE_WINDOW", + "WM_TAKE_FOCUS", + "_NET_SUPPORTED", + "_NET_CLIENT_LIST", + "_NET_NUMBER_OF_DESKTOPS", + "_NET_DESKTOP_GEOMETRY", + "_NET_DESKTOP_VIEWPORT", + "_NET_CURRENT_DESKTOP", + "_NET_DESKTOP_NAMES", + "_NET_ACTIVE_WINDOW", + "_NET_WORKAREA", + "_NET_SUPPORTING_WM_CHECK", + "_NET_VIRTUAL_ROOTS", + "_NET_DESKTOP_LAYOUT", + "_NET_SHOWING_DESKTOP", + "_NET_CLOSE_WINDOW", + "_NET_MOVERESIZE_WINDOW", + "_NET_WM_MOVERESIZE", + "_NET_RESTACK_WINDOW", + "_NET_REQUEST_FRAME_EXTENTS", + "_NET_WM_NAME", + "_NET_WM_VISIBLE_NAME", + "_NET_WM_ICON_NAME", + "_NET_WM_VISIBLE_ICON_NAME", + "_NET_WM_DESKTOP", + "_NET_WM_WINDOW_TYPE", + "_NET_WM_STATE", + "_NET_WM_ALLOWED_ACTIONS", + "_NET_WM_STRUT", + "_NET_WM_STRUT_PARTIAL", + "_NET_WM_ICON_GEOMETRY", + "_NET_WM_ICON", + "_NET_WM_PID", + "_NET_WM_HANDLED_ICONS", + "_NET_WM_USER_TIME", + "_NET_FRAME_EXTENTS", + "_NET_WM_PING", + "_NET_WM_SYNC_REQUEST", + "_NET_SYSTEM_TRAY_OPCODE", + "_NET_SYSTEM_TRAY_ORIENTATION", + "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_STATE_MAXIMIZED_VERT", + "_NET_WM_STATE_HIDDEN", + "_XEMBED", + "_XEMBED_INFO", + "_MOTIF_WM_HINTS", + "_NET_WM_STATE_SKIP_TASKBAR", + "_NET_WM_STATE_ABOVE", + "_NET_WM_STATE_MODAL", + "_NET_WM_CONTEXT_HELP", + "_NET_WM_WINDOW_OPACITY", + "_NET_WM_WINDOW_TYPE_DESKTOP", + "_NET_WM_WINDOW_TYPE_DOCK", + "_NET_WM_WINDOW_TYPE_TOOLBAR", + "_NET_WM_WINDOW_TYPE_MENU", + "_NET_WM_WINDOW_TYPE_UTILITY", + "_NET_WM_WINDOW_TYPE_DIALOG", + "_NET_WM_WINDOW_TYPE_SPLASH", + "_NET_WM_WINDOW_TYPE_NORMAL", + "CLIPBOARD", + "PRIMARY", + "COMPOUND_TEXT", + "UTF8_STRING", + "TARGETS", + "_SWF_AsyncAtom", + "_SWF_PostMessageAtom", + "_SWF_HoverAtom" }; + + IntPtr[] atoms = new IntPtr [atom_names.Length];; + + XInternAtoms (display, atom_names, atom_names.Length, false, atoms); + + int off = 0; + WM_PROTOCOLS = atoms [off++]; + WM_DELETE_WINDOW = atoms [off++]; + WM_TAKE_FOCUS = atoms [off++]; + _NET_SUPPORTED = atoms [off++]; + _NET_CLIENT_LIST = atoms [off++]; + _NET_NUMBER_OF_DESKTOPS = atoms [off++]; + _NET_DESKTOP_GEOMETRY = atoms [off++]; + _NET_DESKTOP_VIEWPORT = atoms [off++]; + _NET_CURRENT_DESKTOP = atoms [off++]; + _NET_DESKTOP_NAMES = atoms [off++]; + _NET_ACTIVE_WINDOW = atoms [off++]; + _NET_WORKAREA = atoms [off++]; + _NET_SUPPORTING_WM_CHECK = atoms [off++]; + _NET_VIRTUAL_ROOTS = atoms [off++]; + _NET_DESKTOP_LAYOUT = atoms [off++]; + _NET_SHOWING_DESKTOP = atoms [off++]; + _NET_CLOSE_WINDOW = atoms [off++]; + _NET_MOVERESIZE_WINDOW = atoms [off++]; + _NET_WM_MOVERESIZE = atoms [off++]; + _NET_RESTACK_WINDOW = atoms [off++]; + _NET_REQUEST_FRAME_EXTENTS = atoms [off++]; + _NET_WM_NAME = atoms [off++]; + _NET_WM_VISIBLE_NAME = atoms [off++]; + _NET_WM_ICON_NAME = atoms [off++]; + _NET_WM_VISIBLE_ICON_NAME = atoms [off++]; + _NET_WM_DESKTOP = atoms [off++]; + _NET_WM_WINDOW_TYPE = atoms [off++]; + _NET_WM_STATE = atoms [off++]; + _NET_WM_ALLOWED_ACTIONS = atoms [off++]; + _NET_WM_STRUT = atoms [off++]; + _NET_WM_STRUT_PARTIAL = atoms [off++]; + _NET_WM_ICON_GEOMETRY = atoms [off++]; + _NET_WM_ICON = atoms [off++]; + _NET_WM_PID = atoms [off++]; + _NET_WM_HANDLED_ICONS = atoms [off++]; + _NET_WM_USER_TIME = atoms [off++]; + _NET_FRAME_EXTENTS = atoms [off++]; + _NET_WM_PING = atoms [off++]; + _NET_WM_SYNC_REQUEST = atoms [off++]; + _NET_SYSTEM_TRAY_OPCODE = atoms [off++]; + _NET_SYSTEM_TRAY_ORIENTATION = atoms [off++]; + _NET_WM_STATE_MAXIMIZED_HORZ = atoms [off++]; + _NET_WM_STATE_MAXIMIZED_VERT = atoms [off++]; + _NET_WM_STATE_HIDDEN = atoms [off++]; + _XEMBED = atoms [off++]; + _XEMBED_INFO = atoms [off++]; + _MOTIF_WM_HINTS = atoms [off++]; + _NET_WM_STATE_SKIP_TASKBAR = atoms [off++]; + _NET_WM_STATE_ABOVE = atoms [off++]; + _NET_WM_STATE_MODAL = atoms [off++]; + _NET_WM_CONTEXT_HELP = atoms [off++]; + _NET_WM_WINDOW_OPACITY = atoms [off++]; + _NET_WM_WINDOW_TYPE_DESKTOP = atoms [off++]; + _NET_WM_WINDOW_TYPE_DOCK = atoms [off++]; + _NET_WM_WINDOW_TYPE_TOOLBAR = atoms [off++]; + _NET_WM_WINDOW_TYPE_MENU = atoms [off++]; + _NET_WM_WINDOW_TYPE_UTILITY = atoms [off++]; + _NET_WM_WINDOW_TYPE_DIALOG = atoms [off++]; + _NET_WM_WINDOW_TYPE_SPLASH = atoms [off++]; + _NET_WM_WINDOW_TYPE_NORMAL = atoms [off++]; + CLIPBOARD = atoms [off++]; + PRIMARY = atoms [off++]; + OEMTEXT = atoms [off++]; + UNICODETEXT = atoms [off++]; + TARGETS = atoms [off++]; + AsyncAtom = atoms [off++]; + PostAtom = atoms [off++]; + HoverState = atoms [off++]; + + DIB = XA_PIXMAP; + + var defScreen = XDefaultScreen(display); + // XXX multi screen stuff here + _NET_SYSTEM_TRAY_S = XInternAtom (display, "_NET_SYSTEM_TRAY_S" + defScreen.ToString(), false); + } + + } + +} + diff --git a/src/Avalonia.X11/X11Enums.cs b/src/Avalonia.X11/X11Enums.cs new file mode 100644 index 0000000000..d97e1c42bb --- /dev/null +++ b/src/Avalonia.X11/X11Enums.cs @@ -0,0 +1,110 @@ +using System; + +namespace Avalonia.X11 +{ + + public enum Status + { + Success = 0, /* everything's okay */ + BadRequest = 1, /* bad request code */ + BadValue = 2, /* int parameter out of range */ + BadWindow = 3, /* parameter not a Window */ + BadPixmap = 4, /* parameter not a Pixmap */ + BadAtom = 5, /* parameter not an Atom */ + BadCursor = 6, /* parameter not a Cursor */ + BadFont = 7, /* parameter not a Font */ + BadMatch = 8, /* parameter mismatch */ + BadDrawable = 9, /* parameter not a Pixmap or Window */ + BadAccess = 10, /* depending on context: + - key/button already grabbed + - attempt to free an illegal + cmap entry + - attempt to store into a read-only + color map entry. + - attempt to modify the access control + list from other than the local host. + */ + BadAlloc = 11, /* insufficient resources */ + BadColor = 12, /* no such colormap */ + BadGC = 13, /* parameter not a GC */ + BadIDChoice = 14, /* choice not in range or already used */ + BadName = 15, /* font or color name doesn't exist */ + BadLength = 16, /* Request length incorrect */ + BadImplementation = 17, /* server is defective */ + + FirstExtensionError = 128, + LastExtensionError = 255, + + } + + [Flags] + public enum XEventMask : int + { + NoEventMask = 0, + KeyPressMask = (1 << 0), + KeyReleaseMask = (1 << 1), + ButtonPressMask = (1 << 2), + ButtonReleaseMask = (1 << 3), + EnterWindowMask = (1 << 4), + LeaveWindowMask = (1 << 5), + PointerMotionMask = (1 << 6), + PointerMotionHintMask = (1 << 7), + Button1MotionMask = (1 << 8), + Button2MotionMask = (1 << 9), + Button3MotionMask = (1 << 10), + Button4MotionMask = (1 << 11), + Button5MotionMask = (1 << 12), + ButtonMotionMask = (1 << 13), + KeymapStateMask = (1 << 14), + ExposureMask = (1 << 15), + VisibilityChangeMask = (1 << 16), + StructureNotifyMask = (1 << 17), + ResizeRedirectMask = (1 << 18), + SubstructureNotifyMask = (1 << 19), + SubstructureRedirectMask = (1 << 20), + FocusChangeMask = (1 << 21), + PropertyChangeMask = (1 << 22), + ColormapChangeMask = (1 << 23), + OwnerGrabButtonMask = (1 << 24) + } + + [Flags] + public enum XModifierMask + { + ShiftMask = (1 << 0), + LockMask = (1 << 1), + ControlMask = (1 << 2), + Mod1Mask = (1 << 3), + Mod2Mask = (1 << 4), + Mod3Mask = (1 << 5), + Mod4Mask = (1 << 6), + Mod5Mask = (1 << 7), + Button1Mask = (1 << 8), + Button2Mask = (1 << 9), + Button3Mask = (1 << 10), + Button4Mask = (1 << 11), + Button5Mask = (1 << 12), + AnyModifier = (1 << 15) + + } + + [Flags] + public enum XCreateWindowFlags + { + CWBackPixmap = (1 << 0), + CWBackPixel = (1 << 1), + CWBorderPixmap = (1 << 2), + CWBorderPixel = (1 << 3), + CWBitGravity = (1 << 4), + CWWinGravity = (1 << 5), + CWBackingStore = (1 << 6), + CWBackingPlanes = (1 << 7), + CWBackingPixel = (1 << 8), + CWOverrideRedirect = (1 << 9), + CWSaveUnder = (1 << 10), + CWEventMask = (1 << 11), + CWDontPropagate = (1 << 12), + CWColormap = (1 << 13), + CWCursor = (1 << 14), + } +} diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs new file mode 100644 index 0000000000..ef93a7347b --- /dev/null +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -0,0 +1,57 @@ +using System; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + class X11Framebuffer : ILockedFramebuffer + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + private IUnmanagedBlob _blob; + + public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor) + { + _display = display; + _xid = xid; + Width = width*factor; + Height = height*factor; + RowBytes = Width * 4; + Dpi = new Vector(96, 96) * factor; + Format = PixelFormat.Bgra8888; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Height); + Address = _blob.Address; + } + + public void Dispose() + { + var image = new XImage(); + int bitsPerPixel = 32; + image.width = Width; + image.height = Height; + image.format = 2; //ZPixmap; + image.data = Address; + image.byte_order = 0;// LSBFirst; + image.bitmap_unit = bitsPerPixel; + image.bitmap_bit_order = 0;// LSBFirst; + image.bitmap_pad = bitsPerPixel; + image.depth = 32; + image.bytes_per_line = RowBytes - Width * 4; + image.bits_per_pixel = bitsPerPixel; + XLockDisplay(_display); + XInitImage(ref image); + var gc = XCreateGC(_display, _xid, 0, IntPtr.Zero); + XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); + XFreeGC(_display, gc); + XSync(_display, true); + XUnlockDisplay(_display); + _blob.Dispose(); + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Vector Dpi { get; } + public PixelFormat Format { get; } + } +} diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs new file mode 100644 index 0000000000..05b21efb0c --- /dev/null +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -0,0 +1,27 @@ +using System; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +namespace Avalonia.X11 +{ + public class X11FramebufferSurface : IFramebufferPlatformSurface + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + + public X11FramebufferSurface(IntPtr display, IntPtr xid) + { + _display = display; + _xid = xid; + } + + public ILockedFramebuffer Lock() + { + XLockDisplay(_display); + XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height, + out var bw, out var d); + XUnlockDisplay(_display); + return new X11Framebuffer(_display, _xid, width, height, 1); + } + } +} diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs new file mode 100644 index 0000000000..6ad9faffbc --- /dev/null +++ b/src/Avalonia.X11/X11Info.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using static Avalonia.X11.XLib; +// ReSharper disable UnusedAutoPropertyAccessor.Local +namespace Avalonia.X11 +{ + class X11Info + { + public IntPtr Display { get; } + public IntPtr DeferredDisplay { get; } + public int DefaultScreen { get; } + public IntPtr BlackPixel { get; } + public IntPtr RootWindow { get; } + public IntPtr DefaultRootWindow { get; } + public XVisualInfo MatchedVisual { get; } + public X11Atoms Atoms { get; } + + public IntPtr LastActivityTimestamp { get; set; } + + public unsafe X11Info(IntPtr display, IntPtr deferredDisplay) + { + Display = display; + DeferredDisplay = deferredDisplay; + DefaultScreen = XDefaultScreen(display); + BlackPixel = XBlackPixel(display, DefaultScreen); + RootWindow = XRootWindow(display, DefaultScreen); + DefaultRootWindow = XDefaultRootWindow(display); + Atoms = new X11Atoms(display); + XMatchVisualInfo(display, DefaultScreen, 32, 4, out var info); + MatchedVisual = info; + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index c266433eef..33497be411 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -1,27 +1,66 @@ using System; +using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.X11; using static Avalonia.X11.XLib; namespace Avalonia.X11 { - public class AvaloniaX11Platform + class AvaloniaX11Platform : IWindowingPlatform { + private Lazy _keyboardDevice = new Lazy(() => new KeyboardDevice()); + private Lazy _mouseDevice = new Lazy(() => new MouseDevice()); + public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; + public MouseDevice MouseDevice => _mouseDevice.Value; + public Dictionary> Windows = new Dictionary>(); + public X11Info Info { get; private set; } public void Initialize() { + XInitThreads(); Display = XOpenDisplay(IntPtr.Zero); DeferredDisplay = XOpenDisplay(IntPtr.Zero); if (Display == IntPtr.Zero) throw new Exception("XOpenDisplay failed"); - + XError.Init(); + Info = new X11Info(Display, DeferredDisplay); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(new X11PlatformThreading(Display)); + .Bind().ToConstant(this) + .Bind().ToConstant(new X11PlatformThreading(Display, Windows)) + .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new RenderLoop()) + .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) + .Bind().ToFunc(() => KeyboardDevice) + .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToSingleton() + .Bind().ToConstant(new PlatformSettingsStub()) + .Bind().ToConstant(new SystemDialogsStub()) + .Bind().ToConstant(new IconLoaderStub()); + EglGlPlatformFeature.TryInitialize(); } public IntPtr DeferredDisplay { get; set; } public IntPtr Display { get; set; } + public IWindowImpl CreateWindow() + { + return new X11Window(this, false); + } + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new NotSupportedException(); + } + + public IPopupImpl CreatePopup() + { + return new X11Window(this, true); + } } } @@ -34,6 +73,8 @@ namespace Avalonia builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); return builder; } + + public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize(); } } diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs index 18b08085e3..298b4da19c 100644 --- a/src/Avalonia.X11/X11PlatformThreading.cs +++ b/src/Avalonia.X11/X11PlatformThreading.cs @@ -9,9 +9,10 @@ using static Avalonia.X11.XLib; namespace Avalonia.X11 { - public unsafe class X11PlatformThreading : IPlatformThreadingInterface + unsafe class X11PlatformThreading : IPlatformThreadingInterface { private readonly IntPtr _display; + private readonly Dictionary> _eventHandlers; private Thread _mainThread; [StructLayout(LayoutKind.Explicit)] @@ -102,9 +103,10 @@ namespace Avalonia.X11 List _timers = new List(); - public X11PlatformThreading(IntPtr display) + public X11PlatformThreading(IntPtr display, Dictionary> eventHandlers) { _display = display; + _eventHandlers = eventHandlers; _mainThread = Thread.CurrentThread; var fd = XLib.XConnectionNumber(display); var ev = new epoll_event() @@ -202,12 +204,22 @@ namespace Avalonia.X11 } else { - while (XPending(_display)) + while (true) { - if (cancellationToken.IsCancellationRequested) - return; - XNextEvent(_display, out var xev); + var pending = XPending(_display); + if (pending == 0) + break; + while (pending > 0) + { + if (cancellationToken.IsCancellationRequested) + return; + XNextEvent(_display, out var xev); + pending--; + if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler)) + handler(xev); + } } + Dispatcher.UIThread.RunJobs(); } } } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs new file mode 100644 index 0000000000..a18bd4be74 --- /dev/null +++ b/src/Avalonia.X11/X11Structs.cs @@ -0,0 +1,1815 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software",, to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004 Novell, Inc. +// +// Authors: +// Peter Bartok pbartok@novell.com +// + + +// NOT COMPLETE + +using System; +using System.ComponentModel; +using System.Collections; +using System.Drawing; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable IdentifierTypo +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable CommentTypo +// ReSharper disable ArrangeThisQualifier +// ReSharper disable NotAccessedField.Global +#pragma warning disable 649 + +namespace Avalonia.X11 { + // + // In the structures below, fields of type long are mapped to IntPtr. + // This will work on all platforms where sizeof(long)==sizeof(void*), which + // is almost all platforms except WIN64. + // + + [StructLayout(LayoutKind.Sequential)] + internal struct XAnyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XKeyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal int state; + internal int keycode; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XButtonEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal int button; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMotionEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal XModifierMask state; + internal byte is_hint; + internal bool same_screen; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCrossingEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr root; + internal IntPtr subwindow; + internal IntPtr time; + internal int x; + internal int y; + internal int x_root; + internal int y_root; + internal NotifyMode mode; + internal NotifyDetail detail; + internal bool same_screen; + internal bool focus; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XFocusChangeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int mode; + internal NotifyDetail detail; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XKeymapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal byte key_vector0; + internal byte key_vector1; + internal byte key_vector2; + internal byte key_vector3; + internal byte key_vector4; + internal byte key_vector5; + internal byte key_vector6; + internal byte key_vector7; + internal byte key_vector8; + internal byte key_vector9; + internal byte key_vector10; + internal byte key_vector11; + internal byte key_vector12; + internal byte key_vector13; + internal byte key_vector14; + internal byte key_vector15; + internal byte key_vector16; + internal byte key_vector17; + internal byte key_vector18; + internal byte key_vector19; + internal byte key_vector20; + internal byte key_vector21; + internal byte key_vector22; + internal byte key_vector23; + internal byte key_vector24; + internal byte key_vector25; + internal byte key_vector26; + internal byte key_vector27; + internal byte key_vector28; + internal byte key_vector29; + internal byte key_vector30; + internal byte key_vector31; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int count; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XGraphicsExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr drawable; + internal int x; + internal int y; + internal int width; + internal int height; + internal int count; + internal int major_code; + internal int minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XNoExposeEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr drawable; + internal int major_code; + internal int minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XVisibilityEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCreateWindowEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XDestroyWindowEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XUnmapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal bool from_configure; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMapRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XReparentEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal IntPtr parent; + internal int x; + internal int y; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XConfigureEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr above; + internal bool override_redirect; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XGravityEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int x; + internal int y; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XResizeRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int width; + internal int height; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XConfigureRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr above; + internal int detail; + internal IntPtr value_mask; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCirculateEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr xevent; + internal IntPtr window; + internal int place; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XCirculateRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr parent; + internal IntPtr window; + internal int place; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XPropertyEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr atom; + internal IntPtr time; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionClearEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr selection; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionRequestEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr owner; + internal IntPtr requestor; + internal IntPtr selection; + internal IntPtr target; + internal IntPtr property; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSelectionEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr requestor; + internal IntPtr selection; + internal IntPtr target; + internal IntPtr property; + internal IntPtr time; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XColormapEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr colormap; + internal bool c_new; + internal int state; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XClientMessageEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal IntPtr message_type; + internal int format; + internal IntPtr ptr1; + internal IntPtr ptr2; + internal IntPtr ptr3; + internal IntPtr ptr4; + internal IntPtr ptr5; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XMappingEvent { + internal XEventName type; + internal IntPtr serial; + internal bool send_event; + internal IntPtr display; + internal IntPtr window; + internal int request; + internal int first_keycode; + internal int count; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XErrorEvent { + internal XEventName type; + internal IntPtr display; + internal IntPtr resourceid; + internal IntPtr serial; + internal byte error_code; + internal XRequest request_code; + internal byte minor_code; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XEventPad { + internal IntPtr pad0; + internal IntPtr pad1; + internal IntPtr pad2; + internal IntPtr pad3; + internal IntPtr pad4; + internal IntPtr pad5; + internal IntPtr pad6; + internal IntPtr pad7; + internal IntPtr pad8; + internal IntPtr pad9; + internal IntPtr pad10; + internal IntPtr pad11; + internal IntPtr pad12; + internal IntPtr pad13; + internal IntPtr pad14; + internal IntPtr pad15; + internal IntPtr pad16; + internal IntPtr pad17; + internal IntPtr pad18; + internal IntPtr pad19; + internal IntPtr pad20; + internal IntPtr pad21; + internal IntPtr pad22; + internal IntPtr pad23; + } + + [StructLayout(LayoutKind.Explicit)] + internal struct XEvent { + [ FieldOffset(0) ] internal XEventName type; + [ FieldOffset(0) ] internal XAnyEvent AnyEvent; + [ FieldOffset(0) ] internal XKeyEvent KeyEvent; + [ FieldOffset(0) ] internal XButtonEvent ButtonEvent; + [ FieldOffset(0) ] internal XMotionEvent MotionEvent; + [ FieldOffset(0) ] internal XCrossingEvent CrossingEvent; + [ FieldOffset(0) ] internal XFocusChangeEvent FocusChangeEvent; + [ FieldOffset(0) ] internal XExposeEvent ExposeEvent; + [ FieldOffset(0) ] internal XGraphicsExposeEvent GraphicsExposeEvent; + [ FieldOffset(0) ] internal XNoExposeEvent NoExposeEvent; + [ FieldOffset(0) ] internal XVisibilityEvent VisibilityEvent; + [ FieldOffset(0) ] internal XCreateWindowEvent CreateWindowEvent; + [ FieldOffset(0) ] internal XDestroyWindowEvent DestroyWindowEvent; + [ FieldOffset(0) ] internal XUnmapEvent UnmapEvent; + [ FieldOffset(0) ] internal XMapEvent MapEvent; + [ FieldOffset(0) ] internal XMapRequestEvent MapRequestEvent; + [ FieldOffset(0) ] internal XReparentEvent ReparentEvent; + [ FieldOffset(0) ] internal XConfigureEvent ConfigureEvent; + [ FieldOffset(0) ] internal XGravityEvent GravityEvent; + [ FieldOffset(0) ] internal XResizeRequestEvent ResizeRequestEvent; + [ FieldOffset(0) ] internal XConfigureRequestEvent ConfigureRequestEvent; + [ FieldOffset(0) ] internal XCirculateEvent CirculateEvent; + [ FieldOffset(0) ] internal XCirculateRequestEvent CirculateRequestEvent; + [ FieldOffset(0) ] internal XPropertyEvent PropertyEvent; + [ FieldOffset(0) ] internal XSelectionClearEvent SelectionClearEvent; + [ FieldOffset(0) ] internal XSelectionRequestEvent SelectionRequestEvent; + [ FieldOffset(0) ] internal XSelectionEvent SelectionEvent; + [ FieldOffset(0) ] internal XColormapEvent ColormapEvent; + [ FieldOffset(0) ] internal XClientMessageEvent ClientMessageEvent; + [ FieldOffset(0) ] internal XMappingEvent MappingEvent; + [ FieldOffset(0) ] internal XErrorEvent ErrorEvent; + [ FieldOffset(0) ] internal XKeymapEvent KeymapEvent; + + //[MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=24)] + //[ FieldOffset(0) ] internal int[] pad; + [ FieldOffset(0) ] internal XEventPad Pad; + public override string ToString() { + switch (type) + { + case XEventName.ButtonPress: + case XEventName.ButtonRelease: + return ToString (ButtonEvent); + case XEventName.CirculateNotify: + case XEventName.CirculateRequest: + return ToString (CirculateEvent); + case XEventName.ClientMessage: + return ToString (ClientMessageEvent); + case XEventName.ColormapNotify: + return ToString (ColormapEvent); + case XEventName.ConfigureNotify: + return ToString (ConfigureEvent); + case XEventName.ConfigureRequest: + return ToString (ConfigureRequestEvent); + case XEventName.CreateNotify: + return ToString (CreateWindowEvent); + case XEventName.DestroyNotify: + return ToString (DestroyWindowEvent); + case XEventName.Expose: + return ToString (ExposeEvent); + case XEventName.FocusIn: + case XEventName.FocusOut: + return ToString (FocusChangeEvent); + case XEventName.GraphicsExpose: + return ToString (GraphicsExposeEvent); + case XEventName.GravityNotify: + return ToString (GravityEvent); + case XEventName.KeymapNotify: + return ToString (KeymapEvent); + case XEventName.MapNotify: + return ToString (MapEvent); + case XEventName.MappingNotify: + return ToString (MappingEvent); + case XEventName.MapRequest: + return ToString (MapRequestEvent); + case XEventName.MotionNotify: + return ToString (MotionEvent); + case XEventName.NoExpose: + return ToString (NoExposeEvent); + case XEventName.PropertyNotify: + return ToString (PropertyEvent); + case XEventName.ReparentNotify: + return ToString (ReparentEvent); + case XEventName.ResizeRequest: + return ToString (ResizeRequestEvent); + case XEventName.SelectionClear: + return ToString (SelectionClearEvent); + case XEventName.SelectionNotify: + return ToString (SelectionEvent); + case XEventName.SelectionRequest: + return ToString (SelectionRequestEvent); + case XEventName.UnmapNotify: + return ToString (UnmapEvent); + case XEventName.VisibilityNotify: + return ToString (VisibilityEvent); + case XEventName.EnterNotify: + case XEventName.LeaveNotify: + return ToString (CrossingEvent); + default: + return type.ToString (); + } + } + + public static string ToString (object ev) + { + string result = string.Empty; + Type type = ev.GetType (); + FieldInfo [] fields = type.GetFields (System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance); + for (int i = 0; i < fields.Length; i++) { + if (result != string.Empty) { + result += ", "; + } + object value = fields [i].GetValue (ev); + result += fields [i].Name + "=" + (value == null ? "" : value.ToString ()); + } + return type.Name + " (" + result + ")"; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XSetWindowAttributes { + internal IntPtr background_pixmap; + internal IntPtr background_pixel; + internal IntPtr border_pixmap; + internal IntPtr border_pixel; + internal Gravity bit_gravity; + internal Gravity win_gravity; + internal int backing_store; + internal IntPtr backing_planes; + internal IntPtr backing_pixel; + internal bool save_under; + internal IntPtr event_mask; + internal IntPtr do_not_propagate_mask; + internal bool override_redirect; + internal IntPtr colormap; + internal IntPtr cursor; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XWindowAttributes { + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal int depth; + internal IntPtr visual; + internal IntPtr root; + internal int c_class; + internal Gravity bit_gravity; + internal Gravity win_gravity; + internal int backing_store; + internal IntPtr backing_planes; + internal IntPtr backing_pixel; + internal bool save_under; + internal IntPtr colormap; + internal bool map_installed; + internal MapState map_state; + internal IntPtr all_event_masks; + internal IntPtr your_event_mask; + internal IntPtr do_not_propagate_mask; + internal bool override_direct; + internal IntPtr screen; + + public override string ToString () + { + return XEvent.ToString (this); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XTextProperty { + internal string value; + internal IntPtr encoding; + internal int format; + internal IntPtr nitems; + } + + internal enum XWindowClass { + InputOutput = 1, + InputOnly = 2 + } + + internal enum XEventName { + KeyPress = 2, + KeyRelease = 3, + ButtonPress = 4, + ButtonRelease = 5, + MotionNotify = 6, + EnterNotify = 7, + LeaveNotify = 8, + FocusIn = 9, + FocusOut = 10, + KeymapNotify = 11, + Expose = 12, + GraphicsExpose = 13, + NoExpose = 14, + VisibilityNotify = 15, + CreateNotify = 16, + DestroyNotify = 17, + UnmapNotify = 18, + MapNotify = 19, + MapRequest = 20, + ReparentNotify = 21, + ConfigureNotify = 22, + ConfigureRequest = 23, + GravityNotify = 24, + ResizeRequest = 25, + CirculateNotify = 26, + CirculateRequest = 27, + PropertyNotify = 28, + SelectionClear = 29, + SelectionRequest = 30, + SelectionNotify = 31, + ColormapNotify = 32, + ClientMessage = 33, + MappingNotify = 34, + + LASTEvent + } + + [Flags] + internal enum SetWindowValuemask { + Nothing = 0, + BackPixmap = 1, + BackPixel = 2, + BorderPixmap = 4, + BorderPixel = 8, + BitGravity = 16, + WinGravity = 32, + BackingStore = 64, + BackingPlanes = 128, + BackingPixel = 256, + OverrideRedirect = 512, + SaveUnder = 1024, + EventMask = 2048, + DontPropagate = 4096, + ColorMap = 8192, + Cursor = 16384 + } + + internal enum SendEventValues { + PointerWindow = 0, + InputFocus = 1 + } + + internal enum CreateWindowArgs { + CopyFromParent = 0, + ParentRelative = 1, + InputOutput = 1, + InputOnly = 2 + } + + internal enum Gravity { + ForgetGravity = 0, + NorthWestGravity= 1, + NorthGravity = 2, + NorthEastGravity= 3, + WestGravity = 4, + CenterGravity = 5, + EastGravity = 6, + SouthWestGravity= 7, + SouthGravity = 8, + SouthEastGravity= 9, + StaticGravity = 10 + } + + internal enum XKeySym : uint { + XK_BackSpace = 0xFF08, + XK_Tab = 0xFF09, + XK_Clear = 0xFF0B, + XK_Return = 0xFF0D, + XK_Home = 0xFF50, + XK_Left = 0xFF51, + XK_Up = 0xFF52, + XK_Right = 0xFF53, + XK_Down = 0xFF54, + XK_Page_Up = 0xFF55, + XK_Page_Down = 0xFF56, + XK_End = 0xFF57, + XK_Begin = 0xFF58, + XK_Menu = 0xFF67, + XK_Shift_L = 0xFFE1, + XK_Shift_R = 0xFFE2, + XK_Control_L = 0xFFE3, + XK_Control_R = 0xFFE4, + XK_Caps_Lock = 0xFFE5, + XK_Shift_Lock = 0xFFE6, + XK_Meta_L = 0xFFE7, + XK_Meta_R = 0xFFE8, + XK_Alt_L = 0xFFE9, + XK_Alt_R = 0xFFEA, + XK_Super_L = 0xFFEB, + XK_Super_R = 0xFFEC, + XK_Hyper_L = 0xFFED, + XK_Hyper_R = 0xFFEE, + } + + [Flags] + internal enum EventMask { + NoEventMask = 0, + KeyPressMask = 1<<0, + KeyReleaseMask = 1<<1, + ButtonPressMask = 1<<2, + ButtonReleaseMask = 1<<3, + EnterWindowMask = 1<<4, + LeaveWindowMask = 1<<5, + PointerMotionMask = 1<<6, + PointerMotionHintMask = 1<<7, + Button1MotionMask = 1<<8, + Button2MotionMask = 1<<9, + Button3MotionMask = 1<<10, + Button4MotionMask = 1<<11, + Button5MotionMask = 1<<12, + ButtonMotionMask = 1<<13, + KeymapStateMask = 1<<14, + ExposureMask = 1<<15, + VisibilityChangeMask = 1<<16, + StructureNotifyMask = 1<<17, + ResizeRedirectMask = 1<<18, + SubstructureNotifyMask = 1<<19, + SubstructureRedirectMask= 1<<20, + FocusChangeMask = 1<<21, + PropertyChangeMask = 1<<22, + ColormapChangeMask = 1<<23, + OwnerGrabButtonMask = 1<<24 + } + + internal enum GrabMode { + GrabModeSync = 0, + GrabModeAsync = 1 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XStandardColormap { + internal IntPtr colormap; + internal IntPtr red_max; + internal IntPtr red_mult; + internal IntPtr green_max; + internal IntPtr green_mult; + internal IntPtr blue_max; + internal IntPtr blue_mult; + internal IntPtr base_pixel; + internal IntPtr visualid; + internal IntPtr killid; + } + + [StructLayout(LayoutKind.Sequential, Pack=2)] + internal struct XColor { + internal IntPtr pixel; + internal ushort red; + internal ushort green; + internal ushort blue; + internal byte flags; + internal byte pad; + } + + internal enum Atom { + AnyPropertyType = 0, + XA_PRIMARY = 1, + XA_SECONDARY = 2, + XA_ARC = 3, + XA_ATOM = 4, + XA_BITMAP = 5, + XA_CARDINAL = 6, + XA_COLORMAP = 7, + XA_CURSOR = 8, + XA_CUT_BUFFER0 = 9, + XA_CUT_BUFFER1 = 10, + XA_CUT_BUFFER2 = 11, + XA_CUT_BUFFER3 = 12, + XA_CUT_BUFFER4 = 13, + XA_CUT_BUFFER5 = 14, + XA_CUT_BUFFER6 = 15, + XA_CUT_BUFFER7 = 16, + XA_DRAWABLE = 17, + XA_FONT = 18, + XA_INTEGER = 19, + XA_PIXMAP = 20, + XA_POINT = 21, + XA_RECTANGLE = 22, + XA_RESOURCE_MANAGER = 23, + XA_RGB_COLOR_MAP = 24, + XA_RGB_BEST_MAP = 25, + XA_RGB_BLUE_MAP = 26, + XA_RGB_DEFAULT_MAP = 27, + XA_RGB_GRAY_MAP = 28, + XA_RGB_GREEN_MAP = 29, + XA_RGB_RED_MAP = 30, + XA_STRING = 31, + XA_VISUALID = 32, + XA_WINDOW = 33, + XA_WM_COMMAND = 34, + XA_WM_HINTS = 35, + XA_WM_CLIENT_MACHINE = 36, + XA_WM_ICON_NAME = 37, + XA_WM_ICON_SIZE = 38, + XA_WM_NAME = 39, + XA_WM_NORMAL_HINTS = 40, + XA_WM_SIZE_HINTS = 41, + XA_WM_ZOOM_HINTS = 42, + XA_MIN_SPACE = 43, + XA_NORM_SPACE = 44, + XA_MAX_SPACE = 45, + XA_END_SPACE = 46, + XA_SUPERSCRIPT_X = 47, + XA_SUPERSCRIPT_Y = 48, + XA_SUBSCRIPT_X = 49, + XA_SUBSCRIPT_Y = 50, + XA_UNDERLINE_POSITION = 51, + XA_UNDERLINE_THICKNESS = 52, + XA_STRIKEOUT_ASCENT = 53, + XA_STRIKEOUT_DESCENT = 54, + XA_ITALIC_ANGLE = 55, + XA_X_HEIGHT = 56, + XA_QUAD_WIDTH = 57, + XA_WEIGHT = 58, + XA_POINT_SIZE = 59, + XA_RESOLUTION = 60, + XA_COPYRIGHT = 61, + XA_NOTICE = 62, + XA_FONT_NAME = 63, + XA_FAMILY_NAME = 64, + XA_FULL_NAME = 65, + XA_CAP_HEIGHT = 66, + XA_WM_CLASS = 67, + XA_WM_TRANSIENT_FOR = 68, + + XA_LAST_PREDEFINED = 68 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XScreen { + internal IntPtr ext_data; + internal IntPtr display; + internal IntPtr root; + internal int width; + internal int height; + internal int mwidth; + internal int mheight; + internal int ndepths; + internal IntPtr depths; + internal int root_depth; + internal IntPtr root_visual; + internal IntPtr default_gc; + internal IntPtr cmap; + internal IntPtr white_pixel; + internal IntPtr black_pixel; + internal int max_maps; + internal int min_maps; + internal int backing_store; + internal bool save_unders; + internal IntPtr root_input_mask; + } + + [Flags] + internal enum ChangeWindowFlags { + CWX = 1<<0, + CWY = 1<<1, + CWWidth = 1<<2, + CWHeight = 1<<3, + CWBorderWidth = 1<<4, + CWSibling = 1<<5, + CWStackMode = 1<<6 + } + + internal enum StackMode { + Above = 0, + Below = 1, + TopIf = 2, + BottomIf = 3, + Opposite = 4 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct XWindowChanges { + internal int x; + internal int y; + internal int width; + internal int height; + internal int border_width; + internal IntPtr sibling; + internal StackMode stack_mode; + } + + [Flags] + internal enum ColorFlags { + DoRed = 1<<0, + DoGreen = 1<<1, + DoBlue = 1<<2 + } + + internal enum NotifyMode { + NotifyNormal = 0, + NotifyGrab = 1, + NotifyUngrab = 2 + } + + internal enum NotifyDetail { + NotifyAncestor = 0, + NotifyVirtual = 1, + NotifyInferior = 2, + NotifyNonlinear = 3, + NotifyNonlinearVirtual = 4, + NotifyPointer = 5, + NotifyPointerRoot = 6, + NotifyDetailNone = 7 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MotifWmHints { + internal IntPtr flags; + internal IntPtr functions; + internal IntPtr decorations; + internal IntPtr input_mode; + internal IntPtr status; + + public override string ToString () + { + return string.Format("MotifWmHints _eventHandler; + private bool _invalidated; + private XConfigureEvent? _configure; + private IInputRoot _inputRoot; + private IMouseDevice _mouse; + private Point _position; + private IntPtr _handle; + private bool _mapped; + + class InputEventContainer + { + public RawInputEventArgs Event; + } + private Queue _inputQueue = new Queue(); + private InputEventContainer _lastEvent; + + public X11Window(AvaloniaX11Platform platform, bool popup) + { + _platform = platform; + _popup = popup; + _x11 = platform.Info; + _mouse = platform.MouseDevice; + + + XSetWindowAttributes attr = new XSetWindowAttributes(); + var valueMask = default(SetWindowValuemask); + /* + _handle = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 10, 10, 100, 100, 0, IntPtr.Zero, + IntPtr.Zero);*/ + var vinfo = _x11.MatchedVisual; + attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, vinfo.visual, 0); + attr.backing_store = 2; + attr.bit_gravity = Gravity.NorthWestGravity; + attr.win_gravity = Gravity.NorthWestGravity; + valueMask |= SetWindowValuemask.ColorMap | SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel + | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore + | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity; + + if (popup) + { + attr.override_redirect = true; + valueMask |= SetWindowValuemask.OverrideRedirect; + } + + _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, + (int)vinfo.depth, + (int)CreateWindowArgs.InputOutput, vinfo.visual, + new UIntPtr((uint)valueMask), ref attr); + + + Handle = new PlatformHandle(_handle, "XID"); + ClientSize = new Size(400, 400); + _eventHandler = OnEvent; + platform.Windows[_handle] = _eventHandler; + XSelectInput(_x11.Display, _handle, + new IntPtr(0xffffff + ^ (int)XEventMask.SubstructureRedirectMask + ^ (int)XEventMask.ResizeRedirectMask + ^ (int)XEventMask.PointerMotionHintMask)); + var protocols = new[] + { + _x11.Atoms.WM_DELETE_WINDOW + }; + XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length); + var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService(); + var surfaces = new List + { + new X11FramebufferSurface(_x11.DeferredDisplay, _handle) + }; + if (feature != null) + surfaces.Insert(0, + new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, + new SurfaceInfo(_x11.DeferredDisplay, _handle))); + Surfaces = surfaces.ToArray(); + UpdateWmHits(); + XFlush(_x11.Display); + } + + class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo + { + private readonly IntPtr _display; + + public SurfaceInfo(IntPtr display, IntPtr xid) + { + _display = display; + Handle = xid; + } + public IntPtr Handle { get; } + + public System.Drawing.Size PixelSize + { + get + { + XLockDisplay(_display); + + XGetGeometry(_display, Handle, out var geo); + XUnlockDisplay(_display); + return new System.Drawing.Size(geo.width, geo.height); + } + } + + public double Scaling { get; } = 1; + } + + void UpdateWmHits() + { + var functions = MotifFunctions.All; + var decorations = MotifDecorations.All; + + if (_popup || !_systemDecorations) + { + functions = 0; + decorations = 0; + } + + if (!_canResize) + { + functions ^= MotifFunctions.Resize | MotifFunctions.Maximize; + decorations ^= MotifDecorations.Maximize | MotifDecorations.ResizeH; + } + + var hints = new MotifWmHints(); + hints.flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)); + + hints.decorations = new IntPtr((int)decorations); + hints.functions = new IntPtr((int)functions); + + XChangeProperty(_x11.Display, _handle, + _x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32, + PropertyMode.Replace, ref hints, 5); + } + + public Size ClientSize { get; private set; } + public double Scaling { get; } = 1; + public IEnumerable Surfaces { get; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action Deactivated { get; set; } + public Action Activated { get; set; } + public Func Closing { get; set; } + public WindowState WindowState { get; set; } + public Action WindowStateChanged { get; set; } + public Action Closed { get; set; } + public Action PositionChanged { get; set; } + + public IRenderer CreateRenderer(IRenderRoot root) => + new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); + + unsafe void OnEvent(XEvent ev) + { + if (ev.type == XEventName.MapNotify) + _mapped = true; + else if (ev.type == XEventName.UnmapNotify) + _mapped = false; + else if (ev.type == XEventName.Expose) + DoPaint(); + else if (ev.type == XEventName.FocusIn) + Activated?.Invoke(); + else if (ev.type == XEventName.FocusOut) + Deactivated?.Invoke(); + else if (ev.type == XEventName.MotionNotify) + MouseEvent(RawMouseEventType.Move, ev, ev.MotionEvent.state); + + else if (ev.type == XEventName.ButtonPress) + { + if (ev.ButtonEvent.button < 4) + MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown + : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown + : RawMouseEventType.RightButtonDown, ev, ev.ButtonEvent.state); + else + { + var delta = ev.ButtonEvent.button == 4 + ? new Vector(0, 1) + : ev.ButtonEvent.button == 5 + ? new Vector(0, -1) + : ev.ButtonEvent.button == 6 + ? new Vector(1, 0) + : new Vector(-1, 0); + ScheduleInput(new RawMouseWheelEventArgs(_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), + _inputRoot, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), delta, + TranslateModifiers(ev.ButtonEvent.state))); + } + + } + else if (ev.type == XEventName.ButtonRelease) + { + if (ev.ButtonEvent.button < 4) + MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp + : ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp + : RawMouseEventType.RightButtonUp, ev, ev.ButtonEvent.state); + } + else if (ev.type == XEventName.ConfigureNotify) + { + var needEnqueue = (_configure == null); + _configure = ev.ConfigureEvent; + if (needEnqueue) + Dispatcher.UIThread.Post(() => + { + if (_configure == null) + return; + var cev = _configure.Value; + _configure = null; + var nsize = new Size(cev.width, cev.height); + var npos = new Point(cev.x, cev.y); + var changedSize = ClientSize != nsize; + var changedPos = npos != _position; + ClientSize = nsize; + _position = npos; + if (changedSize) + Resized?.Invoke(nsize); + if (changedPos) + PositionChanged?.Invoke(npos); + }); + } + else if (ev.type == XEventName.DestroyNotify) + { + if (_handle != IntPtr.Zero) + { + _platform.Windows.Remove(_handle); + _handle = IntPtr.Zero; + Closed?.Invoke(); + } + } + else if (ev.type == XEventName.ClientMessage) + { + if (ev.ClientMessageEvent.message_type == _x11.Atoms.WM_PROTOCOLS) + { + if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_DELETE_WINDOW) + { + if (Closing?.Invoke() != true) + Dispose(); + } + + } + } + } + + + + InputModifiers TranslateModifiers(XModifierMask state) + { + var rv = default(InputModifiers); + if (state.HasFlag(XModifierMask.Button1Mask)) + rv |= InputModifiers.LeftMouseButton; + if (state.HasFlag(XModifierMask.Button2Mask)) + rv |= InputModifiers.RightMouseButton; + if (state.HasFlag(XModifierMask.Button2Mask)) + rv |= InputModifiers.MiddleMouseButton; + if (state.HasFlag(XModifierMask.ShiftMask)) + rv |= InputModifiers.Shift; + if (state.HasFlag(XModifierMask.ControlMask)) + rv |= InputModifiers.Control; + if (state.HasFlag(XModifierMask.Mod1Mask)) + rv |= InputModifiers.Alt; + if (state.HasFlag(XModifierMask.Mod4Mask)) + rv |= InputModifiers.Windows; + return rv; + } + static Stopwatch St = Stopwatch.StartNew(); + private bool _systemDecorations = true; + private bool _canResize = true; + + void ScheduleInput(RawInputEventArgs args) + { + _lastEvent = new InputEventContainer() {Event = args}; + _inputQueue.Enqueue(_lastEvent); + if (_inputQueue.Count == 1) + { + Dispatcher.UIThread.Post(() => + { + while (_inputQueue.Count > 0) + { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + var ev = _inputQueue.Dequeue(); + Input?.Invoke(ev.Event); + } + }, DispatcherPriority.Input); + } + } + + void MouseEvent(RawMouseEventType type, XEvent ev, XModifierMask mods) + { + _x11.LastActivityTimestamp = ev.ButtonEvent.time; + var mev = new RawMouseEventArgs( + _mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot, + type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods)); + if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma) + if (ma.Type == RawMouseEventType.Move) + { + _lastEvent.Event = mev; + return; + } + ScheduleInput(mev); + } + + void DoPaint() + { + _invalidated = false; + Paint?.Invoke(new Rect()); + } + + public void Invalidate(Rect rect) + { + if(_invalidated) + return; + _invalidated = true; + Dispatcher.UIThread.InvokeAsync(() => + { + if (_mapped) + DoPaint(); + }); + } + + public void SetInputRoot(IInputRoot inputRoot) + { + _inputRoot = inputRoot; + } + + public void Dispose() + { + if (_handle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _handle); + _platform.Windows.Remove(_handle); + _handle = IntPtr.Zero; + Closed?.Invoke(); + + } + } + + public void Show() => XMapWindow(_x11.Display, _handle); + + public void Hide() => XUnmapWindow(_x11.Display, _handle); + + + public Point PointToClient(Point point) => new Point(point.X - _position.X, point.Y - _position.Y); + + public Point PointToScreen(Point point) => new Point(point.X + _position.X, point.Y + _position.Y); + + public void SetSystemDecorations(bool enabled) + { + _systemDecorations = enabled; + UpdateWmHits(); + } + + + public void Resize(Size clientSize) + { + if (clientSize == ClientSize) + return; + var changes = new XWindowChanges(); + changes.width = (int)clientSize.Width; + changes.height = (int)clientSize.Height; + var needResize = clientSize != ClientSize; + XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth, + ref changes); + XFlush(_x11.Display); + + if (_popup && needResize) + { + ClientSize = clientSize; + Resized?.Invoke(clientSize); + } + } + + public void CanResize(bool value) + { + _canResize = value; + UpdateWmHits(); + } + + public void SetCursor(IPlatformHandle cursor) + { + } + + public IPlatformHandle Handle { get; } + + public Point Position + { + get => _position; + set + { + var changes = new XWindowChanges(); + changes.x = (int)value.X; + changes.y = (int)value.Y; + XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY, + ref changes); + XFlush(_x11.Display); + + } + } + + public IMouseDevice MouseDevice => _mouse; + + public unsafe void Activate() + { + if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) + { + + var ev = new XEvent + { + AnyEvent = + { + type = XEventName.ClientMessage, + window = _handle, + }, + ClientMessageEvent = { + message_type = _x11.Atoms._NET_ACTIVE_WINDOW, + format = 32, + ptr1 = new IntPtr(1), + ptr2 = _x11.LastActivityTimestamp + + } + }; + + XSendEvent(_x11.Display, _x11.RootWindow, false, + new IntPtr((int)(XEventMask.SubstructureRedirectMask | XEventMask.StructureNotifyMask)), ref ev); + } + else + { + XRaiseWindow(_x11.Display, _handle); + XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero); + } + } + + + public IScreenImpl Screen { get; } = new ScreenStub(); + public Size MaxClientSize { get; } = new Size(1920, 1280); + + + public void BeginMoveDrag() + { + } + + public void BeginResizeDrag(WindowEdge edge) + { + } + + public void SetTitle(string title) + { + } + + public void SetMinMaxSize(Size minSize, Size maxSize) + { + + } + + public void SetTopmost(bool value) + { + + } + + public IDisposable ShowDialog() + { + return Disposable.Empty; + } + + public void SetIcon(IWindowIconImpl icon) + { + } + + public void ShowTaskbarIcon(bool value) + { + } + + + + + } +} diff --git a/src/Avalonia.X11/XError.cs b/src/Avalonia.X11/XError.cs new file mode 100644 index 0000000000..2cc8f63c96 --- /dev/null +++ b/src/Avalonia.X11/XError.cs @@ -0,0 +1,30 @@ +using System; + +namespace Avalonia.X11 +{ + static class XError + { + private static readonly XErrorHandler s_errorHandlerDelegate = Handler; + public static XErrorEvent LastError; + static int Handler(IntPtr display, ref XErrorEvent error) + { + LastError = error; + return 0; + } + + public static void ThrowLastError(string desc) + { + var err = LastError; + LastError = new XErrorEvent(); + if (err.error_code == 0) + throw new X11Exception(desc); + throw new X11Exception(desc + ": " + err.error_code); + + } + + public static void Init() + { + XLib.XSetErrorHandler(s_errorHandlerDelegate); + } + } +} diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 88d36091ca..593e6b438f 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -1,105 +1,421 @@ using System; using System.Runtime.InteropServices; +using System.Text; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable CommentTypo +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +// ReSharper disable NotAccessedField.Global +// ReSharper disable UnusedMethodReturnValue.Global namespace Avalonia.X11 { - public static class XLib + internal static class XLib { + const string libX11 = "X11"; + + [DllImport(libX11)] + public static extern IntPtr XOpenDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern int XCloseDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XSynchronize(IntPtr display, bool onoff); + + [DllImport(libX11)] + public static extern IntPtr XCreateWindow(IntPtr display, IntPtr parent, int x, int y, int width, int height, + int border_width, int depth, int xclass, IntPtr visual, UIntPtr valuemask, + ref XSetWindowAttributes attributes); + + [DllImport(libX11)] + public static extern IntPtr XCreateSimpleWindow(IntPtr display, IntPtr parent, int x, int y, int width, + int height, int border_width, IntPtr border, IntPtr background); + + [DllImport(libX11)] + public static extern int XMapWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XUnmapWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XMapSubindows(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XUnmapSubwindows(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern IntPtr XRootWindow(IntPtr display, int screen_number); + [DllImport(libX11)] + public static extern IntPtr XDefaultRootWindow(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XNextEvent(IntPtr display, out XEvent xevent); + + [DllImport(libX11)] + public static extern int XConnectionNumber(IntPtr diplay); + + [DllImport(libX11)] + public static extern int XPending(IntPtr diplay); + + [DllImport(libX11)] + public static extern IntPtr XSelectInput(IntPtr display, IntPtr window, IntPtr mask); + + [DllImport(libX11)] + public static extern int XDestroyWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XReparentWindow(IntPtr display, IntPtr window, IntPtr parent, int x, int y); + + [DllImport(libX11)] + public static extern int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, int width, int height); + + [DllImport(libX11)] + public static extern int XResizeWindow(IntPtr display, IntPtr window, int width, int height); + + [DllImport(libX11)] + public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes); + + [DllImport(libX11)] + public static extern int XFlush(IntPtr display); + + [DllImport(libX11)] + public static extern int XSetWMName(IntPtr display, IntPtr window, ref XTextProperty text_prop); + + [DllImport(libX11)] + public static extern int XStoreName(IntPtr display, IntPtr window, string window_name); + + [DllImport(libX11)] + public static extern int XFetchName(IntPtr display, IntPtr window, ref IntPtr window_name); + + [DllImport(libX11)] + public static extern int XSendEvent(IntPtr display, IntPtr window, bool propagate, IntPtr event_mask, + ref XEvent send_event); + + [DllImport(libX11)] + public static extern int XQueryTree(IntPtr display, IntPtr window, out IntPtr root_return, + out IntPtr parent_return, out IntPtr children_return, out int nchildren_return); + + [DllImport(libX11)] + public static extern int XFree(IntPtr data); + + [DllImport(libX11)] + public static extern int XRaiseWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern uint XLowerWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern uint XConfigureWindow(IntPtr display, IntPtr window, ChangeWindowFlags value_mask, + ref XWindowChanges values); + + [DllImport(libX11)] + public static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists); + + [DllImport(libX11)] + public static extern int XInternAtoms(IntPtr display, string[] atom_names, int atom_count, bool only_if_exists, + IntPtr[] atoms); + + [DllImport(libX11)] + public static extern int XSetWMProtocols(IntPtr display, IntPtr window, IntPtr[] protocols, int count); + + [DllImport(libX11)] + public static extern int XGrabPointer(IntPtr display, IntPtr window, bool owner_events, EventMask event_mask, + GrabMode pointer_mode, GrabMode keyboard_mode, IntPtr confine_to, IntPtr cursor, IntPtr timestamp); + + [DllImport(libX11)] + public static extern int XUngrabPointer(IntPtr display, IntPtr timestamp); + + [DllImport(libX11)] + public static extern bool XQueryPointer(IntPtr display, IntPtr window, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int win_x, out int win_y, out int keys_buttons); + + [DllImport(libX11)] + public static extern bool XTranslateCoordinates(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, + int src_y, out int intdest_x_return, out int dest_y_return, out IntPtr child_return); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, out IntPtr root, out int x, out int y, + out int width, out int height, out int border_width, out int depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y, + out int width, out int height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, out int x, out int y, + IntPtr width, IntPtr height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern bool XGetGeometry(IntPtr display, IntPtr window, IntPtr root, IntPtr x, IntPtr y, + out int width, out int height, IntPtr border_width, IntPtr depth); + + [DllImport(libX11)] + public static extern uint XWarpPointer(IntPtr display, IntPtr src_w, IntPtr dest_w, int src_x, int src_y, + uint src_width, uint src_height, int dest_x, int dest_y); + + [DllImport(libX11)] + public static extern int XClearWindow(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XClearArea(IntPtr display, IntPtr window, int x, int y, int width, int height, + bool exposures); + + // Colormaps + [DllImport(libX11)] + public static extern IntPtr XDefaultScreenOfDisplay(IntPtr display); + + [DllImport(libX11)] + public static extern int XScreenNumberOfScreen(IntPtr display, IntPtr Screen); + + [DllImport(libX11)] + public static extern IntPtr XDefaultVisual(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern uint XDefaultDepth(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern int XDefaultScreen(IntPtr display); + + [DllImport(libX11)] + public static extern IntPtr XDefaultColormap(IntPtr display, int screen_number); + + [DllImport(libX11)] + public static extern int XLookupColor(IntPtr display, IntPtr Colormap, string Coloranem, + ref XColor exact_def_color, ref XColor screen_def_color); + + [DllImport(libX11)] + public static extern int XAllocColor(IntPtr display, IntPtr Colormap, ref XColor colorcell_def); + + [DllImport(libX11)] + public static extern int XSetTransientForHint(IntPtr display, IntPtr window, IntPtr prop_window); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref MotifWmHints data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref uint value, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, ref IntPtr value, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, uint[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, int[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, IntPtr[] data, int nelements); + + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, IntPtr atoms, int nelements); + + [DllImport(libX11, CharSet = CharSet.Ansi)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, string text, int text_length); + + [DllImport(libX11)] + public static extern int XDeleteProperty(IntPtr display, IntPtr window, IntPtr property); + + // Drawing + [DllImport(libX11)] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr window, IntPtr valuemask, ref XGCValues values); + + [DllImport(libX11)] + public static extern int XFreeGC(IntPtr display, IntPtr gc); + + [DllImport(libX11)] + public static extern int XSetFunction(IntPtr display, IntPtr gc, GXFunction function); + + [DllImport(libX11)] + internal static extern int XSetLineAttributes(IntPtr display, IntPtr gc, int line_width, GCLineStyle line_style, + GCCapStyle cap_style, GCJoinStyle join_style); + + [DllImport(libX11)] + public static extern int XDrawLine(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int x2, int y2); + + [DllImport(libX11)] + public static extern int XDrawRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width, + int height); + + [DllImport(libX11)] + public static extern int XFillRectangle(IntPtr display, IntPtr drawable, IntPtr gc, int x1, int y1, int width, + int height); + + [DllImport(libX11)] + public static extern int XSetWindowBackground(IntPtr display, IntPtr window, IntPtr background); + + [DllImport(libX11)] + public static extern int XCopyArea(IntPtr display, IntPtr src, IntPtr dest, IntPtr gc, int src_x, int src_y, + int width, int height, int dest_x, int dest_y); + + [DllImport(libX11)] + public static extern int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr atom, IntPtr long_offset, + IntPtr long_length, bool delete, IntPtr req_type, out IntPtr actual_type, out int actual_format, + out IntPtr nitems, out IntPtr bytes_after, ref IntPtr prop); + + [DllImport(libX11)] + public static extern int XSetInputFocus(IntPtr display, IntPtr window, RevertTo revert_to, IntPtr time); + + [DllImport(libX11)] + public static extern int XIconifyWindow(IntPtr display, IntPtr window, int screen_number); + + [DllImport(libX11)] + public static extern int XDefineCursor(IntPtr display, IntPtr window, IntPtr cursor); + + [DllImport(libX11)] + public static extern int XUndefineCursor(IntPtr display, IntPtr window); + + [DllImport(libX11)] + public static extern int XFreeCursor(IntPtr display, IntPtr cursor); + + [DllImport(libX11)] + public static extern IntPtr XCreateFontCursor(IntPtr display, CursorFontShape shape); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmapCursor(IntPtr display, IntPtr source, IntPtr mask, + ref XColor foreground_color, ref XColor background_color, int x_hot, int y_hot); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmapFromBitmapData(IntPtr display, IntPtr drawable, byte[] data, int width, + int height, IntPtr fg, IntPtr bg, int depth); + + [DllImport(libX11)] + public static extern IntPtr XCreatePixmap(IntPtr display, IntPtr d, int width, int height, int depth); + + [DllImport(libX11)] + public static extern IntPtr XFreePixmap(IntPtr display, IntPtr pixmap); + + [DllImport(libX11)] + public static extern int XQueryBestCursor(IntPtr display, IntPtr drawable, int width, int height, + out int best_width, out int best_height); + + [DllImport(libX11)] + public static extern IntPtr XWhitePixel(IntPtr display, int screen_no); + + [DllImport(libX11)] + public static extern IntPtr XBlackPixel(IntPtr display, int screen_no); + + [DllImport(libX11)] + public static extern void XGrabServer(IntPtr display); + + [DllImport(libX11)] + public static extern void XUngrabServer(IntPtr display); + + [DllImport(libX11)] + public static extern void XGetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints, + out IntPtr supplied_return); + + [DllImport(libX11)] + public static extern void XSetWMNormalHints(IntPtr display, IntPtr window, ref XSizeHints hints); + + [DllImport(libX11)] + public static extern void XSetZoomHints(IntPtr display, IntPtr window, ref XSizeHints hints); + + [DllImport(libX11)] + public static extern void XSetWMHints(IntPtr display, IntPtr window, ref XWMHints wmhints); + + [DllImport(libX11)] + public static extern int XGetIconSizes(IntPtr display, IntPtr window, out IntPtr size_list, out int count); + + [DllImport(libX11)] + public static extern IntPtr XSetErrorHandler(XErrorHandler error_handler); + + [DllImport(libX11)] + public static extern IntPtr XGetErrorText(IntPtr display, byte code, StringBuilder buffer, int length); + + [DllImport(libX11)] + public static extern int XInitThreads(); + + [DllImport(libX11)] + public static extern int XConvertSelection(IntPtr display, IntPtr selection, IntPtr target, IntPtr property, + IntPtr requestor, IntPtr time); + + [DllImport(libX11)] + public static extern IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection); + + [DllImport(libX11)] + public static extern int XSetSelectionOwner(IntPtr display, IntPtr selection, IntPtr owner, IntPtr time); + + [DllImport(libX11)] + public static extern int XSetPlaneMask(IntPtr display, IntPtr gc, IntPtr mask); + + [DllImport(libX11)] + public static extern int XSetForeground(IntPtr display, IntPtr gc, UIntPtr foreground); + + [DllImport(libX11)] + public static extern int XSetBackground(IntPtr display, IntPtr gc, UIntPtr background); + + [DllImport(libX11)] + public static extern int XBell(IntPtr display, int percent); + + [DllImport(libX11)] + public static extern int XChangeActivePointerGrab(IntPtr display, EventMask event_mask, IntPtr cursor, + IntPtr time); + + [DllImport(libX11)] + public static extern bool XFilterEvent(ref XEvent xevent, IntPtr window); + + [DllImport(libX11)] + public static extern void XkbSetDetectableAutoRepeat(IntPtr display, bool detectable, IntPtr supported); + + [DllImport(libX11)] + public static extern void XPeekEvent(IntPtr display, out XEvent xevent); - [DllImport("libX11.so.6")] - public static extern IntPtr XInitThreads(); - - [DllImport("libX11.so.6")] - public static extern IntPtr XOpenDisplay(IntPtr name); - - [DllImport("libX11.so.6")] - public static extern int XConnectionNumber(IntPtr display); + [DllImport(libX11)] + public static extern void XMatchVisualInfo(IntPtr display, int screen, int depth, int klass, out XVisualInfo info); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern IntPtr XLockDisplay(IntPtr display); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern IntPtr XUnlockDisplay(IntPtr display); - [DllImport("libX11.so.6")] - public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); - - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern int XInitImage(ref XImage image); - [DllImport("libX11.so.6")] + [DllImport(libX11)] public static extern int XDestroyImage(ref XImage image); - - [DllImport("libX11.so.6")] - public static extern IntPtr XSetErrorHandler(XErrorHandler handler); - [DllImport("libX11.so.6")] + [DllImport(libX11)] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + [DllImport(libX11)] public static extern int XSync(IntPtr display, bool discard); - [DllImport("libX11.so.6")] - public static extern int XNextEvent(IntPtr display, out XEvent ev); + [DllImport(libX11)] + public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create); - [DllImport("libX11.so.6")] - public static extern bool XPending(IntPtr display); - - [StructLayout(LayoutKind.Sequential)] - public struct XAnyEvent + public struct XGeometry { - - public int Type; - public ulong Serial; /* # of last request processed by server */ - public bool send_event; /* true if this came from a SendEvent request */ - public IntPtr display; /* Display the event was read from */ - public IntPtr window; /* window on which event was requested in event mask */ + public IntPtr root; + public int x; + public int y; + public int width; + public int height; + public int bw; + public int d; } - [StructLayout(LayoutKind.Explicit)] - public unsafe struct XEvent + public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo) { - [FieldOffset(0)] public int Type; - [FieldOffset(0)] public XAnyEvent XAny; - [FieldOffset(0)] private fixed int pad[40]; + geo = new XGeometry(); + return XGetGeometry(display, window, out geo.root, out geo.x, out geo.y, out geo.width, out geo.height, + out geo.bw, out geo.d); } - public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error); - - [DllImport("libX11.so.6")] - public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, - int srcx, int srcy, int destx, int desty, uint width, uint height); - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct XErrorEvent - { - public int type; - public IntPtr* display; /* Display the event was read from */ - public ulong serial; /* serial number of failed request */ - public byte error_code; /* error code of failed request */ - public byte request_code; /* Major op-code of failed request */ - public byte minor_code; /* Minor op-code of failed request */ - public IntPtr resourceid; /* resource id */ - } - - [StructLayout(LayoutKind.Sequential)] - public unsafe struct XImage - { - public int width, height; /* size of image */ - public int xoffset; /* number of pixels offset in X direction */ - public int format; /* XYBitmap, XYPixmap, ZPixmap */ - public IntPtr data; /* pointer to image data */ - public int byte_order; /* data byte order, LSBFirst, MSBFirst */ - public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ - public int bitmap_bit_order; /* LSBFirst, MSBFirst */ - public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ - public int depth; /* depth of image */ - public int bytes_per_line; /* accelerator to next scanline */ - public int bits_per_pixel; /* bits per pixel (ZPixmap) */ - public ulong red_mask; /* bits in z arrangement */ - public ulong green_mask; - public ulong blue_mask; - private fixed byte funcs[128]; - } } } From 80950d0eb17136903799b56edf83b503d32aa3d3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 11:11:56 +0300 Subject: [PATCH 03/86] [X11] Use 24-bit visuals so GL drawing works on nVidia cards --- src/Avalonia.X11/X11Framebuffer.cs | 2 +- src/Avalonia.X11/X11Info.cs | 3 --- src/Avalonia.X11/X11Window.cs | 16 ++++++---------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs index ef93a7347b..2d6955f47a 100644 --- a/src/Avalonia.X11/X11Framebuffer.cs +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -34,7 +34,7 @@ namespace Avalonia.X11 image.bitmap_unit = bitsPerPixel; image.bitmap_bit_order = 0;// LSBFirst; image.bitmap_pad = bitsPerPixel; - image.depth = 32; + image.depth = 24; image.bytes_per_line = RowBytes - Width * 4; image.bits_per_pixel = bitsPerPixel; XLockDisplay(_display); diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 6ad9faffbc..ddad739dd0 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -14,7 +14,6 @@ namespace Avalonia.X11 public IntPtr BlackPixel { get; } public IntPtr RootWindow { get; } public IntPtr DefaultRootWindow { get; } - public XVisualInfo MatchedVisual { get; } public X11Atoms Atoms { get; } public IntPtr LastActivityTimestamp { get; set; } @@ -28,8 +27,6 @@ namespace Avalonia.X11 RootWindow = XRootWindow(display, DefaultScreen); DefaultRootWindow = XDefaultRootWindow(display); Atoms = new X11Atoms(display); - XMatchVisualInfo(display, DefaultScreen, 32, 4, out var info); - MatchedVisual = info; } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 944044cfa8..6bf59866f1 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -44,15 +44,11 @@ namespace Avalonia.X11 XSetWindowAttributes attr = new XSetWindowAttributes(); var valueMask = default(SetWindowValuemask); - /* - _handle = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 10, 10, 100, 100, 0, IntPtr.Zero, - IntPtr.Zero);*/ - var vinfo = _x11.MatchedVisual; - attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, vinfo.visual, 0); - attr.backing_store = 2; + + attr.backing_store = 1; attr.bit_gravity = Gravity.NorthWestGravity; attr.win_gravity = Gravity.NorthWestGravity; - valueMask |= SetWindowValuemask.ColorMap | SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel + valueMask |= SetWindowValuemask.BackPixel | SetWindowValuemask.BorderPixel | SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore | SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity; @@ -63,8 +59,8 @@ namespace Avalonia.X11 } _handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0, - (int)vinfo.depth, - (int)CreateWindowArgs.InputOutput, vinfo.visual, + 24, + (int)CreateWindowArgs.InputOutput, IntPtr.Zero, new UIntPtr((uint)valueMask), ref attr); @@ -278,7 +274,7 @@ namespace Avalonia.X11 rv |= InputModifiers.Windows; return rv; } - static Stopwatch St = Stopwatch.StartNew(); + private bool _systemDecorations = true; private bool _canResize = true; From 9ee771b425d3f4aa0d0a951fc861144c86096bd4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 11:12:14 +0300 Subject: [PATCH 04/86] [X11] Reference X11 backend from ControlCatalog --- samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index ec841db5b2..8410a197ab 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -8,6 +8,7 @@ + From 9b7f8b5f35a1d748158b43c55de44eea80d92e31 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 11:55:48 +0300 Subject: [PATCH 05/86] [X11] Use a child window for OpenGL rendering. Fixes tons of issues on nVidia cards --- src/Avalonia.X11/X11Window.cs | 48 +++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 6bf59866f1..f102a6abff 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -25,6 +25,7 @@ namespace Avalonia.X11 private IMouseDevice _mouse; private Point _position; private IntPtr _handle; + private IntPtr _renderHandle; private bool _mapped; class InputEventContainer @@ -62,8 +63,12 @@ namespace Avalonia.X11 24, (int)CreateWindowArgs.InputOutput, IntPtr.Zero, new UIntPtr((uint)valueMask), ref attr); - - + _renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24, + (int)CreateWindowArgs.InputOutput, + IntPtr.Zero, + new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity | + SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); + Handle = new PlatformHandle(_handle, "XID"); ClientSize = new Size(400, 400); _eventHandler = OnEvent; @@ -86,7 +91,7 @@ namespace Avalonia.X11 if (feature != null) surfaces.Insert(0, new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, - new SurfaceInfo(_x11.DeferredDisplay, _handle))); + new SurfaceInfo(_x11.DeferredDisplay, _handle, _renderHandle))); Surfaces = surfaces.ToArray(); UpdateWmHits(); XFlush(_x11.Display); @@ -95,10 +100,12 @@ namespace Avalonia.X11 class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { private readonly IntPtr _display; + private readonly IntPtr _parent; - public SurfaceInfo(IntPtr display, IntPtr xid) + public SurfaceInfo(IntPtr display, IntPtr parent, IntPtr xid) { _display = display; + _parent = parent; Handle = xid; } public IntPtr Handle { get; } @@ -108,8 +115,10 @@ namespace Avalonia.X11 get { XLockDisplay(_display); - - XGetGeometry(_display, Handle, out var geo); + XGetGeometry(_display, _parent, out var geo); + XResizeWindow(_display, Handle, geo.width, geo.height); + XFlush(_display); + XSync(_display, true); XUnlockDisplay(_display); return new System.Drawing.Size(geo.width, geo.height); } @@ -167,7 +176,10 @@ namespace Avalonia.X11 unsafe void OnEvent(XEvent ev) { if (ev.type == XEventName.MapNotify) + { _mapped = true; + XMapWindow(_x11.Display, _renderHandle); + } else if (ev.type == XEventName.UnmapNotify) _mapped = false; else if (ev.type == XEventName.Expose) @@ -232,12 +244,7 @@ namespace Avalonia.X11 } else if (ev.type == XEventName.DestroyNotify) { - if (_handle != IntPtr.Zero) - { - _platform.Windows.Remove(_handle); - _handle = IntPtr.Zero; - Closed?.Invoke(); - } + Cleanup(); } else if (ev.type == XEventName.ClientMessage) { @@ -335,6 +342,15 @@ namespace Avalonia.X11 } public void Dispose() + { + if (_handle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _handle); + Cleanup(); + } + } + + void Cleanup() { if (_handle != IntPtr.Zero) { @@ -342,10 +358,16 @@ namespace Avalonia.X11 _platform.Windows.Remove(_handle); _handle = IntPtr.Zero; Closed?.Invoke(); - + } + + if (_renderHandle != IntPtr.Zero) + { + XDestroyWindow(_x11.Display, _renderHandle); + _renderHandle = IntPtr.Zero; } } + public void Show() => XMapWindow(_x11.Display, _handle); public void Hide() => XUnmapWindow(_x11.Display, _handle); From 074d8949a6900b4d8ae2cd89a7fe85dbf4483709 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 24 Oct 2018 12:37:01 +0300 Subject: [PATCH 06/86] [X11] Enforce layout priority --- src/Avalonia.X11/X11Window.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index f102a6abff..deb0491168 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -240,7 +240,8 @@ namespace Avalonia.X11 Resized?.Invoke(nsize); if (changedPos) PositionChanged?.Invoke(npos); - }); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + }, DispatcherPriority.Layout); } else if (ev.type == XEventName.DestroyNotify) { From 52a0514ee112565a5e3f2d0c7e2d1e799aa7f1a0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 26 Oct 2018 22:32:21 +0300 Subject: [PATCH 07/86] [X11] BeginMove/ResizeDrag and WindowState --- src/Avalonia.X11/X11Window.cs | 157 ++++++++++++++++++++++++++++------ src/Avalonia.X11/XLib.cs | 61 ++++++++++++- 2 files changed, 189 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index deb0491168..036c7d0f34 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Linq; using System.Reactive.Disposables; using Avalonia.Controls; using Avalonia.Input; @@ -13,7 +14,7 @@ using Avalonia.Threading; using static Avalonia.X11.XLib; namespace Avalonia.X11 { - class X11Window : IWindowImpl, IPopupImpl + unsafe class X11Window : IWindowImpl, IPopupImpl { private readonly AvaloniaX11Platform _platform; private readonly bool _popup; @@ -134,7 +135,6 @@ namespace Avalonia.X11 if (_popup || !_systemDecorations) { - functions = 0; decorations = 0; } @@ -165,7 +165,6 @@ namespace Avalonia.X11 public Action Deactivated { get; set; } public Action Activated { get; set; } public Func Closing { get; set; } - public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } public Action Closed { get; set; } public Action PositionChanged { get; set; } @@ -190,7 +189,10 @@ namespace Avalonia.X11 Deactivated?.Invoke(); else if (ev.type == XEventName.MotionNotify) MouseEvent(RawMouseEventType.Move, ev, ev.MotionEvent.state); - + else if (ev.type == XEventName.PropertyNotify) + { + OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0); + } else if (ev.type == XEventName.ButtonPress) { if (ev.ButtonEvent.button < 4) @@ -260,8 +262,76 @@ namespace Avalonia.X11 } } } - - + + private WindowState _lastWindowState; + public WindowState WindowState + { + get { return _lastWindowState; } + set + { + _lastWindowState = value; + if (value == WindowState.Minimized) + { + XIconifyWindow(_x11.Display, _handle, _x11.DefaultScreen); + } + else if (value == WindowState.Maximized) + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero); + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)1, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } + else + { + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero); + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, + _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + } + } + } + + private void OnPropertyChange(IntPtr atom, bool hasValue) + { + if (atom == _x11.Atoms._NET_WM_STATE) + { + WindowState state = WindowState.Normal; + if(hasValue) + { + + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256), + false, (IntPtr)Atom.XA_ATOM, out var actualAtom, out var actualFormat, out var nitems, out var bytesAfter, + out var prop); + int maximized = 0; + var pitems = (IntPtr*)prop.ToPointer(); + for (var c = 0; c < nitems.ToInt32(); c++) + { + if (pitems[c] == _x11.Atoms._NET_WM_STATE_HIDDEN) + { + state = WindowState.Minimized; + break; + } + + if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ || + pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT) + { + maximized++; + if (maximized == 2) + { + state = WindowState.Maximized; + break; + } + } + } + XFree(prop); + } + if (_lastWindowState != state) + { + _lastWindowState = state; + WindowStateChanged?.Invoke(state); + } + } + + } + InputModifiers TranslateModifiers(XModifierMask state) { @@ -437,25 +507,8 @@ namespace Avalonia.X11 { if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) { - - var ev = new XEvent - { - AnyEvent = - { - type = XEventName.ClientMessage, - window = _handle, - }, - ClientMessageEvent = { - message_type = _x11.Atoms._NET_ACTIVE_WINDOW, - format = 32, - ptr1 = new IntPtr(1), - ptr2 = _x11.LastActivityTimestamp - - } - }; - - XSendEvent(_x11.Display, _x11.RootWindow, false, - new IntPtr((int)(XEventMask.SubstructureRedirectMask | XEventMask.StructureNotifyMask)), ref ev); + SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp, + IntPtr.Zero); } else { @@ -467,14 +520,64 @@ namespace Avalonia.X11 public IScreenImpl Screen { get; } = new ScreenStub(); public Size MaxClientSize { get; } = new Size(1920, 1280); - - + + + + void SendNetWMMessage(IntPtr message_type, IntPtr l0, + IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null) + { + XEvent xev; + + xev = new XEvent(); + xev.ClientMessageEvent.type = XEventName.ClientMessage; + xev.ClientMessageEvent.send_event = true; + xev.ClientMessageEvent.window = _handle; + xev.ClientMessageEvent.message_type = message_type; + xev.ClientMessageEvent.format = 32; + xev.ClientMessageEvent.ptr1 = l0; + xev.ClientMessageEvent.ptr2 = l1 ?? IntPtr.Zero; + xev.ClientMessageEvent.ptr3 = l2 ?? IntPtr.Zero; + xev.ClientMessageEvent.ptr4 = l3 ?? IntPtr.Zero; + xev.ClientMessageEvent.ptr4 = l4 ?? IntPtr.Zero; + XSendEvent(_x11.Display, _x11.RootWindow, false, + new IntPtr((int)(EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask)), ref xev); + + } + + void BeginMoveResize(NetWmMoveResize side) + { + var pos = GetCursorPos(_x11); + XUngrabPointer(_x11.Display, _x11.LastActivityTimestamp); + SendNetWMMessage (_x11.Atoms._NET_WM_MOVERESIZE, (IntPtr) pos.x, (IntPtr) pos.y, + (IntPtr) side, + (IntPtr) 1, (IntPtr)1); // left button + } + public void BeginMoveDrag() { + BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE); } public void BeginResizeDrag(WindowEdge edge) { + var side = NetWmMoveResize._NET_WM_MOVERESIZE_CANCEL; + if (edge == WindowEdge.East) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_RIGHT; + if (edge == WindowEdge.North) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOP; + if (edge == WindowEdge.South) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOM; + if (edge == WindowEdge.West) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_LEFT; + if (edge == WindowEdge.NorthEast) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + if (edge == WindowEdge.NorthWest) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_TOPLEFT; + if (edge == WindowEdge.SouthEast) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + if (edge == WindowEdge.SouthWest) + side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + BeginMoveResize(side); } public void SetTitle(string title) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 593e6b438f..d6c4ec6593 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -12,7 +12,7 @@ using System.Text; namespace Avalonia.X11 { - internal static class XLib + internal unsafe static class XLib { const string libX11 = "X11"; @@ -262,7 +262,7 @@ namespace Avalonia.X11 [DllImport(libX11)] public static extern int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr atom, IntPtr long_offset, IntPtr long_length, bool delete, IntPtr req_type, out IntPtr actual_type, out int actual_format, - out IntPtr nitems, out IntPtr bytes_after, ref IntPtr prop); + out IntPtr nitems, out IntPtr bytes_after, out IntPtr prop); [DllImport(libX11)] public static extern int XSetInputFocus(IntPtr display, IntPtr window, RevertTo revert_to, IntPtr time); @@ -416,6 +416,63 @@ namespace Avalonia.X11 return XGetGeometry(display, window, out geo.root, out geo.x, out geo.y, out geo.width, out geo.height, out geo.bw, out geo.d); } + + public static void QueryPointer (IntPtr display, IntPtr w, out IntPtr root, out IntPtr child, + out int root_x, out int root_y, out int child_x, out int child_y, + out int mask) + { + + IntPtr c; + + XGrabServer (display); + + XQueryPointer(display, w, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + + if (root != w) + c = root; + + IntPtr child_last = IntPtr.Zero; + while (c != IntPtr.Zero) { + child_last = c; + XQueryPointer(display, c, out root, out c, + out root_x, out root_y, out child_x, out child_y, + out mask); + } + XUngrabServer (display); + XFlush (display); + + child = child_last; + } + + public static (int x, int y) GetCursorPos(X11Info x11, IntPtr? handle = null) + { + IntPtr root; + IntPtr child; + int root_x; + int root_y; + int win_x; + int win_y; + int keys_buttons; + + + + QueryPointer(x11.Display, handle ?? x11.RootWindow, out root, out child, out root_x, out root_y, out win_x, out win_y, + out keys_buttons); + + + if (handle != null) + { + return (win_x, win_y); + } + else + { + return (root_x, root_y); + } + } + + } } From 8f80351bcce3229ebdf6b1730fa4bce1440700de Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 26 Oct 2018 22:54:01 +0300 Subject: [PATCH 08/86] [X11] Implemented cursor support --- src/Avalonia.X11/Stubs.cs | 8 ---- src/Avalonia.X11/X11Atoms.cs | 11 +----- src/Avalonia.X11/X11CursorFactory.cs | 57 ++++++++++++++++++++++++++++ src/Avalonia.X11/X11Info.cs | 2 + src/Avalonia.X11/X11Platform.cs | 2 +- src/Avalonia.X11/X11Window.cs | 8 ++++ 6 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 src/Avalonia.X11/X11CursorFactory.cs diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs index ed14ab7878..6dda7b81ca 100644 --- a/src/Avalonia.X11/Stubs.cs +++ b/src/Avalonia.X11/Stubs.cs @@ -9,14 +9,6 @@ using Avalonia.Platform; namespace Avalonia.X11 { - class CursorFactoryStub : IStandardCursorFactory - { - public IPlatformHandle GetCursor(StandardCursorType cursorType) - { - return new PlatformHandle(IntPtr.Zero, "FAKE"); - } - } - class ClipboardStub : IClipboard { private string _text; diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index e7b83f59d3..50a735b2ef 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -176,9 +176,6 @@ namespace Avalonia.X11 { public readonly IntPtr OEMTEXT; public readonly IntPtr UNICODETEXT; public readonly IntPtr TARGETS; - public readonly IntPtr PostAtom; - public readonly IntPtr HoverState; - public readonly IntPtr AsyncAtom; public X11Atoms (IntPtr display) { @@ -249,10 +246,7 @@ namespace Avalonia.X11 { "PRIMARY", "COMPOUND_TEXT", "UTF8_STRING", - "TARGETS", - "_SWF_AsyncAtom", - "_SWF_PostMessageAtom", - "_SWF_HoverAtom" }; + "TARGETS"}; IntPtr[] atoms = new IntPtr [atom_names.Length];; @@ -324,9 +318,6 @@ namespace Avalonia.X11 { OEMTEXT = atoms [off++]; UNICODETEXT = atoms [off++]; TARGETS = atoms [off++]; - AsyncAtom = atoms [off++]; - PostAtom = atoms [off++]; - HoverState = atoms [off++]; DIB = XA_PIXMAP; diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs new file mode 100644 index 0000000000..d25a0da0a2 --- /dev/null +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.X11 +{ + class X11CursorFactory : IStandardCursorFactory + { + private readonly IntPtr _display; + private Dictionary _cursors; + + private static readonly Dictionary s_mapping = + new Dictionary + { + {StandardCursorType.Arrow, CursorFontShape.XC_arrow}, + {StandardCursorType.Cross, CursorFontShape.XC_cross}, + {StandardCursorType.Hand, CursorFontShape.XC_hand1}, + {StandardCursorType.Help, CursorFontShape.XC_question_arrow}, + {StandardCursorType.Ibeam, CursorFontShape.XC_xterm}, + {StandardCursorType.No, CursorFontShape.XC_X_cursor}, + {StandardCursorType.Wait, CursorFontShape.XC_watch}, + {StandardCursorType.AppStarting, CursorFontShape.XC_watch}, + {StandardCursorType.BottomSize, CursorFontShape.XC_bottom_side}, + {StandardCursorType.DragCopy, CursorFontShape.XC_center_ptr}, + {StandardCursorType.DragLink, CursorFontShape.XC_fleur}, + {StandardCursorType.DragMove, CursorFontShape.XC_diamond_cross}, + {StandardCursorType.LeftSide, CursorFontShape.XC_left_side}, + {StandardCursorType.RightSide, CursorFontShape.XC_right_side}, + {StandardCursorType.SizeAll, CursorFontShape.XC_sizing}, + {StandardCursorType.TopSide, CursorFontShape.XC_top_side}, + {StandardCursorType.UpArrow, CursorFontShape.XC_sb_up_arrow}, + {StandardCursorType.BottomLeftCorner, CursorFontShape.XC_bottom_left_corner}, + {StandardCursorType.BottomRightCorner, CursorFontShape.XC_bottom_right_corner}, + {StandardCursorType.SizeNorthSouth, CursorFontShape.XC_sb_v_double_arrow}, + {StandardCursorType.SizeWestEast, CursorFontShape.XC_sb_h_double_arrow}, + {StandardCursorType.TopLeftCorner, CursorFontShape.XC_top_left_corner}, + {StandardCursorType.TopRightCorner, CursorFontShape.XC_top_right_corner}, + }; + + public X11CursorFactory(IntPtr display) + { + _display = display; + _cursors = Enum.GetValues(typeof(CursorFontShape)).Cast() + .ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id)); + } + + public IPlatformHandle GetCursor(StandardCursorType cursorType) + { + var handle = s_mapping.TryGetValue(cursorType, out var shape) + ? _cursors[shape] + : _cursors[CursorFontShape.XC_arrow]; + return new PlatformHandle(handle, "XCURSOR"); + } + } +} diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index ddad739dd0..527cdc31ef 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -14,6 +14,7 @@ namespace Avalonia.X11 public IntPtr BlackPixel { get; } public IntPtr RootWindow { get; } public IntPtr DefaultRootWindow { get; } + public IntPtr DefaultCursor { get; } public X11Atoms Atoms { get; } public IntPtr LastActivityTimestamp { get; set; } @@ -25,6 +26,7 @@ namespace Avalonia.X11 DefaultScreen = XDefaultScreen(display); BlackPixel = XBlackPixel(display, DefaultScreen); RootWindow = XRootWindow(display, DefaultScreen); + DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_arrow); DefaultRootWindow = XDefaultRootWindow(display); Atoms = new X11Atoms(display); } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 33497be411..b3ffc2a30b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -36,7 +36,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) .Bind().ToFunc(() => KeyboardDevice) - .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToSingleton() .Bind().ToConstant(new PlatformSettingsStub()) .Bind().ToConstant(new SystemDialogsStub()) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 036c7d0f34..82564e98e0 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -482,6 +482,14 @@ namespace Avalonia.X11 public void SetCursor(IPlatformHandle cursor) { + if (cursor == null) + XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor); + else + { + if (cursor.HandleDescriptor != "XCURSOR") + throw new ArgumentException("Expected XCURSOR handle type"); + XDefineCursor(_x11.Display, _handle, cursor.Handle); + } } public IPlatformHandle Handle { get; } From 2ad8e3162bd671ad02b0dff4700f83df995e8846 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 27 Oct 2018 13:34:11 +0300 Subject: [PATCH 09/86] [X11] MinMax size/SetTopmost/ShowTaskbarIcon/Title --- src/Avalonia.X11/X11Atoms.cs | 150 +++------------------------------- src/Avalonia.X11/X11Window.cs | 56 ++++++++++--- src/Avalonia.X11/XLib.cs | 3 + 3 files changed, 58 insertions(+), 151 deletions(-) diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index 50a735b2ef..51a4fc52f0 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -22,6 +22,7 @@ // using System; +using System.Linq; using static Avalonia.X11.XLib; // ReSharper disable FieldCanBeMadeReadOnly.Global // ReSharper disable IdentifierTypo @@ -172,158 +173,27 @@ namespace Avalonia.X11 { public readonly IntPtr _NET_WM_WINDOW_TYPE_NORMAL; public readonly IntPtr CLIPBOARD; public readonly IntPtr PRIMARY; - public readonly IntPtr DIB; public readonly IntPtr OEMTEXT; public readonly IntPtr UNICODETEXT; public readonly IntPtr TARGETS; + public readonly IntPtr UTF8_STRING; public X11Atoms (IntPtr display) { // make sure this array stays in sync with the statements below - string [] atom_names = new string[] { - "WM_PROTOCOLS", - "WM_DELETE_WINDOW", - "WM_TAKE_FOCUS", - "_NET_SUPPORTED", - "_NET_CLIENT_LIST", - "_NET_NUMBER_OF_DESKTOPS", - "_NET_DESKTOP_GEOMETRY", - "_NET_DESKTOP_VIEWPORT", - "_NET_CURRENT_DESKTOP", - "_NET_DESKTOP_NAMES", - "_NET_ACTIVE_WINDOW", - "_NET_WORKAREA", - "_NET_SUPPORTING_WM_CHECK", - "_NET_VIRTUAL_ROOTS", - "_NET_DESKTOP_LAYOUT", - "_NET_SHOWING_DESKTOP", - "_NET_CLOSE_WINDOW", - "_NET_MOVERESIZE_WINDOW", - "_NET_WM_MOVERESIZE", - "_NET_RESTACK_WINDOW", - "_NET_REQUEST_FRAME_EXTENTS", - "_NET_WM_NAME", - "_NET_WM_VISIBLE_NAME", - "_NET_WM_ICON_NAME", - "_NET_WM_VISIBLE_ICON_NAME", - "_NET_WM_DESKTOP", - "_NET_WM_WINDOW_TYPE", - "_NET_WM_STATE", - "_NET_WM_ALLOWED_ACTIONS", - "_NET_WM_STRUT", - "_NET_WM_STRUT_PARTIAL", - "_NET_WM_ICON_GEOMETRY", - "_NET_WM_ICON", - "_NET_WM_PID", - "_NET_WM_HANDLED_ICONS", - "_NET_WM_USER_TIME", - "_NET_FRAME_EXTENTS", - "_NET_WM_PING", - "_NET_WM_SYNC_REQUEST", - "_NET_SYSTEM_TRAY_OPCODE", - "_NET_SYSTEM_TRAY_ORIENTATION", - "_NET_WM_STATE_MAXIMIZED_HORZ", - "_NET_WM_STATE_MAXIMIZED_VERT", - "_NET_WM_STATE_HIDDEN", - "_XEMBED", - "_XEMBED_INFO", - "_MOTIF_WM_HINTS", - "_NET_WM_STATE_SKIP_TASKBAR", - "_NET_WM_STATE_ABOVE", - "_NET_WM_STATE_MODAL", - "_NET_WM_CONTEXT_HELP", - "_NET_WM_WINDOW_OPACITY", - "_NET_WM_WINDOW_TYPE_DESKTOP", - "_NET_WM_WINDOW_TYPE_DOCK", - "_NET_WM_WINDOW_TYPE_TOOLBAR", - "_NET_WM_WINDOW_TYPE_MENU", - "_NET_WM_WINDOW_TYPE_UTILITY", - "_NET_WM_WINDOW_TYPE_DIALOG", - "_NET_WM_WINDOW_TYPE_SPLASH", - "_NET_WM_WINDOW_TYPE_NORMAL", - "CLIPBOARD", - "PRIMARY", - "COMPOUND_TEXT", - "UTF8_STRING", - "TARGETS"}; - IntPtr[] atoms = new IntPtr [atom_names.Length];; + var fields = typeof(X11Atoms).GetFields() + .Where(f => f.FieldType == typeof(IntPtr) && (IntPtr)f.GetValue(this) == IntPtr.Zero).ToArray(); + var atomNames = fields.Select(f => f.Name).ToArray(); + + IntPtr[] atoms = new IntPtr [atomNames.Length];; - XInternAtoms (display, atom_names, atom_names.Length, false, atoms); + XInternAtoms (display, atomNames, atomNames.Length, true, atoms); - int off = 0; - WM_PROTOCOLS = atoms [off++]; - WM_DELETE_WINDOW = atoms [off++]; - WM_TAKE_FOCUS = atoms [off++]; - _NET_SUPPORTED = atoms [off++]; - _NET_CLIENT_LIST = atoms [off++]; - _NET_NUMBER_OF_DESKTOPS = atoms [off++]; - _NET_DESKTOP_GEOMETRY = atoms [off++]; - _NET_DESKTOP_VIEWPORT = atoms [off++]; - _NET_CURRENT_DESKTOP = atoms [off++]; - _NET_DESKTOP_NAMES = atoms [off++]; - _NET_ACTIVE_WINDOW = atoms [off++]; - _NET_WORKAREA = atoms [off++]; - _NET_SUPPORTING_WM_CHECK = atoms [off++]; - _NET_VIRTUAL_ROOTS = atoms [off++]; - _NET_DESKTOP_LAYOUT = atoms [off++]; - _NET_SHOWING_DESKTOP = atoms [off++]; - _NET_CLOSE_WINDOW = atoms [off++]; - _NET_MOVERESIZE_WINDOW = atoms [off++]; - _NET_WM_MOVERESIZE = atoms [off++]; - _NET_RESTACK_WINDOW = atoms [off++]; - _NET_REQUEST_FRAME_EXTENTS = atoms [off++]; - _NET_WM_NAME = atoms [off++]; - _NET_WM_VISIBLE_NAME = atoms [off++]; - _NET_WM_ICON_NAME = atoms [off++]; - _NET_WM_VISIBLE_ICON_NAME = atoms [off++]; - _NET_WM_DESKTOP = atoms [off++]; - _NET_WM_WINDOW_TYPE = atoms [off++]; - _NET_WM_STATE = atoms [off++]; - _NET_WM_ALLOWED_ACTIONS = atoms [off++]; - _NET_WM_STRUT = atoms [off++]; - _NET_WM_STRUT_PARTIAL = atoms [off++]; - _NET_WM_ICON_GEOMETRY = atoms [off++]; - _NET_WM_ICON = atoms [off++]; - _NET_WM_PID = atoms [off++]; - _NET_WM_HANDLED_ICONS = atoms [off++]; - _NET_WM_USER_TIME = atoms [off++]; - _NET_FRAME_EXTENTS = atoms [off++]; - _NET_WM_PING = atoms [off++]; - _NET_WM_SYNC_REQUEST = atoms [off++]; - _NET_SYSTEM_TRAY_OPCODE = atoms [off++]; - _NET_SYSTEM_TRAY_ORIENTATION = atoms [off++]; - _NET_WM_STATE_MAXIMIZED_HORZ = atoms [off++]; - _NET_WM_STATE_MAXIMIZED_VERT = atoms [off++]; - _NET_WM_STATE_HIDDEN = atoms [off++]; - _XEMBED = atoms [off++]; - _XEMBED_INFO = atoms [off++]; - _MOTIF_WM_HINTS = atoms [off++]; - _NET_WM_STATE_SKIP_TASKBAR = atoms [off++]; - _NET_WM_STATE_ABOVE = atoms [off++]; - _NET_WM_STATE_MODAL = atoms [off++]; - _NET_WM_CONTEXT_HELP = atoms [off++]; - _NET_WM_WINDOW_OPACITY = atoms [off++]; - _NET_WM_WINDOW_TYPE_DESKTOP = atoms [off++]; - _NET_WM_WINDOW_TYPE_DOCK = atoms [off++]; - _NET_WM_WINDOW_TYPE_TOOLBAR = atoms [off++]; - _NET_WM_WINDOW_TYPE_MENU = atoms [off++]; - _NET_WM_WINDOW_TYPE_UTILITY = atoms [off++]; - _NET_WM_WINDOW_TYPE_DIALOG = atoms [off++]; - _NET_WM_WINDOW_TYPE_SPLASH = atoms [off++]; - _NET_WM_WINDOW_TYPE_NORMAL = atoms [off++]; - CLIPBOARD = atoms [off++]; - PRIMARY = atoms [off++]; - OEMTEXT = atoms [off++]; - UNICODETEXT = atoms [off++]; - TARGETS = atoms [off++]; + for (var c = 0; c < fields.Length; c++) + fields[c].SetValue(this, atoms[c]); - DIB = XA_PIXMAP; - - var defScreen = XDefaultScreen(display); - // XXX multi screen stuff here - _NET_SYSTEM_TRAY_S = XInternAtom (display, "_NET_SYSTEM_TRAY_S" + defScreen.ToString(), false); } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 82564e98e0..523d4dae29 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; +using System.Text; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -94,7 +95,7 @@ namespace Avalonia.X11 new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, new SurfaceInfo(_x11.DeferredDisplay, _handle, _renderHandle))); Surfaces = surfaces.ToArray(); - UpdateWmHits(); + UpdateMotifHits(); XFlush(_x11.Display); } @@ -128,7 +129,7 @@ namespace Avalonia.X11 public double Scaling { get; } = 1; } - void UpdateWmHits() + void UpdateMotifHits() { var functions = MotifFunctions.All; var decorations = MotifDecorations.All; @@ -154,13 +155,36 @@ namespace Avalonia.X11 _x11.Atoms._MOTIF_WM_HINTS, _x11.Atoms._MOTIF_WM_HINTS, 32, PropertyMode.Replace, ref hints, 5); } + + void UpdateSizeHits() + { + var hints = new XSizeHints(); + hints.min_width = (int)_minMaxSize.minSize.Width; + hints.min_height = (int)_minMaxSize.minSize.Height; + hints.height_inc = hints.width_inc = 1; + var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; + // People might be passing double.MaxValue + if (_minMaxSize.maxSize.Width < 100000 && _minMaxSize.maxSize.Height < 100000) + { + + hints.max_width = (int)Math.Max(100000, _minMaxSize.maxSize.Width); + hints.max_height = (int)Math.Max(100000, _minMaxSize.maxSize.Height); + flags |= XSizeHintsFlags.PMaxSize; + } + + hints.flags = (IntPtr)flags; + + XSetWMNormalHints(_x11.Display, _handle, ref hints); + } public Size ClientSize { get; private set; } + //TODO public double Scaling { get; } = 1; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } + //TODO public Action ScalingChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } @@ -355,6 +379,7 @@ namespace Avalonia.X11 private bool _systemDecorations = true; private bool _canResize = true; + private (Size minSize, Size maxSize) _minMaxSize; void ScheduleInput(RawInputEventArgs args) { @@ -451,7 +476,7 @@ namespace Avalonia.X11 public void SetSystemDecorations(bool enabled) { _systemDecorations = enabled; - UpdateWmHits(); + UpdateMotifHits(); } @@ -477,7 +502,7 @@ namespace Avalonia.X11 public void CanResize(bool value) { _canResize = value; - UpdateWmHits(); + UpdateMotifHits(); } public void SetCursor(IPlatformHandle cursor) @@ -590,33 +615,42 @@ namespace Avalonia.X11 public void SetTitle(string title) { + var data = Encoding.UTF8.GetBytes(title); + fixed (void* pdata = data) + { + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_NAME, _x11.Atoms.UTF8_STRING, 8, + PropertyMode.Replace, pdata, data.Length); + XStoreName(_x11.Display, _handle, title); + } } public void SetMinMaxSize(Size minSize, Size maxSize) { - + _minMaxSize = (minSize, maxSize); + UpdateSizeHits(); } public void SetTopmost(bool value) { - + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, + (IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero); } public IDisposable ShowDialog() { + // TODO return Disposable.Empty; } public void SetIcon(IWindowIconImpl icon) { + //TODO } public void ShowTaskbarIcon(bool value) { - } - - - - + SendNetWMMessage(_x11.Atoms._NET_WM_STATE, + (IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero); + } } } diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index d6c4ec6593..0f27191f88 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -215,6 +215,9 @@ namespace Avalonia.X11 [DllImport(libX11)] public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, PropertyMode mode, IntPtr[] data, int nelements); + [DllImport(libX11)] + public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, + int format, PropertyMode mode, void* data, int nelements); [DllImport(libX11)] public static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, From 94b2e2becadb44054b755a8627cdfc45bf6a28d0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Oct 2018 00:28:06 +0200 Subject: [PATCH 10/86] Cache the styles for control types. A lot of time was being spent calling `Attach` on styles that will never match a control. On the first pass, cache the styles that can match a particular control type and use this cache to bypass styles that will never match on subsequent passes. --- src/Avalonia.Styling/Styling/ChildSelector.cs | 20 ++++- .../Styling/DescendentSelector.cs | 16 ++-- src/Avalonia.Styling/Styling/IStyle.cs | 7 +- .../Styling/PropertyEqualsSelector.cs | 6 +- src/Avalonia.Styling/Styling/Selector.cs | 39 ++++++--- src/Avalonia.Styling/Styling/SelectorMatch.cs | 86 ++++++++++++++----- src/Avalonia.Styling/Styling/Style.cs | 24 +++--- src/Avalonia.Styling/Styling/Styles.cs | 45 +++++++++- .../Styling/TemplateSelector.cs | 5 +- .../Styling/TypeNameAndClassSelector.cs | 19 ++-- .../Styling/StyleInclude.cs | 6 +- .../SelectorTests_Child.cs | 8 +- .../SelectorTests_Class.cs | 23 ++--- .../SelectorTests_Descendent.cs | 10 +-- .../SelectorTests_Multiple.cs | 40 ++++++++- .../SelectorTests_Name.cs | 6 +- .../SelectorTests_OfType.cs | 15 +++- .../SelectorTests_PropertyEquals.cs | 11 ++- .../SelectorTests_Template.cs | 31 +++++-- 19 files changed, 313 insertions(+), 104 deletions(-) diff --git a/src/Avalonia.Styling/Styling/ChildSelector.cs b/src/Avalonia.Styling/Styling/ChildSelector.cs index bcbf358721..8b1d2a8553 100644 --- a/src/Avalonia.Styling/Styling/ChildSelector.cs +++ b/src/Avalonia.Styling/Styling/ChildSelector.cs @@ -24,6 +24,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _parent.InTemplate; + /// + public override bool IsCombinator => true; + /// public override Type TargetType => null; @@ -43,11 +46,24 @@ namespace Avalonia.Styling if (controlParent != null) { - return _parent.Match((IStyleable)controlParent, subscribe); + var parentMatch = _parent.Match((IStyleable)controlParent, subscribe); + + if (parentMatch.Result == SelectorMatchResult.Sometimes) + { + return parentMatch; + } + else if (parentMatch.IsMatch) + { + return SelectorMatch.AlwaysThisInstance; + } + else + { + return SelectorMatch.NeverThisInstance; + } } else { - return SelectorMatch.False; + return SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/DescendentSelector.cs b/src/Avalonia.Styling/Styling/DescendentSelector.cs index 943b3f0161..a81908f23d 100644 --- a/src/Avalonia.Styling/Styling/DescendentSelector.cs +++ b/src/Avalonia.Styling/Styling/DescendentSelector.cs @@ -22,6 +22,9 @@ namespace Avalonia.Styling _parent = parent; } + /// + public override bool IsCombinator => true; + /// public override bool InTemplate => _parent.InTemplate; @@ -51,16 +54,13 @@ namespace Avalonia.Styling { var match = _parent.Match((IStyleable)c, subscribe); - if (match.ImmediateResult != null) + if (match.Result == SelectorMatchResult.Sometimes) { - if (match.ImmediateResult == true) - { - return SelectorMatch.True; - } + descendantMatches.Add(match.Activator); } - else + else if (match.IsMatch) { - descendantMatches.Add(match.ObservableResult); + return SelectorMatch.AlwaysThisInstance; } } } @@ -71,7 +71,7 @@ namespace Avalonia.Styling } else { - return SelectorMatch.False; + return SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 3cf8491ae3..da2a08f04d 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -17,7 +17,12 @@ namespace Avalonia.Styling /// /// The control that contains this style. May be null. /// - void Attach(IStyleable control, IStyleHost container); + /// + /// True if the style can match a control of type + /// (even if it does not match this control specifically); false if the style + /// can never match. + /// + bool Attach(IStyleable control, IStyleHost container); void Detach(); } diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 541e8646b1..cfc0998fe0 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -30,6 +30,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _previous?.InTemplate ?? false; + /// + public override bool IsCombinator => false; + /// /// Gets the name of the control to match. /// @@ -78,7 +81,8 @@ namespace Avalonia.Styling } else { - return new SelectorMatch((control.GetValue(_property) ?? string.Empty).Equals(_value)); + var result = (control.GetValue(_property) ?? string.Empty).Equals(_value); + return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index 24c6be9b7a..af209ea970 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -17,6 +17,14 @@ namespace Avalonia.Styling /// public abstract bool InTemplate { get; } + /// + /// Gets a value indicating whether this selector is a combinator. + /// + /// + /// A combinator is a selector such as Child or Descendent which links simple selectors. + /// + public abstract bool IsCombinator { get; } + /// /// Gets the target type of the selector, if available. /// @@ -33,25 +41,32 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - List> inputs = new List>(); - Selector selector = this; + var inputs = new List>(); + var selector = this; + var alwaysThisType = true; + var hitCombinator = false; while (selector != null) { - if (selector.InTemplate && control.TemplatedParent == null) - { - return SelectorMatch.False; - } + hitCombinator |= selector.IsCombinator; var match = selector.Evaluate(control, subscribe); - if (match.ImmediateResult == false) + if (!match.IsMatch) + { + return hitCombinator ? SelectorMatch.NeverThisInstance : match; + } + else if (selector.InTemplate && control.TemplatedParent == null) + { + return SelectorMatch.NeverThisInstance; + } + else if (match.Result == SelectorMatchResult.AlwaysThisInstance) { - return match; + alwaysThisType = false; } - else if (match.ObservableResult != null) + else if (match.Result == SelectorMatchResult.Sometimes) { - inputs.Add(match.ObservableResult); + inputs.Add(match.Activator); } selector = selector.MovePrevious(); @@ -63,7 +78,9 @@ namespace Avalonia.Styling } else { - return SelectorMatch.True; + return alwaysThisType && !hitCombinator ? + SelectorMatch.AlwaysThisType : + SelectorMatch.AlwaysThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/SelectorMatch.cs b/src/Avalonia.Styling/Styling/SelectorMatch.cs index f325578389..ca73c1f727 100644 --- a/src/Avalonia.Styling/Styling/SelectorMatch.cs +++ b/src/Avalonia.Styling/Styling/SelectorMatch.cs @@ -5,51 +5,95 @@ using System; namespace Avalonia.Styling { + /// + /// Describes how a matches a control and its type. + /// + public enum SelectorMatchResult + { + /// + /// The selector never matches this type. + /// + NeverThisType, + + /// + /// The selector never matches this instance, but can match this type. + /// + NeverThisInstance, + + /// + /// The selector always matches this type. + /// + AlwaysThisType, + + /// + /// The selector always matches this instance, but doesn't always match this type. + /// + AlwaysThisInstance, + + /// + /// The selector matches this instance based on the . + /// + Sometimes, + } + /// /// Holds the result of a match. /// /// - /// There are two types of selectors - ones whose match can never change for a particular - /// control (such as ) and ones whose result can - /// change over time (such as . For the first - /// category of selectors, the value of will be set but for the - /// second, will be null and will - /// hold an observable which tracks the match. + /// A selector match describes whether and how a matches a control, and + /// in addition whether the selector can ever match a control of the same type. /// public class SelectorMatch { - public static readonly SelectorMatch False = new SelectorMatch(false); + /// + /// A selector match with the result of / + /// + public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType); - public static readonly SelectorMatch True = new SelectorMatch(true); + /// + /// A selector match with the result of / + /// + public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance); /// - /// Initializes a new instance of the class. + /// A selector match with the result of / /// - /// The immediate match value. - public SelectorMatch(bool match) - { - ImmediateResult = match; - } + public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType); /// - /// Initializes a new instance of the class. + /// Gets a selector match with the result of / /// - /// The observable match value. + public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance); + + /// + /// Initializes a new instance of the class with a + /// result. + /// + /// The match activator. public SelectorMatch(IObservable match) { - ObservableResult = match; + Contract.Requires(match != null); + + Result = SelectorMatchResult.Sometimes; + Activator = match; } + private SelectorMatch(SelectorMatchResult result) => Result = result; + /// - /// Gets the immediate result of the selector match, in the case of selectors that cannot - /// change over time. + /// Gets a value indicating whether the match was positive. + /// + public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType; + + /// + /// Gets the result of the match. /// - public bool? ImmediateResult { get; } + public SelectorMatchResult Result { get; } /// /// Gets an observable which tracks the selector match, in the case of selectors that can /// change over time. /// - public IObservable ObservableResult { get; } + public IObservable Activator { get; } } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 23c318a809..27fad58346 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -106,20 +106,14 @@ namespace Avalonia.Styling /// bool IResourceProvider.HasResources => _resources?.Count > 0; - /// - /// Attaches the style to a control if the style's selector matches. - /// - /// The control to attach to. - /// - /// The control that contains this style. May be null. - /// - public void Attach(IStyleable control, IStyleHost container) + /// + public bool Attach(IStyleable control, IStyleHost container) { if (Selector != null) { var match = Selector.Match(control); - if (match.ImmediateResult != false) + if (match.IsMatch) { var controlSubscriptions = GetSubscriptions(control); @@ -129,9 +123,10 @@ namespace Avalonia.Styling { foreach (var animation in Animations) { - IObservable obsMatch = match.ObservableResult; + var obsMatch = match.Activator; - if (match.ImmediateResult == true) + if (match.Result == SelectorMatchResult.AlwaysThisType || + match.Result == SelectorMatchResult.AlwaysThisInstance) { obsMatch = Observable.Return(true); } @@ -143,13 +138,15 @@ namespace Avalonia.Styling foreach (var setter in Setters) { - var sub = setter.Apply(this, control, match.ObservableResult); + var sub = setter.Apply(this, control, match.Activator); subs.Add(sub); } controlSubscriptions.Add(subs); Subscriptions.Add(subs); } + + return match.Result != SelectorMatchResult.NeverThisType; } else if (control == container) { @@ -165,7 +162,10 @@ namespace Avalonia.Styling controlSubscriptions.Add(subs); Subscriptions.Add(subs); + return true; } + + return false; } /// diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 22ed8bb241..51499b737a 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.Linq; using Avalonia.Collections; using Avalonia.Controls; @@ -15,6 +16,7 @@ namespace Avalonia.Styling { private IResourceNode _parent; private IResourceDictionary _resources; + private Dictionary> _cache; public Styles() { @@ -34,6 +36,7 @@ namespace Avalonia.Styling } x.ResourcesChanged += SubResourceChanged; + _cache = null; }, x => { @@ -49,6 +52,7 @@ namespace Avalonia.Styling } x.ResourcesChanged -= SubResourceChanged; + _cache = null; }, () => { }); } @@ -97,11 +101,46 @@ namespace Avalonia.Styling /// /// The control that contains this style. May be null. /// - public void Attach(IStyleable control, IStyleHost container) + public bool Attach(IStyleable control, IStyleHost container) { - foreach (IStyle style in this) + if (_cache == null) + { + _cache = new Dictionary>(); + } + + if (_cache.TryGetValue(control.StyleKey, out var cached)) + { + if (cached != null) + { + foreach (var style in cached) + { + style.Attach(control, container); + } + + return true; + } + + return false; + } + else { - style.Attach(control, container); + List result = null; + + foreach (var style in this) + { + if (style.Attach(control, container)) + { + if (result == null) + { + result = new List(); + } + + result.Add(style); + } + } + + _cache.Add(control.StyleKey, result); + return result != null; } } diff --git a/src/Avalonia.Styling/Styling/TemplateSelector.cs b/src/Avalonia.Styling/Styling/TemplateSelector.cs index 6919b4ad27..7530339883 100644 --- a/src/Avalonia.Styling/Styling/TemplateSelector.cs +++ b/src/Avalonia.Styling/Styling/TemplateSelector.cs @@ -23,6 +23,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => true; + /// + public override bool IsCombinator => true; + /// public override Type TargetType => null; @@ -46,7 +49,7 @@ namespace Avalonia.Styling "Cannot call Template selector on control with null TemplatedParent."); } - return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True; + return _parent.Match(templatedParent, subscribe); } protected override Selector MovePrevious() => null; diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs index 94c0b75c6e..cd019e6535 100644 --- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs @@ -68,6 +68,9 @@ namespace Avalonia.Styling /// public override Type TargetType => _targetType ?? _previous?.TargetType; + /// + public override bool IsCombinator => false; + /// /// Whether the selector matches the concrete or any object which /// implements . @@ -101,21 +104,23 @@ namespace Avalonia.Styling { if (controlType != TargetType) { - return SelectorMatch.False; + return SelectorMatch.NeverThisType; } } else { if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo())) { - return SelectorMatch.False; + return SelectorMatch.NeverThisType; } } } - if (Name != null && control.Name != Name) + if (Name != null) { - return SelectorMatch.False; + return control.Name == Name ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance; } if (_classes.IsValueCreated && _classes.Value.Count > 0) @@ -127,12 +132,14 @@ namespace Avalonia.Styling } else { - return new SelectorMatch(Matches(control.Classes)); + return Matches(control.Classes) ? + SelectorMatch.AlwaysThisInstance : + SelectorMatch.NeverThisInstance; } } else { - return SelectorMatch.True; + return SelectorMatch.AlwaysThisType; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index fc2656f236..9273011cb9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling IResourceNode IResourceNode.ResourceParent => _parent; /// - public void Attach(IStyleable control, IStyleHost container) + public bool Attach(IStyleable control, IStyleHost container) { if (Source != null) { - Loaded.Attach(control, container); + return Loaded.Attach(control, container); } + + return false; } public void Detach() diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs index d837f2cb2f..0561837ad1 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs @@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -42,7 +42,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(child).Result); } [Fact] @@ -54,7 +54,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Child().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; var result = new List(); Assert.False(await activator.Take(1)); @@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests var control = new TestLogical3(); var selector = default(Selector).OfType().Child().OfType(); - Assert.False(selector.Match(control).ImmediateResult); + Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result); } [Fact] diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs index 75599925b7..496998ecd9 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs @@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.True(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.True(await match.Activator.Take(1)); } [Fact] @@ -54,9 +55,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.False(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.False(await match.Activator.Take(1)); } [Fact] @@ -69,9 +71,10 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var match = target.Match(control); - Assert.True(await activator.Take(1)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + Assert.True(await match.Activator.Take(1)); } [Fact] @@ -80,7 +83,7 @@ namespace Avalonia.Styling.UnitTests var control = new Control1(); var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.False(await activator.Take(1)); control.Classes.Add("foo"); @@ -96,7 +99,7 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.True(await activator.Take(1)); control.Classes.Remove("foo"); @@ -108,7 +111,7 @@ namespace Avalonia.Styling.UnitTests { var control = new Control1(); var target = default(Selector).Class("foo").Class("bar"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; Assert.False(await activator.Take(1)); control.Classes.Add("foo"); @@ -129,7 +132,7 @@ namespace Avalonia.Styling.UnitTests }; var target = default(Selector).Class("foo"); - var activator = target.Match(control).ObservableResult; + var activator = target.Match(control).Activator; var result = new List(); using (activator.Subscribe(x => result.Add(x))) diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs index b75b59c212..56dad13186 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs @@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests var selector = default(Selector).OfType().Descendant().OfType(); - Assert.True(selector.Match(child).ImmediateResult); + Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result); } [Fact] @@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.True(await activator.Take(1)); } @@ -74,7 +74,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); } @@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests child.LogicalParent = parent; var selector = default(Selector).OfType().Class("foo").Descendant().OfType(); - var activator = selector.Match(child).ObservableResult; + var activator = selector.Match(child).Activator; Assert.False(await activator.Take(1)); parent.Classes.Add("foo"); diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs index 067b08b296..04b29376b0 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests public class SelectorTests_Multiple { [Fact] - public void Template_Child_Of_Control_With_Two_Classes() + public void Named_Template_Child_Of_Control_With_Two_Classes() { var template = new FuncControlTemplate(parent => { @@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests var border = (Border)((IVisual)control).VisualChildren.Single(); var values = new List(); - var activator = selector.Match(border).ObservableResult; + var match = selector.Match(border); - activator.Subscribe(x => values.Add(x)); + Assert.Equal(SelectorMatchResult.Sometimes, match.Result); + match.Activator.Subscribe(x => values.Add(x)); Assert.Equal(new[] { false }, values); control.Classes.AddRange(new[] { "foo", "bar" }); @@ -51,6 +52,39 @@ namespace Avalonia.Styling.UnitTests Assert.Equal(new[] { false, true, false }, values); } + [Fact] + public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type() + { + var template = new FuncControlTemplate(parent => + { + return new Border + { + Name = "border", + }; + }); + + var control = new Button + { + Template = template, + }; + + control.ApplyTemplate(); + + var selector = default(Selector) + .OfType public static FontFamily Default => new FontFamily(String.Empty); + /// + /// Represents all font families in the system + /// + public static IEnumerable SystemFontFamilies => + AvaloniaLocator.Current.GetService().InstalledFontNames.Select(name => new FontFamily(name)); + /// /// Gets the primary family name of the font family. /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index aacdef0538..3a1f79e32a 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -13,6 +13,11 @@ namespace Avalonia.Platform /// public interface IPlatformRenderInterface { + /// + /// Get all installed fonts in the system + /// + IEnumerable InstalledFontNames { get; } + /// /// Creates a formatted text implementation. /// diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 9b10d74c64..8080c27831 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -19,6 +19,8 @@ namespace Avalonia.Skia { private GRContext GrContext { get; } + public IEnumerable InstalledFontNames => SKFontManager.Default.FontFamilies; + public PlatformRenderInterface() { var gl = AvaloniaLocator.Current.GetService(); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 3ec18dac5e..8412a65e23 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -41,6 +41,19 @@ namespace Avalonia.Direct2D1 public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; } + public IEnumerable InstalledFontNames + { + get + { + var cache = Direct2D1FontCollectionCache.s_installedFontCollection; + var length = cache.FontFamilyCount; + for (int i = 0; i < length; i++) + { + var names = cache.GetFontFamily(i).FamilyNames; + yield return names.GetString(0); + } + } + } private static readonly object s_initLock = new object(); private static bool s_initialized = false; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index d60aa15a5e..d93a59d384 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -7,7 +7,7 @@ namespace Avalonia.Direct2D1.Media internal static class Direct2D1FontCollectionCache { private static readonly ConcurrentDictionary s_cachedCollections; - private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; + internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; static Direct2D1FontCollectionCache() { diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index f8ad7e63f2..b65c40cedd 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -9,6 +9,8 @@ namespace Avalonia.UnitTests { public class MockPlatformRenderInterface : IPlatformRenderInterface { + public IEnumerable InstalledFontNames => Mock.Of>(); + public IFormattedTextImpl CreateFormattedText( string text, Typeface typeface, From dad512ba80fe8b01730f97fbe9cc54374d6e8cec Mon Sep 17 00:00:00 2001 From: Jeffrey Ye Date: Tue, 8 Jan 2019 11:03:14 -0800 Subject: [PATCH 27/86] Font Dropdown in ControlCatalog --- samples/ControlCatalog/Pages/DropDownPage.xaml | 9 +++++++++ samples/ControlCatalog/Pages/DropDownPage.xaml.cs | 3 +++ 2 files changed, 12 insertions(+) diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml b/samples/ControlCatalog/Pages/DropDownPage.xaml index 864d2be49c..7673294e46 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml @@ -27,6 +27,15 @@ + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml.cs b/samples/ControlCatalog/Pages/DropDownPage.xaml.cs index edab5f1ceb..397f9f21e1 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml.cs @@ -13,6 +13,9 @@ namespace ControlCatalog.Pages private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + var fontDropDown = this.Find("fontDropDown"); + fontDropDown.Items = Avalonia.Media.FontFamily.SystemFontFamilies; + fontDropDown.SelectedIndex = 0; } } } From 4a84014263fabc1a017e27e825ca9a01aa477552 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Jan 2019 15:58:29 +0100 Subject: [PATCH 28/86] Fix typos. --- src/Avalonia.Styling/Styling/SelectorMatch.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Styling/Styling/SelectorMatch.cs b/src/Avalonia.Styling/Styling/SelectorMatch.cs index ca73c1f727..63b89e9e97 100644 --- a/src/Avalonia.Styling/Styling/SelectorMatch.cs +++ b/src/Avalonia.Styling/Styling/SelectorMatch.cs @@ -46,22 +46,22 @@ namespace Avalonia.Styling public class SelectorMatch { /// - /// A selector match with the result of / + /// A selector match with the result of . /// public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType); /// - /// A selector match with the result of / + /// A selector match with the result of . /// public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance); /// - /// A selector match with the result of / + /// A selector match with the result of . /// public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType); /// - /// Gets a selector match with the result of / + /// Gets a selector match with the result of . /// public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance); From 27edc74bf88fcb1ae63b616775bd340b1d1cccb9 Mon Sep 17 00:00:00 2001 From: Jeffrey Ye Date: Wed, 9 Jan 2019 08:12:19 -0800 Subject: [PATCH 29/86] more comments, and fix compilation error --- src/Avalonia.Visuals/Media/FontFamily.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index 60d40e449e..5c152cd8a0 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -53,7 +53,7 @@ namespace Avalonia.Media public static FontFamily Default => new FontFamily(String.Empty); /// - /// Represents all font families in the system + /// Represents all font families in the system. This can be an expensive call depending on platform implementation. /// public static IEnumerable SystemFontFamilies => AvaloniaLocator.Current.GetService().InstalledFontNames.Select(name => new FontFamily(name)); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 92c12c6b9e..bd46e68fd8 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -8,6 +8,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree { class MockRenderInterface : IPlatformRenderInterface { + public IEnumerable InstalledFontNames => throw new NotImplementedException(); + public IFormattedTextImpl CreateFormattedText( string text, Typeface typeface, From 88c05b1a19991bf3b5219b540b287d3f7e136495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 21 Nov 2018 01:26:31 +0000 Subject: [PATCH 30/86] Fixed field declarations which should be readonly. --- src/Avalonia.Controls/Calendar/CalendarItem.cs | 2 +- src/Avalonia.Controls/Primitives/AdornerLayer.cs | 2 +- src/Avalonia.Controls/Viewbox.cs | 2 +- src/Avalonia.Input/Cursors.cs | 2 +- src/Avalonia.Input/DataFormats.cs | 6 +++--- src/Avalonia.Input/DragDrop.cs | 10 +++++----- src/Avalonia.Visuals/Media/GradientStop.cs | 4 ++-- src/Avalonia.Visuals/Media/Typeface.cs | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 577555333f..fb6dacaf81 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -52,7 +52,7 @@ namespace Avalonia.Controls.Primitives internal Calendar Owner { get; set; } internal CalendarDayButton CurrentButton { get; set; } - public static StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); + public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); public IBrush HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 5308a062ec..ed590679cd 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives // TODO: Need to track position of adorned elements and move the adorner if they move. public class AdornerLayer : Panel, ICustomSimpleHitTest { - public static AttachedProperty AdornedElementProperty = + public static readonly AttachedProperty AdornedElementProperty = AvaloniaProperty.RegisterAttached("AdornedElement"); private static readonly AttachedProperty s_adornedElementInfoProperty = diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index db753f4ab4..3a070d07b2 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// The stretch property /// - public static AvaloniaProperty StretchProperty = + public static readonly AvaloniaProperty StretchProperty = AvaloniaProperty.RegisterDirect(nameof(Stretch), v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursors.cs index 6f67f3b08c..d3618f30f3 100644 --- a/src/Avalonia.Input/Cursors.cs +++ b/src/Avalonia.Input/Cursors.cs @@ -47,7 +47,7 @@ namespace Avalonia.Input public class Cursor { - public static Cursor Default = new Cursor(StandardCursorType.Arrow); + public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow); internal Cursor(IPlatformHandle platformCursor) { diff --git a/src/Avalonia.Input/DataFormats.cs b/src/Avalonia.Input/DataFormats.cs index 559d2cb643..cf5a6592e1 100644 --- a/src/Avalonia.Input/DataFormats.cs +++ b/src/Avalonia.Input/DataFormats.cs @@ -5,11 +5,11 @@ /// /// Dataformat for plaintext /// - public static string Text = nameof(Text); + public static readonly string Text = nameof(Text); /// /// Dataformat for one or more filenames /// - public static string FileNames = nameof(FileNames); + public static readonly string FileNames = nameof(FileNames); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/DragDrop.cs b/src/Avalonia.Input/DragDrop.cs index b5aba55f6c..c58b764b1d 100644 --- a/src/Avalonia.Input/DragDrop.cs +++ b/src/Avalonia.Input/DragDrop.cs @@ -9,21 +9,21 @@ namespace Avalonia.Input /// /// Event which is raised, when a drag-and-drop operation enters the element. /// - public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation leaves the element. /// - public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation is updated while over the element. /// - public static RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation should complete over the element. /// - public static RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); - public static AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); + public static readonly AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); /// /// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation. diff --git a/src/Avalonia.Visuals/Media/GradientStop.cs b/src/Avalonia.Visuals/Media/GradientStop.cs index 00d96a0b3c..25a6eb10dc 100644 --- a/src/Avalonia.Visuals/Media/GradientStop.cs +++ b/src/Avalonia.Visuals/Media/GradientStop.cs @@ -11,13 +11,13 @@ namespace Avalonia.Media /// /// Describes the property. /// - public static StyledProperty OffsetProperty = + public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register(nameof(Offset)); /// /// Describes the property. /// - public static StyledProperty ColorProperty = + public static readonly StyledProperty ColorProperty = AvaloniaProperty.Register(nameof(Color)); /// diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 6dde2bb591..37ac0953bf 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// public class Typeface { - public static Typeface Default = new Typeface(FontFamily.Default); + public static readonly Typeface Default = new Typeface(FontFamily.Default); /// /// Initializes a new instance of the class. From 2f07f66740c2979ba0192b61bcf596ac54a082d6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Jan 2019 22:25:09 +0100 Subject: [PATCH 31/86] Avalonia.sln updated by VS. Every time VS opens the solution it makes this change. --- Avalonia.sln | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Avalonia.sln b/Avalonia.sln index 3ed931933a..cf440972b2 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 @@ -130,6 +131,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 ProjectSection(SolutionItems) = preProject build\Base.props = build\Base.props build\Binding.props = build\Binding.props + build\BuildTargets.targets = build\BuildTargets.targets build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props @@ -147,7 +149,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Splat.props = build\Splat.props build\System.Memory.props = build\System.Memory.props build\XUnit.props = build\XUnit.props - build\BuildTargets.targets = build\BuildTargets.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" @@ -189,11 +190,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Avalonia.Desktop\Avalonia.Desktop.csproj", "{3C471044-3640-45E3-B1B2-16D2FF8399EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -220,10 +221,6 @@ Global Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU @@ -1722,6 +1719,30 @@ Global {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU From 08b50a6830219677ecdb415080352ae0ecc3f237 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 00:59:29 +0300 Subject: [PATCH 32/86] [X11] Get screen info (including scale factor) via RANDR 1.5 --- samples/ControlCatalog.NetCore/Program.cs | 2 +- src/Avalonia.X11/Avalonia.X11.csproj | 2 +- src/Avalonia.X11/Stubs.cs | 8 - src/Avalonia.X11/X11Info.cs | 23 ++ src/Avalonia.X11/X11Platform.cs | 1 + src/Avalonia.X11/X11Screens.cs | 254 ++++++++++++++++++++++ src/Avalonia.X11/X11Structs.cs | 52 ++++- src/Avalonia.X11/X11Window.cs | 4 +- src/Avalonia.X11/XLib.cs | 19 +- 9 files changed, 351 insertions(+), 14 deletions(-) create mode 100644 src/Avalonia.X11/X11Screens.cs diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 57c8b700df..2a7d0b30a9 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -43,7 +43,7 @@ namespace ControlCatalog.NetCore /// This method is needed for IDE previewer infrastructure /// public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure().UsePlatformDetect().UseSkia().UseReactiveUI(); + => AppBuilder.Configure().UsePlatformDetect().UseSkia().UseReactiveUI().UseX11(); static void ConsoleSilencer() { diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index 681eb0a2be..3e75180468 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs index f70c7fee25..91d788c576 100644 --- a/src/Avalonia.X11/Stubs.cs +++ b/src/Avalonia.X11/Stubs.cs @@ -63,12 +63,4 @@ namespace Avalonia.X11 return new FakeIcon(ms.ToArray()); } } - - class ScreenStub : IScreenImpl - { - public int ScreenCount { get; } = 1; - - public Screen[] AllScreens { get; } = - {new Screen(new Rect(0, 0, 1920, 1280), new Rect(0, 0, 1920, 1280), true)}; - } } diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index fece7c2ba2..e102283f87 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using JetBrains.Annotations; using static Avalonia.X11.XLib; // ReSharper disable UnusedAutoPropertyAccessor.Local namespace Avalonia.X11 @@ -17,6 +18,12 @@ namespace Avalonia.X11 public IntPtr DefaultCursor { get; } public X11Atoms Atoms { get; } public IntPtr Xim { get; } + + public int RandrEventBase { get; } + public int RandrErrorBase { get; } + + public Version RandrVersion { get; } + public IntPtr LastActivityTimestamp { get; set; } @@ -33,6 +40,22 @@ namespace Avalonia.X11 //TODO: Open an actual XIM once we get support for preedit in our textbox XSetLocaleModifiers("@im=none"); Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + try + { + if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0) + { + RandrEventBase = randrEventBase; + RandrErrorBase = randrErrorBase; + if (XRRQueryVersion(display, out var major, out var minor) != 0) + RandrVersion = new Version(major, minor); + } + } + catch + { + //Ignore, randr is not supported + } + } } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index cce5299843..d315ba5ffb 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -41,6 +41,7 @@ namespace Avalonia.X11 .Bind().ToConstant(new PlatformSettingsStub()) .Bind().ToConstant(new SystemDialogsStub()) .Bind().ToConstant(new IconLoaderStub()); + X11Screens.Init(this); EglGlPlatformFeature.TryInitialize(); } diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs new file mode 100644 index 0000000000..4a9cbe98a4 --- /dev/null +++ b/src/Avalonia.X11/X11Screens.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Platform; +using static Avalonia.X11.XLib; +using JetBrains.Annotations; + +namespace Avalonia.X11 +{ + class X11Screens : IScreenImpl + { + private IX11Screens _impl; + + private X11Screens(IX11Screens impl) + { + _impl = impl; + } + + static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens) + { + var rect = default(Rect); + foreach (var s in screens) + { + rect = rect.Union(s.Bounds); + //Fallback value + s.WorkingArea = s.Bounds; + } + + var res = XGetWindowProperty(info.Display, + info.RootWindow, + info.Atoms._NET_WORKAREA, + IntPtr.Zero, + new IntPtr(128), + false, + info.Atoms.AnyPropertyType, + out var type, + out var format, + out var count, + out var bytesAfter, + out var prop); + + if (res != (int)Status.Success || type == IntPtr.Zero || + format == 0 || bytesAfter.ToInt64() != 0 || count.ToInt64() % 4 != 0) + return screens; + + var pwa = (IntPtr*)prop; + var wa = new Rect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32()); + + + foreach (var s in screens) + s.WorkingArea = s.Bounds.Intersect(wa); + + XFree(prop); + return screens; + } + + class Randr15ScreensImpl : IX11Screens + { + private readonly X11ScreensUserSettings _settings; + private X11Screen[] _cache; + private X11Info _x11; + private IntPtr _window; + + public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings) + { + _settings = settings; + _x11 = platform.Info; + _window = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero); + platform.Windows[_window] = OnEvent; + XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify); + } + + private void OnEvent(XEvent ev) + { + // Invalidate cache on RRScreenChangeNotify + if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify) + _cache = null; + } + + public unsafe X11Screen[] Screens + { + get + { + if (_cache != null) + return _cache; + var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count); + + var screens = new X11Screen[count]; + for (var c = 0; c < count; c++) + { + var mon = monitors[c]; + var namePtr = XGetAtomName(_x11.Display, mon.Name); + var name = Marshal.PtrToStringAnsi(namePtr); + XFree(namePtr); + + var density = 1d; + if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true) + { + if (mon.MWidth == 0) + density = 1; + else + density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth); + } + + density *= _settings.GlobalScaleFactor; + + var bounds = new Rect(mon.X, mon.Y, mon.Width, mon.Height); + screens[0] = new X11Screen(bounds, + mon.Primary != 0, + name, + (mon.MWidth == 0 || mon.MHeight == 0) ? (Size?)null : new Size(mon.MWidth, mon.MHeight), + density); + } + + XFree(new IntPtr(monitors)); + _cache = UpdateWorkArea(_x11, screens); + return screens; + } + } + } + + class FallbackScreensImpl : IX11Screens + { + public FallbackScreensImpl(X11Info info, X11ScreensUserSettings settings) + { + if (XGetGeometry(info.Display, info.RootWindow, out var geo)) + { + + Screens = UpdateWorkArea(info, + new[] + { + new X11Screen(new Rect(0, 0, geo.width, geo.height), true, "Default", null, + settings.GlobalScaleFactor) + }); + } + + Screens = new[] {new X11Screen(new Rect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)}; + } + + public X11Screen[] Screens { get; } + } + + public static void Init(AvaloniaX11Platform platform) + { + var info = platform.Info; + var settings = X11ScreensUserSettings.Detect(); + var impl = (info.RandrVersion != null && info.RandrVersion >= new Version(1, 5)) + ? new Randr15ScreensImpl(platform, settings) + : (IX11Screens)new FallbackScreensImpl(info, settings); + + AvaloniaLocator.CurrentMutable.Bind().ToConstant(impl); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new X11Screens(impl)); + + } + + + public int ScreenCount => _impl.Screens.Length; + + public Screen[] AllScreens => + _impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray(); + } + + interface IX11Screens + { + X11Screen[] Screens { get; } + } + + class X11ScreensUserSettings + { + public double GlobalScaleFactor { get; set; } = 1; + public Dictionary NamedScaleFactors { get; set; } + + static double? TryParse(string s) + { + if (s == null) + return null; + if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var rv)) + return rv; + return null; + } + + + public static X11ScreensUserSettings DetectEnvironment() + { + var globalFactor = Environment.GetEnvironmentVariable("AVALONIA_GLOBAL_SCALE_FACTOR"); + var screenFactors = Environment.GetEnvironmentVariable("AVALONIA_SCREEN_SCALE_FACTORS"); + if (globalFactor == null && screenFactors == null) + return null; + + var rv = new X11ScreensUserSettings + { + GlobalScaleFactor = TryParse(globalFactor) ?? 1 + }; + + try + { + if (!string.IsNullOrWhiteSpace(screenFactors)) + { + rv.NamedScaleFactors = screenFactors.Split(';').Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Split('=')).ToDictionary(x => x[0], + x => double.Parse(x[1], CultureInfo.InvariantCulture)); + } + } + catch + { + //Ignore + } + + return null; + } + + + public static X11ScreensUserSettings Detect() + { + return DetectEnvironment() ?? new X11ScreensUserSettings(); + } + } + + class X11Screen + { + public bool Primary { get; } + public string Name { get; set; } + public Rect Bounds { get; set; } + public Size? PhysicalSize { get; set; } + public double PixelDensity { get; set; } + public Rect WorkingArea { get; set; } + + public X11Screen(Rect bounds, bool primary, + string name, Size? physicalSize, double? pixelDensity) + { + Primary = primary; + Name = name; + Bounds = bounds; + if (physicalSize == null && pixelDensity == null) + { + PixelDensity = 1; + } + else if (pixelDensity == null) + { + PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width); + } + else + { + PixelDensity = pixelDensity.Value; + PhysicalSize = physicalSize; + } + } + + public static double GuessPixelDensity(double pixelWidth, double mmWidth) + => Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96)); + } +} diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 6d73975b39..fc1bb01b6e 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -849,7 +849,43 @@ namespace Avalonia.X11 { OwnerGrabButtonMask = 1<<24 } - internal enum GrabMode { + [Flags] + internal enum RandrEventMask + { + RRScreenChangeNotify = 1 << 0, + +/* V1.2 additions */ + RRCrtcChangeNotifyMask = 1 << 1, + RROutputChangeNotifyMask = 1 << 2, + RROutputPropertyNotifyMask = 1 << 3, + +/* V1.4 additions */ + RRProviderChangeNotifyMask = 1 << 4, + RRProviderPropertyNotifyMask = 1 << 5, + RRResourceChangeNotifyMask = 1 << 6, + +/* V1.6 additions */ + RRLeaseNotifyMask = 1 << 7 + } + + internal enum RandrEvent + { + RRScreenChangeNotify = 0, + + /* V1.2 additions */ + RRNotify = 1 + } + + internal enum RandrRotate + { + /* used in the rotation field; rotation and reflection in 0.1 proto. */ + RR_Rotate_0 = 1, + RR_Rotate_90 = 2, + RR_Rotate_180 = 4, + RR_Rotate_270 = 8 + } + + internal enum GrabMode { GrabModeSync = 0, GrabModeAsync = 1 } @@ -1812,4 +1848,18 @@ namespace Avalonia.X11 { public const string XNSpotLocation = "spotLocation"; public const string XNFontSet = "fontSet"; } + + struct XRRMonitorInfo { + public IntPtr Name; + public int Primary; + public int Automatic; + public int NOutput; + public int X; + public int Y; + public int Width; + public int Height; + public int MWidth; + public int MHeight; + public IntPtr Outputs; + } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 7a05bc896d..3212123238 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -636,8 +636,8 @@ namespace Avalonia.X11 } } - - public IScreenImpl Screen { get; } = new ScreenStub(); + + public IScreenImpl Screen { get; } = AvaloniaLocator.CurrentMutable.GetService(); public Size MaxClientSize { get; } = new Size(1920, 1280); diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 89eab88966..1688d69f4f 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -14,7 +14,8 @@ namespace Avalonia.X11 { internal unsafe static class XLib { - const string libX11 = "X11"; + const string libX11 = "libX11.so.6"; + const string libX11Randr = "libXrandr.so.2"; [DllImport(libX11)] public static extern IntPtr XOpenDisplay(IntPtr display); @@ -451,6 +452,22 @@ namespace Avalonia.X11 [DllImport (libX11)] public static extern void XDestroyIC (IntPtr xic); + [DllImport(libX11Randr)] + public static extern int XRRQueryExtension (IntPtr dpy, + out int event_base_return, + out int error_base_return); + + [DllImport(libX11Randr)] + public static extern int XRRQueryVersion(IntPtr dpy, + out int major_version_return, + out int minor_version_return); + + [DllImport(libX11Randr)] + public static extern XRRMonitorInfo* + XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors); + [DllImport(libX11Randr)] + public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask); + public struct XGeometry { public IntPtr root; From effc7c09c658126ddaa5e253e22b097dd281be85 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 08:15:15 +0300 Subject: [PATCH 33/86] Fixed #2228 --- .../AvaloniaTypeConverters.cs | 6 +++++ .../AvaloniaTypeAttributeProvider.cs | 7 ++++-- .../Converters/ConverterTests.cs | 23 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index fa9d364fc0..ac4d60dde1 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -44,6 +44,12 @@ namespace Avalonia.Markup.Xaml { typeof(FontFamily), typeof(FontFamilyTypeConverter) } }; + internal static Type GetBuiltinTypeConverter(Type type) + { + _converters.TryGetValue(type, out var result); + return result; + } + /// /// Tries to lookup a for a type. /// diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs index 83f4919d4b..7558a5df0b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaTypeAttributeProvider.cs @@ -42,7 +42,10 @@ namespace Avalonia.Markup.Xaml.PortableXaml } else if (attributeType == typeof(TypeConverterAttribute)) { - result = ti.GetCustomAttribute(attributeType, inherit); + var builtin = AvaloniaTypeConverters.GetBuiltinTypeConverter(_type); + if (builtin != null) + result = new TypeConverterAttribute(builtin); + result = result ?? ti.GetCustomAttribute(attributeType, inherit); if (result == null) { @@ -111,4 +114,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml return null; } } -} \ No newline at end of file +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs new file mode 100644 index 0000000000..6ffaaaee5c --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/ConverterTests.cs @@ -0,0 +1,23 @@ +using System; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Converters +{ + public class ConverterTests + { + [Fact] + public void Bug_2228_Relative_Uris_Should_Be_Correctly_Parsed() + { + var testClass = typeof(TestClassWithUri); + var parsed = AvaloniaXamlLoader.Parse( + $"<{testClass.Name} xmlns='clr-namespace:{testClass.Namespace}' Uri='/test'/>", testClass.Assembly); + + Assert.False(parsed.Uri.IsAbsoluteUri); + } + } + + public class TestClassWithUri + { + public Uri Uri { get; set; } + } +} From b7e196b57e5335dd471463d8c0bd3fc548c0d8f1 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 10 Jan 2019 13:16:02 +0800 Subject: [PATCH 34/86] Add workaround for for failing CastXML on macOS by manually updating Hombrew. cc @jkoritzinsky --- azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8c5380e65e..bcd0082c6f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,7 +51,9 @@ jobs: - task: CmdLine@2 displayName: 'Install CastXML' inputs: - script: brew install castxml + script: | + brew update + brew install castxml - task: CmdLine@2 displayName: 'Install Nuke' From 4b234f73dfb8604d7f13f1b23d91dc3da150f74d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 08:34:58 +0300 Subject: [PATCH 35/86] Run Avalonia.Base.UnitTests on netfx/mono --- build/NetFX.props | 5 ++ build/UnitTests.NetFX.props | 8 ++++ build/xunit.runner.mono.json | 1 + nukebuild/Build.cs | 47 +++++++++++-------- .../Avalonia.Base.UnitTests.csproj | 4 +- .../DataAnnotationsValidationPluginTests.cs | 24 +++++----- 6 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 build/UnitTests.NetFX.props create mode 100644 build/xunit.runner.mono.json diff --git a/build/NetFX.props b/build/NetFX.props index d8b2daf13a..4d2841714b 100644 --- a/build/NetFX.props +++ b/build/NetFX.props @@ -3,4 +3,9 @@ /usr/lib/mono/4.6.1-api /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api + + /usr/lib/mono/4.7-api/ + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api + + diff --git a/build/UnitTests.NetFX.props b/build/UnitTests.NetFX.props new file mode 100644 index 0000000000..e9a29d80fe --- /dev/null +++ b/build/UnitTests.NetFX.props @@ -0,0 +1,8 @@ + + + + + PreserveNewest + + + diff --git a/build/xunit.runner.mono.json b/build/xunit.runner.mono.json new file mode 100644 index 0000000000..4fb88b54c6 --- /dev/null +++ b/build/xunit.runner.mono.json @@ -0,0 +1 @@ +{ "appDomain": "denied" } diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index a14842cfd3..c8d35cd877 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; +using System.Xml.Linq; using Nuke.Common; using Nuke.Common.Git; using Nuke.Common.ProjectModel; @@ -92,16 +93,24 @@ partial class Build : NukeBuild ); }); - void RunCoreTest(string project, bool coreOnly = false) + void RunCoreTest(string project) { if(!project.EndsWith(".csproj")) project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); Information("Running tests from " + project); - var frameworks = new List(){"netcoreapp2.0"}; + XDocument xdoc; + using (var s = File.OpenRead(project)) + xdoc = XDocument.Load(s); + + List frameworks = null; + var targets = xdoc.Root.Descendants("TargetFrameworks").FirstOrDefault(); + if (targets != null) + frameworks = targets.Value.Split(';').Where(f => !string.IsNullOrWhiteSpace(f)).ToList(); + else + frameworks = new List {xdoc.Root.Descendants("TargetFramework").First().Value}; + foreach(var fw in frameworks) { - if(!fw.StartsWith("netcoreapp") && coreOnly) - continue; Information("Running for " + fw); DotNetTest(c => { @@ -124,18 +133,18 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Animation.UnitTests", false); - RunCoreTest("./tests/Avalonia.Base.UnitTests", false); - RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); - RunCoreTest("./tests/Avalonia.Input.UnitTests", false); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", false); - RunCoreTest("./tests/Avalonia.Layout.UnitTests", false); - RunCoreTest("./tests/Avalonia.Markup.UnitTests", false); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", false); - RunCoreTest("./tests/Avalonia.Styling.UnitTests", false); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false); - RunCoreTest("./tests/Avalonia.Skia.UnitTests", false); - RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false); + RunCoreTest("./tests/Avalonia.Animation.UnitTests"); + RunCoreTest("./tests/Avalonia.Base.UnitTests"); + RunCoreTest("./tests/Avalonia.Controls.UnitTests"); + RunCoreTest("./tests/Avalonia.Input.UnitTests"); + RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); + RunCoreTest("./tests/Avalonia.Layout.UnitTests"); + RunCoreTest("./tests/Avalonia.Markup.UnitTests"); + RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests"); + RunCoreTest("./tests/Avalonia.Styling.UnitTests"); + RunCoreTest("./tests/Avalonia.Visuals.UnitTests"); + RunCoreTest("./tests/Avalonia.Skia.UnitTests"); + RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests"); }); Target RunRenderTests => _ => _ @@ -143,9 +152,9 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); + RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj"); }); Target RunDesignerTests => _ => _ @@ -153,7 +162,7 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", false); + RunCoreTest("./tests/Avalonia.DesignerSupport.Tests"); }); [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit; diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 373b3f7196..6e885f33be 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs index 2bffb7b84a..378c225e23 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using Avalonia.Data; using Avalonia.Data.Core.Plugins; using Avalonia.UnitTests; @@ -86,17 +87,18 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins validator.SetValue("123456", BindingPriority.LocalValue); validator.SetValue("abcdefghijklm", BindingPriority.LocalValue); - Assert.Equal(new[] - { - new BindingNotification(null), - new BindingNotification("123456"), - new BindingNotification( - new AggregateException( - new ValidationException("The PhoneNumber field is not a valid phone number."), - new ValidationException("The field PhoneNumber must be a string or array type with a maximum length of '10'.")), - BindingErrorType.DataValidationError, - "abcdefghijklm"), - }, result); + Assert.Equal(3, result.Count); + Assert.Equal(new BindingNotification(null), result[0]); + Assert.Equal(new BindingNotification("123456"), result[1]); + var errorResult = (BindingNotification)result[2]; + Assert.Equal(BindingErrorType.DataValidationError, errorResult.ErrorType); + Assert.Equal("abcdefghijklm", errorResult.Value); + var exceptions = ((AggregateException)(errorResult.Error)).InnerExceptions; + Assert.True(exceptions.Any(ex => + ex.Message.Contains("The PhoneNumber field is not a valid phone number."))); + Assert.True(exceptions.Any(ex => + ex.Message.Contains("The field PhoneNumber must be a string or array type with a maximum length of '10'."))); + } private class Data From feadefc1a18a517c94f8a673c74d77dd2ad44c23 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 08:51:27 +0300 Subject: [PATCH 36/86] Run Mono/FullNET tests for all "core" libs except Avalonia.Controls --- .../Avalonia.Animation.UnitTests.csproj | 6 ++++-- .../Data/Core/ExpressionObserverTests_Property.cs | 7 ++++++- .../Avalonia.Input.UnitTests.csproj | 6 ++++-- .../Avalonia.Interactivity.UnitTests.csproj | 5 ++++- .../Avalonia.Markup.UnitTests.csproj | 6 ++++-- .../Avalonia.Markup.Xaml.UnitTests.csproj | 6 ++++-- .../Avalonia.Styling.UnitTests.csproj | 6 ++++-- .../Avalonia.Visuals.UnitTests.csproj | 7 +++++-- 8 files changed, 35 insertions(+), 14 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj index 1b2ba3c7de..ef515ce155 100644 --- a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj +++ b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -22,4 +24,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index c90683959e..239c9d56aa 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -558,7 +558,12 @@ namespace Avalonia.Base.UnitTests.Data.Core var result = run(); result.Item1.Subscribe(x => { }); - GC.Collect(); + // Mono trickery + GC.Collect(2); + GC.WaitForPendingFinalizers(); + GC.WaitForPendingFinalizers(); + GC.Collect(2); + Assert.Null(result.Item2.Target); } diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index 1b2ba3c7de..ef515ce155 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -22,4 +24,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj index bc883d0251..6b19d81034 100644 --- a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj +++ b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj @@ -1,9 +1,12 @@  netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -20,4 +23,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj index 1680ecf798..aba5f2d469 100644 --- a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj +++ b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -23,4 +25,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 735d5c421a..8b47bcec30 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -1,9 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library + true + @@ -36,4 +38,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj index e65dd848dd..3fd280f7ab 100644 --- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj +++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj @@ -1,10 +1,12 @@  - netcoreapp2.0 + netcoreapp2.0;net47 Library CS0067 + true + @@ -23,4 +25,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj index 2b559c9c56..50c2e580b0 100644 --- a/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj +++ b/tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj @@ -1,8 +1,11 @@  - netcoreapp2.0 + netcoreapp2.0;net47 + Library + true + @@ -21,4 +24,4 @@ - \ No newline at end of file + From a75c8b985995fc1a7bc139b9f19ce4b444538811 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 09:22:03 +0300 Subject: [PATCH 37/86] Adapted GridLayoutTests to run on Mono --- .../Avalonia.Controls.UnitTests.csproj | 6 ++- .../GridLayoutTests.cs | 53 +++++++++++-------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index b418d56fcd..aa470bce9d 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -1,10 +1,12 @@  - netcoreapp2.0 + netcoreapp2.0;net47 latest Library + true + @@ -26,4 +28,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs index fbb90de505..93163f4a92 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Avalonia.Controls.Utils; @@ -16,7 +17,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("100, 200, 300", 600d, 600d, new[] { 100d, 200d, 300d })] [InlineData("100, 200, 300", 400d, 400d, new[] { 100d, 200d, 100d })] public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -25,7 +26,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })] [InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })] public void MeasureArrange_AllStarLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -36,7 +37,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("100,2*,3*", 100d, 100d, new[] { 100d, 0d, 0d })] [InlineData("100,2*,3*", 50d, 50d, new[] { 50d, 0d, 0d })] public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -49,7 +50,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("100,200,Auto", 100d, 100d, new[] { 100d, 0d, 0d })] [InlineData("100,200,Auto", 50d, 50d, new[] { 50d, 0d, 0d })] public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -58,7 +59,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,2*,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] [InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })] public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } @@ -69,14 +70,24 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,200,Auto", 200d, 200d, new[] { 0d, 200d, 0d })] [InlineData("*,200,Auto", 100d, 100d, new[] { 0d, 100d, 0d })] public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); } + + /// + /// This is needed because Mono somehow converts double array to object array in attribute metadata + /// + static void AssertEqual(IList expected, IReadOnlyList actual) + { + var conv = expected.Cast().ToArray(); + Assert.Equal(conv, actual); + } + [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] private static void TestRowDefinitionsOnly(string length, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + double expectedDesiredLength, IList expectedLengthList) { // Arrange var layout = new GridLayout(new RowDefinitions(length)); @@ -84,11 +95,11 @@ namespace Avalonia.Controls.UnitTests // Measure - Action & Assert var measure = layout.Measure(containerLength); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedLengthList, measure.LengthList); + AssertEqual(expectedLengthList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange(containerLength, measure); - Assert.Equal(expectedLengthList, arrange.LengthList); + AssertEqual(expectedLengthList, arrange.LengthList); } [Theory] @@ -99,7 +110,7 @@ namespace Avalonia.Controls.UnitTests [InlineData("*,2*,Auto", 0d, new[] { Inf, Inf, 0d }, new[] { 0d, 0d, 0d })] [InlineData("*,200,Auto", 200d, new[] { Inf, 200d, 0d }, new[] { 0d, 200d, 0d })] public void MeasureArrange_InfiniteMeasure_Correct(string length, double expectedDesiredLength, - IList expectedMeasureList, IList expectedArrangeList) + IList expectedMeasureList, IList expectedArrangeList) { // Arrange var layout = new GridLayout(new RowDefinitions(length)); @@ -107,34 +118,34 @@ namespace Avalonia.Controls.UnitTests // Measure - Action & Assert var measure = layout.Measure(Inf); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedMeasureList, measure.LengthList); + AssertEqual(expectedMeasureList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange(measure.DesiredLength, measure); - Assert.Equal(expectedArrangeList, arrange.LengthList); + AssertEqual(expectedArrangeList, arrange.LengthList); } [Theory] [InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 300d, new[] { 100d, 250d, 250d })] public void MeasureArrange_ChildHasSize_Correct(string length, - IList childLengthList, double containerLength, - double expectedDesiredLength, IList expectedLengthList) + IList childLengthList, double containerLength, + double expectedDesiredLength, IList expectedLengthList) { // Arrange var lengthList = new ColumnDefinitions(length); var layout = new GridLayout(lengthList); layout.AppendMeasureConventions( Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, 1)), - x => childLengthList[x]); + x => (double)childLengthList[x]); // Measure - Action & Assert var measure = layout.Measure(containerLength); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedLengthList, measure.LengthList); + AssertEqual(expectedLengthList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange(containerLength, measure); - Assert.Equal(expectedLengthList, arrange.LengthList); + AssertEqual(expectedLengthList, arrange.LengthList); } [Theory] @@ -145,7 +156,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(160d, 160d, new[] { 100d, 20d, 40d }, new[] { 100d, 20d, 40d })] public void MeasureArrange_ChildHasSizeAndHasMultiSpan_Correct( double containerLength, double expectedDesiredLength, - IList expectedMeasureLengthList, IList expectedArrangeLengthList) + IList expectedMeasureLengthList, IList expectedArrangeLengthList) { var length = "100,*,2*"; var childLengthList = new[] { 150d, 150d, 150d }; @@ -161,13 +172,13 @@ namespace Avalonia.Controls.UnitTests // Measure - Action & Assert var measure = layout.Measure(containerLength); Assert.Equal(expectedDesiredLength, measure.DesiredLength); - Assert.Equal(expectedMeasureLengthList, measure.LengthList); + AssertEqual(expectedMeasureLengthList, measure.LengthList); // Arrange - Action & Assert var arrange = layout.Arrange( double.IsInfinity(containerLength) ? measure.DesiredLength : containerLength, measure); - Assert.Equal(expectedArrangeLengthList, arrange.LengthList); + AssertEqual(expectedArrangeLengthList, arrange.LengthList); } } } From 0c47fc299acfe7f1d4bb9160c513cbe980744edf Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 09:32:38 +0300 Subject: [PATCH 38/86] Print dotnet and mono versions --- nukebuild/Build.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index c8d35cd877..bb31034299 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -57,6 +57,17 @@ partial class Build : NukeBuild Information("IsReleasable: " + Parameters.IsReleasable); Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); + + void ExecWait(string preamble, string command, string args) + { + Console.WriteLine(preamble); + Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit(); + } + ExecWait("dotnet version:", "dotnet", "--version"); + if (Parameters.IsRunningOnUnix) + ExecWait("Mono version:", "mono", "--version"); + + } Target Clean => _ => _.Executes(() => From 3a28179704ffe4f164c472a805f4d1accb42878b Mon Sep 17 00:00:00 2001 From: Jeffrey Ye Date: Wed, 9 Jan 2019 23:19:45 -0800 Subject: [PATCH 39/86] return new string[0] from mock interfaces --- tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index b65c40cedd..0e2abb314d 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -9,7 +9,7 @@ namespace Avalonia.UnitTests { public class MockPlatformRenderInterface : IPlatformRenderInterface { - public IEnumerable InstalledFontNames => Mock.Of>(); + public IEnumerable InstalledFontNames => new string[0]; public IFormattedTextImpl CreateFormattedText( string text, diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index bd46e68fd8..fec0f0831a 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree { class MockRenderInterface : IPlatformRenderInterface { - public IEnumerable InstalledFontNames => throw new NotImplementedException(); + public IEnumerable InstalledFontNames => new string[0]; public IFormattedTextImpl CreateFormattedText( string text, From 8aea57118a02c40dedaf76a041583b8928f521d4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 10 Jan 2019 13:58:12 +0300 Subject: [PATCH 40/86] Install Mono 5.18 for OSX Azure build --- azure-pipelines.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bcd0082c6f..fd40118888 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,6 +38,13 @@ jobs: inputs: version: '2.1.403' + - task: CmdLine@2 + displayName: 'Install Mono 5.18' + inputs: + script: | + curl -o ./mono.pkg https://download.mono-project.com/archive/5.18.0/macos-10-universal/MonoFramework-MDK-5.18.0.225.macos10.xamarin.universal.pkg + sudo installer -verbose -pkg ./mono.pkg -target / + - task: Xcode@5 inputs: actions: 'build' From 440985a2b3f4b0bcdf7dde10a461d65ae982c631 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 10 Jan 2019 19:35:21 +0800 Subject: [PATCH 41/86] Add unit test for FillMode --- .../AnimationIterationTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs index d89b8469df..f7a8774689 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -8,6 +8,7 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.Data; using Xunit; +using Avalonia.Animation.Easings; namespace Avalonia.Animation.UnitTests { @@ -73,5 +74,58 @@ namespace Avalonia.Animation.UnitTests Assert.True(animationRun.Status == TaskStatus.RanToCompletion); Assert.Equal(border.Width, 100d); } + + [Fact] + public void Check_FillModes_Start_and_End_Values_if_Retained() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 0d), + }, + Cue = new Cue(0.0d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 300d), + }, + Cue = new Cue(1.0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(0.05d), + Delay = TimeSpan.FromSeconds(0.05d), + Easing = new SineEaseInOut(), + FillMode = FillMode.Both, + Children = + { + keyframe1, + keyframe2 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 100d, + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(border, clock); + + clock.Step(TimeSpan.FromSeconds(0d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.050d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.100d)); + Assert.Equal(border.Width, 300d); + } } } From 97d624a9ae5cdac6ffe2cce42a485f2ea4a2cad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Wed, 9 Jan 2019 22:39:00 +0000 Subject: [PATCH 42/86] Use nameof where possible. --- src/Avalonia.Animation/IterationCount.cs | 2 +- src/Avalonia.Base/Collections/AvaloniaDictionary.cs | 4 ++-- src/Avalonia.Base/Collections/AvaloniaList.cs | 2 +- src/Avalonia.Controls/Calendar/DatePicker.cs | 2 +- src/Avalonia.Controls/GridLength.cs | 4 ++-- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 2 +- src/Avalonia.Styling/Styling/Setter.cs | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Animation/IterationCount.cs b/src/Avalonia.Animation/IterationCount.cs index eafc483868..e9cd0686d8 100644 --- a/src/Avalonia.Animation/IterationCount.cs +++ b/src/Avalonia.Animation/IterationCount.cs @@ -44,7 +44,7 @@ namespace Avalonia.Animation { if (type > IterationType.Infinite) { - throw new ArgumentException("Invalid value", "type"); + throw new ArgumentException("Invalid value", nameof(type)); } _type = type; diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index e8dc2a5ed7..4de4c540a0 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -148,7 +148,7 @@ namespace Avalonia.Collections { if (_inner.TryGetValue(key, out TValue value)) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); if (CollectionChanged != null) @@ -209,7 +209,7 @@ namespace Avalonia.Collections private void NotifyAdd(TKey key, TValue value) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index d5a4e63003..4d4a561b08 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -511,7 +511,7 @@ namespace Avalonia.Collections /// private void NotifyCountChanged() { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); } /// diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs index 2270598623..70de8bec16 100644 --- a/src/Avalonia.Controls/Calendar/DatePicker.cs +++ b/src/Avalonia.Controls/Calendar/DatePicker.cs @@ -954,7 +954,7 @@ namespace Avalonia.Controls } else { - var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException("text", "SelectedDate value is not valid."), text); + var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text); OnDateValidationError(dateValidationError); if (dateValidationError.ThrowException) diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index f6a608cd71..02be95b647 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -56,12 +56,12 @@ namespace Avalonia.Controls { if (value < 0 || double.IsNaN(value) || double.IsInfinity(value)) { - throw new ArgumentException("Invalid value", "value"); + throw new ArgumentException("Invalid value", nameof(value)); } if (type < GridUnitType.Auto || type > GridUnitType.Star) { - throw new ArgumentException("Invalid value", "type"); + throw new ArgumentException("Invalid value", nameof(type)); } _type = type; diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index de68eb0ab0..57db1e19bb 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -487,7 +487,7 @@ namespace Avalonia.Controls { if (e == null) { - throw new ArgumentNullException("e"); + throw new ArgumentNullException(nameof(e)); } var handler = Spinned; diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index 54d0a7d40f..cfe17a4291 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -69,7 +69,7 @@ namespace Avalonia.Styling { throw new ArgumentException( "Cannot assign a control to Setter.Value. Wrap the control in a