committed by
GitHub
52 changed files with 8391 additions and 93 deletions
@ -0,0 +1,6 @@ |
|||||
|
<Application xmlns="https://github.com/avaloniaui"> |
||||
|
<Application.Styles> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
</Application.Styles> |
||||
|
</Application> |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace PlatformSanityChecks |
||||
|
{ |
||||
|
public class App : Application |
||||
|
{ |
||||
|
public override void Initialize() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,132 @@ |
|||||
|
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<App>().RuntimePlatformServicesInitializer(); |
||||
|
var app = new App(); |
||||
|
|
||||
|
AvaloniaX11PlatformExtensions.InitializeX11Platform(); |
||||
|
|
||||
|
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<CancellationTokenSource> 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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,14 +1,14 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
<Project Sdk="Microsoft.NET.Sdk"> |
||||
<PropertyGroup> |
<PropertyGroup> |
||||
<TargetFrameworks>netstandard2.0</TargetFrameworks> |
<TargetFramework>netstandard2.0</TargetFramework> |
||||
</PropertyGroup> |
</PropertyGroup> |
||||
|
|
||||
<ItemGroup> |
<ItemGroup> |
||||
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" /> |
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" /> |
||||
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj" /> |
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj" /> |
||||
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" /> |
<ProjectReference Include="../../src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" /> |
||||
<ProjectReference Include="../../src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj" /> |
|
||||
<ProjectReference Include="../../src/Avalonia.Native/Avalonia.Native.csproj" /> |
<ProjectReference Include="../../src/Avalonia.Native/Avalonia.Native.csproj" /> |
||||
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" /> |
<ProjectReference Include="../../packages/Avalonia/Avalonia.csproj" /> |
||||
|
<ProjectReference Include="../Avalonia.X11/Avalonia.X11.csproj" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,14 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> |
||||
|
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" /> |
||||
|
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
File diff suppressed because it is too large
@ -0,0 +1,17 @@ |
|||||
|
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 PlatformSettingsStub : IPlatformSettings |
||||
|
{ |
||||
|
public Size DoubleClickSize { get; } = new Size(2, 2); |
||||
|
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,207 @@ |
|||||
|
// 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 System.Linq; |
||||
|
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 CLIPBOARD_MANAGER; |
||||
|
public readonly IntPtr SAVE_TARGETS; |
||||
|
public readonly IntPtr MULTIPLE; |
||||
|
public readonly IntPtr PRIMARY; |
||||
|
public readonly IntPtr OEMTEXT; |
||||
|
public readonly IntPtr UNICODETEXT; |
||||
|
public readonly IntPtr TARGETS; |
||||
|
public readonly IntPtr UTF8_STRING; |
||||
|
public readonly IntPtr UTF16_STRING; |
||||
|
public readonly IntPtr ATOM_PAIR; |
||||
|
|
||||
|
|
||||
|
public X11Atoms(IntPtr display) |
||||
|
{ |
||||
|
|
||||
|
// make sure this array stays in sync with the statements below
|
||||
|
|
||||
|
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, atomNames, atomNames.Length, true, atoms); |
||||
|
|
||||
|
for (var c = 0; c < fields.Length; c++) |
||||
|
fields[c].SetValue(this, atoms[c]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,234 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Input.Platform; |
||||
|
using static Avalonia.X11.XLib; |
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
class X11Clipboard : IClipboard |
||||
|
{ |
||||
|
private readonly X11Info _x11; |
||||
|
private string _storedString; |
||||
|
private IntPtr _handle; |
||||
|
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs; |
||||
|
private TaskCompletionSource<string> _requestedTextTcs; |
||||
|
private readonly IntPtr[] _textAtoms; |
||||
|
private readonly IntPtr _avaloniaSaveTargetsAtom; |
||||
|
|
||||
|
public X11Clipboard(AvaloniaX11Platform platform) |
||||
|
{ |
||||
|
_x11 = platform.Info; |
||||
|
_handle = CreateEventWindow(platform, OnEvent); |
||||
|
_avaloniaSaveTargetsAtom = XInternAtom(_x11.Display, "AVALONIA_SAVE_TARGETS_PROPERTY_ATOM", false); |
||||
|
_textAtoms = new[] |
||||
|
{ |
||||
|
_x11.Atoms.XA_STRING, |
||||
|
_x11.Atoms.OEMTEXT, |
||||
|
_x11.Atoms.UTF8_STRING, |
||||
|
_x11.Atoms.UTF16_STRING |
||||
|
}.Where(a => a != IntPtr.Zero).ToArray(); |
||||
|
} |
||||
|
|
||||
|
Encoding GetStringEncoding(IntPtr atom) |
||||
|
{ |
||||
|
return (atom == _x11.Atoms.XA_STRING |
||||
|
|| atom == _x11.Atoms.OEMTEXT) |
||||
|
? Encoding.ASCII |
||||
|
: atom == _x11.Atoms.UTF8_STRING |
||||
|
? Encoding.UTF8 |
||||
|
: atom == _x11.Atoms.UTF16_STRING |
||||
|
? Encoding.Unicode |
||||
|
: null; |
||||
|
} |
||||
|
|
||||
|
private unsafe void OnEvent(XEvent ev) |
||||
|
{ |
||||
|
if (ev.type == XEventName.SelectionRequest) |
||||
|
{ |
||||
|
var sel = ev.SelectionRequestEvent; |
||||
|
var resp = new XEvent |
||||
|
{ |
||||
|
SelectionEvent = |
||||
|
{ |
||||
|
type = XEventName.SelectionNotify, |
||||
|
send_event = true, |
||||
|
display = _x11.Display, |
||||
|
selection = sel.selection, |
||||
|
target = sel.target, |
||||
|
requestor = sel.requestor, |
||||
|
time = sel.time, |
||||
|
property = IntPtr.Zero |
||||
|
} |
||||
|
}; |
||||
|
if (sel.selection == _x11.Atoms.CLIPBOARD) |
||||
|
{ |
||||
|
resp.SelectionEvent.property = WriteTargetToProperty(sel.target, sel.requestor, sel.property); |
||||
|
} |
||||
|
|
||||
|
XSendEvent(_x11.Display, sel.requestor, false, new IntPtr((int)EventMask.NoEventMask), ref resp); |
||||
|
} |
||||
|
|
||||
|
IntPtr WriteTargetToProperty(IntPtr target, IntPtr window, IntPtr property) |
||||
|
{ |
||||
|
Encoding textEnc; |
||||
|
if (target == _x11.Atoms.TARGETS) |
||||
|
{ |
||||
|
var atoms = _textAtoms; |
||||
|
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE}) |
||||
|
.ToArray(); |
||||
|
XChangeProperty(_x11.Display, window, property, |
||||
|
target, 32, PropertyMode.Replace, atoms, atoms.Length); |
||||
|
return property; |
||||
|
} |
||||
|
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) |
||||
|
{ |
||||
|
return property; |
||||
|
} |
||||
|
else if ((textEnc = GetStringEncoding(target)) != null) |
||||
|
{ |
||||
|
|
||||
|
var data = textEnc.GetBytes(_storedString ?? ""); |
||||
|
fixed (void* pdata = data) |
||||
|
XChangeProperty(_x11.Display, window, property, target, 8, |
||||
|
PropertyMode.Replace, |
||||
|
pdata, data.Length); |
||||
|
return property; |
||||
|
} |
||||
|
else if (target == _x11.Atoms.MULTIPLE && _x11.Atoms.MULTIPLE != IntPtr.Zero) |
||||
|
{ |
||||
|
XGetWindowProperty(_x11.Display, window, property, IntPtr.Zero, new IntPtr(0x7fffffff), false, |
||||
|
_x11.Atoms.ATOM_PAIR, out _, out var actualFormat, out var nitems, out _, out var prop); |
||||
|
if (nitems == IntPtr.Zero) |
||||
|
return IntPtr.Zero; |
||||
|
if (actualFormat == 32) |
||||
|
{ |
||||
|
var data = (IntPtr*)prop.ToPointer(); |
||||
|
for (var c = 0; c < nitems.ToInt32(); c += 2) |
||||
|
{ |
||||
|
var subTarget = data[c]; |
||||
|
var subProp = data[c + 1]; |
||||
|
var converted = WriteTargetToProperty(subTarget, window, subProp); |
||||
|
data[c + 1] = converted; |
||||
|
} |
||||
|
|
||||
|
XChangeProperty(_x11.Display, window, property, _x11.Atoms.ATOM_PAIR, 32, PropertyMode.Replace, |
||||
|
prop.ToPointer(), nitems.ToInt32()); |
||||
|
} |
||||
|
|
||||
|
XFree(prop); |
||||
|
|
||||
|
return property; |
||||
|
} |
||||
|
else |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
if (ev.type == XEventName.SelectionNotify && ev.SelectionEvent.selection == _x11.Atoms.CLIPBOARD) |
||||
|
{ |
||||
|
var sel = ev.SelectionEvent; |
||||
|
if (sel.property == IntPtr.Zero) |
||||
|
{ |
||||
|
_requestedFormatsTcs?.TrySetResult(null); |
||||
|
_requestedTextTcs?.TrySetResult(null); |
||||
|
} |
||||
|
XGetWindowProperty(_x11.Display, _handle, sel.property, IntPtr.Zero, new IntPtr (0x7fffffff), true, (IntPtr)Atom.AnyPropertyType, |
||||
|
out var actualAtom, out var actualFormat, out var nitems, out var bytes_after, out var prop); |
||||
|
Encoding textEnc = null; |
||||
|
if (nitems == IntPtr.Zero) |
||||
|
{ |
||||
|
_requestedFormatsTcs?.TrySetResult(null); |
||||
|
_requestedTextTcs?.TrySetResult(null); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (sel.property == _x11.Atoms.TARGETS) |
||||
|
{ |
||||
|
if (actualFormat != 32) |
||||
|
_requestedFormatsTcs?.TrySetResult(null); |
||||
|
else |
||||
|
{ |
||||
|
var formats = new IntPtr[nitems.ToInt32()]; |
||||
|
Marshal.Copy(prop, formats, 0, formats.Length); |
||||
|
_requestedFormatsTcs?.TrySetResult(formats); |
||||
|
} |
||||
|
} |
||||
|
else if ((textEnc = GetStringEncoding(sel.property)) != null) |
||||
|
{ |
||||
|
var text = textEnc.GetString((byte*)prop.ToPointer(), nitems.ToInt32()); |
||||
|
_requestedTextTcs?.TrySetResult(text); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
XFree(prop); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Task<IntPtr[]> SendFormatRequest() |
||||
|
{ |
||||
|
if (_requestedFormatsTcs == null || _requestedFormatsTcs.Task.IsCompleted) |
||||
|
_requestedFormatsTcs = new TaskCompletionSource<IntPtr[]>(); |
||||
|
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, _x11.Atoms.TARGETS, _x11.Atoms.TARGETS, _handle, |
||||
|
IntPtr.Zero); |
||||
|
return _requestedFormatsTcs.Task; |
||||
|
} |
||||
|
|
||||
|
Task<string> SendTextRequest(IntPtr format) |
||||
|
{ |
||||
|
if (_requestedTextTcs == null || _requestedFormatsTcs.Task.IsCompleted) |
||||
|
_requestedTextTcs = new TaskCompletionSource<string>(); |
||||
|
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD, format, format, _handle, IntPtr.Zero); |
||||
|
return _requestedTextTcs.Task; |
||||
|
} |
||||
|
|
||||
|
public async Task<string> GetTextAsync() |
||||
|
{ |
||||
|
if (XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD) == IntPtr.Zero) |
||||
|
return null; |
||||
|
var res = await SendFormatRequest(); |
||||
|
var target = _x11.Atoms.UTF8_STRING; |
||||
|
if (res != null) |
||||
|
{ |
||||
|
var preferredFormats = new[] {_x11.Atoms.UTF16_STRING, _x11.Atoms.UTF8_STRING, _x11.Atoms.XA_STRING}; |
||||
|
foreach (var pf in preferredFormats) |
||||
|
if (res.Contains(pf)) |
||||
|
{ |
||||
|
target = pf; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return await SendTextRequest(target); |
||||
|
} |
||||
|
|
||||
|
void StoreAtomsInClipboardManager(IntPtr[] atoms) |
||||
|
{ |
||||
|
if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) |
||||
|
{ |
||||
|
var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER); |
||||
|
if (clipboardManager != IntPtr.Zero) |
||||
|
{ |
||||
|
XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32, |
||||
|
PropertyMode.Replace, |
||||
|
atoms, atoms.Length); |
||||
|
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER, _x11.Atoms.SAVE_TARGETS, |
||||
|
_avaloniaSaveTargetsAtom, _handle, IntPtr.Zero); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task SetTextAsync(string text) |
||||
|
{ |
||||
|
_storedString = text; |
||||
|
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); |
||||
|
StoreAtomsInClipboardManager(_textAtoms); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return SetTextAsync(null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<CursorFontShape, IntPtr> _cursors; |
||||
|
|
||||
|
private static readonly Dictionary<StandardCursorType, CursorFontShape> s_mapping = |
||||
|
new Dictionary<StandardCursorType, CursorFontShape> |
||||
|
{ |
||||
|
{StandardCursorType.Arrow, CursorFontShape.XC_top_left_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<CursorFontShape>() |
||||
|
.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_top_left_arrow]; |
||||
|
return new PlatformHandle(handle, "XCURSOR"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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), |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
public class X11Exception : Exception |
||||
|
{ |
||||
|
public X11Exception(string message) : base(message) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using Avalonia.Platform; |
||||
|
using SkiaSharp; |
||||
|
using static Avalonia.X11.XLib; |
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
class X11Framebuffer : ILockedFramebuffer |
||||
|
{ |
||||
|
private readonly IntPtr _display; |
||||
|
private readonly IntPtr _xid; |
||||
|
private readonly int _depth; |
||||
|
private IUnmanagedBlob _blob; |
||||
|
|
||||
|
public X11Framebuffer(IntPtr display, IntPtr xid, int depth, int width, int height, double factor) |
||||
|
{ |
||||
|
_display = display; |
||||
|
_xid = xid; |
||||
|
_depth = depth; |
||||
|
Size = new PixelSize(width, height); |
||||
|
RowBytes = width * 4; |
||||
|
Dpi = new Vector(96, 96) * factor; |
||||
|
Format = PixelFormat.Bgra8888; |
||||
|
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(RowBytes * height); |
||||
|
Address = _blob.Address; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
var image = new XImage(); |
||||
|
int bitsPerPixel = 32; |
||||
|
image.width = Size.Width; |
||||
|
image.height = Size.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 = _depth; |
||||
|
image.bytes_per_line = RowBytes; |
||||
|
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) Size.Width, (uint) Size.Height); |
||||
|
XFreeGC(_display, gc); |
||||
|
XSync(_display, true); |
||||
|
XUnlockDisplay(_display); |
||||
|
_blob.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public IntPtr Address { get; } |
||||
|
public PixelSize Size { get; } |
||||
|
public int RowBytes { get; } |
||||
|
public Vector Dpi { get; } |
||||
|
public PixelFormat Format { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
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; |
||||
|
private readonly Func<double> _scaling; |
||||
|
|
||||
|
public X11FramebufferSurface(IntPtr display, IntPtr xid, Func<double> scaling) |
||||
|
{ |
||||
|
_display = display; |
||||
|
_xid = xid; |
||||
|
_scaling = scaling; |
||||
|
} |
||||
|
|
||||
|
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, 24,width, height, _scaling()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
using System; |
||||
|
using System.IO; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Controls.Platform.Surfaces; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Utilities; |
||||
|
using Avalonia.Visuals.Media.Imaging; |
||||
|
using static Avalonia.X11.XLib; |
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
class X11IconLoader : IPlatformIconLoader |
||||
|
{ |
||||
|
private readonly X11Info _x11; |
||||
|
|
||||
|
public X11IconLoader(X11Info x11) |
||||
|
{ |
||||
|
_x11 = x11; |
||||
|
} |
||||
|
|
||||
|
IWindowIconImpl LoadIcon(Bitmap bitmap) |
||||
|
{ |
||||
|
var rv = new X11IconData(bitmap); |
||||
|
bitmap.Dispose(); |
||||
|
return rv; |
||||
|
} |
||||
|
|
||||
|
public IWindowIconImpl LoadIcon(string fileName) => LoadIcon(new Bitmap(fileName)); |
||||
|
|
||||
|
public IWindowIconImpl LoadIcon(Stream stream) => LoadIcon(new Bitmap(stream)); |
||||
|
|
||||
|
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) |
||||
|
{ |
||||
|
var ms = new MemoryStream(); |
||||
|
bitmap.Save(ms); |
||||
|
ms.Position = 0; |
||||
|
return LoadIcon(ms); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
unsafe class X11IconData : IWindowIconImpl, IFramebufferPlatformSurface |
||||
|
{ |
||||
|
private int _width; |
||||
|
private int _height; |
||||
|
private uint[] _bdata; |
||||
|
public UIntPtr[] Data { get; } |
||||
|
|
||||
|
public X11IconData(Bitmap bitmap) |
||||
|
{ |
||||
|
_width = Math.Min(bitmap.PixelSize.Width, 128); |
||||
|
_height = Math.Min(bitmap.PixelSize.Height, 128); |
||||
|
_bdata = new uint[_width * _height]; |
||||
|
fixed (void* ptr = _bdata) |
||||
|
{ |
||||
|
var iptr = (int*)ptr; |
||||
|
iptr[0] = _width; |
||||
|
iptr[1] = _height; |
||||
|
} |
||||
|
using(var rt = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[]{this})) |
||||
|
using (var ctx = rt.CreateDrawingContext(null)) |
||||
|
ctx.DrawImage(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), |
||||
|
new Rect(0, 0, _width, _height)); |
||||
|
Data = new UIntPtr[_width * _height + 2]; |
||||
|
Data[0] = new UIntPtr((uint)_width); |
||||
|
Data[1] = new UIntPtr((uint)_height); |
||||
|
for (var y = 0; y < _height; y++) |
||||
|
{ |
||||
|
var r = y * _width; |
||||
|
for (var x = 0; x < _width; x++) |
||||
|
Data[r + x] = new UIntPtr(_bdata[r + x]); |
||||
|
} |
||||
|
|
||||
|
_bdata = null; |
||||
|
} |
||||
|
|
||||
|
public void Save(Stream outputStream) |
||||
|
{ |
||||
|
using (var wr = |
||||
|
new WriteableBitmap(new PixelSize(_width, _height), new Vector(96, 96), PixelFormat.Bgra8888)) |
||||
|
{ |
||||
|
using (var fb = wr.Lock()) |
||||
|
{ |
||||
|
var fbp = (uint*)fb.Address; |
||||
|
for (var y = 0; y < _height; y++) |
||||
|
{ |
||||
|
var r = y * _width; |
||||
|
var fbr = y * fb.RowBytes / 4; |
||||
|
for (var x = 0; x < _width; x++) |
||||
|
fbp[fbr + x] = Data[r + x].ToUInt32(); |
||||
|
} |
||||
|
} |
||||
|
wr.Save(outputStream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ILockedFramebuffer Lock() |
||||
|
{ |
||||
|
var h = GCHandle.Alloc(_bdata, GCHandleType.Pinned); |
||||
|
return new LockedFramebuffer(h.AddrOfPinnedObject(), new PixelSize(_width, _height), _width * 4, |
||||
|
new Vector(96, 96), PixelFormat.Bgra8888, |
||||
|
() => h.Free()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
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 |
||||
|
{ |
||||
|
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 IntPtr DefaultCursor { get; } |
||||
|
public X11Atoms Atoms { get; } |
||||
|
public IntPtr Xim { get; } |
||||
|
|
||||
|
public int RandrEventBase { get; } |
||||
|
public int RandrErrorBase { get; } |
||||
|
|
||||
|
public Version RandrVersion { get; } |
||||
|
|
||||
|
public int XInputOpcode { get; } |
||||
|
public int XInputEventBase { get; } |
||||
|
public int XInputErrorBase { get; } |
||||
|
|
||||
|
public Version XInputVersion { 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); |
||||
|
DefaultCursor = XCreateFontCursor(display, CursorFontShape.XC_top_left_arrow); |
||||
|
DefaultRootWindow = XDefaultRootWindow(display); |
||||
|
Atoms = new X11Atoms(display); |
||||
|
//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
|
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (XQueryExtension(display, "XInputExtension", |
||||
|
out var xiopcode, out var xievent, out var xierror)) |
||||
|
{ |
||||
|
int major = 2, minor = 2; |
||||
|
if (XIQueryVersion(display, ref major, ref minor) == Status.Success) |
||||
|
{ |
||||
|
XInputVersion = new Version(major, minor); |
||||
|
XInputOpcode = xiopcode; |
||||
|
XInputEventBase = xievent; |
||||
|
XInputErrorBase = xierror; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//Ignore, XI is not supported
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,232 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Input; |
||||
|
|
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
static class X11KeyTransform |
||||
|
{ |
||||
|
private static readonly Dictionary<X11Key, Key> KeyDic = new Dictionary<X11Key, Key> |
||||
|
{ |
||||
|
{X11Key.Cancel, Key.Cancel}, |
||||
|
{X11Key.BackSpace, Key.Back}, |
||||
|
{X11Key.Tab, Key.Tab}, |
||||
|
{X11Key.Linefeed, Key.LineFeed}, |
||||
|
{X11Key.Clear, Key.Clear}, |
||||
|
{X11Key.Return, Key.Return}, |
||||
|
{X11Key.KP_Enter, Key.Return}, |
||||
|
{X11Key.Pause, Key.Pause}, |
||||
|
{X11Key.Caps_Lock, Key.CapsLock}, |
||||
|
//{ X11Key.?, Key.HangulMode }
|
||||
|
//{ X11Key.?, Key.JunjaMode }
|
||||
|
//{ X11Key.?, Key.FinalMode }
|
||||
|
//{ X11Key.?, Key.KanjiMode }
|
||||
|
{X11Key.Escape, Key.Escape}, |
||||
|
//{ X11Key.?, Key.ImeConvert }
|
||||
|
//{ X11Key.?, Key.ImeNonConvert }
|
||||
|
//{ X11Key.?, Key.ImeAccept }
|
||||
|
//{ X11Key.?, Key.ImeModeChange }
|
||||
|
{X11Key.space, Key.Space}, |
||||
|
{X11Key.Prior, Key.Prior}, |
||||
|
{X11Key.KP_Prior, Key.Prior}, |
||||
|
{X11Key.Page_Down, Key.PageDown}, |
||||
|
{X11Key.KP_Page_Down, Key.PageDown}, |
||||
|
{X11Key.End, Key.End}, |
||||
|
{X11Key.KP_End, Key.End}, |
||||
|
{X11Key.Home, Key.Home}, |
||||
|
{X11Key.KP_Home, Key.Home}, |
||||
|
{X11Key.Left, Key.Left}, |
||||
|
{X11Key.KP_Left, Key.Left}, |
||||
|
{X11Key.Up, Key.Up}, |
||||
|
{X11Key.KP_Up, Key.Up}, |
||||
|
{X11Key.Right, Key.Right}, |
||||
|
{X11Key.KP_Right, Key.Right}, |
||||
|
{X11Key.Down, Key.Down}, |
||||
|
{X11Key.KP_Down, Key.Down}, |
||||
|
{X11Key.Select, Key.Select}, |
||||
|
{X11Key.Print, Key.Print}, |
||||
|
{X11Key.Execute, Key.Execute}, |
||||
|
//{ X11Key.?, Key.Snapshot }
|
||||
|
{X11Key.Insert, Key.Insert}, |
||||
|
{X11Key.KP_Insert, Key.Insert}, |
||||
|
{X11Key.Delete, Key.Delete}, |
||||
|
{X11Key.KP_Delete, Key.Delete}, |
||||
|
{X11Key.Help, Key.Help}, |
||||
|
{X11Key.XK_0, Key.D0}, |
||||
|
{X11Key.XK_1, Key.D1}, |
||||
|
{X11Key.XK_2, Key.D2}, |
||||
|
{X11Key.XK_3, Key.D3}, |
||||
|
{X11Key.XK_4, Key.D4}, |
||||
|
{X11Key.XK_5, Key.D5}, |
||||
|
{X11Key.XK_6, Key.D6}, |
||||
|
{X11Key.XK_7, Key.D7}, |
||||
|
{X11Key.XK_8, Key.D8}, |
||||
|
{X11Key.XK_9, Key.D9}, |
||||
|
{X11Key.A, Key.A}, |
||||
|
{X11Key.B, Key.B}, |
||||
|
{X11Key.C, Key.C}, |
||||
|
{X11Key.D, Key.D}, |
||||
|
{X11Key.E, Key.E}, |
||||
|
{X11Key.F, Key.F}, |
||||
|
{X11Key.G, Key.G}, |
||||
|
{X11Key.H, Key.H}, |
||||
|
{X11Key.I, Key.I}, |
||||
|
{X11Key.J, Key.J}, |
||||
|
{X11Key.K, Key.K}, |
||||
|
{X11Key.L, Key.L}, |
||||
|
{X11Key.M, Key.M}, |
||||
|
{X11Key.N, Key.N}, |
||||
|
{X11Key.O, Key.O}, |
||||
|
{X11Key.P, Key.P}, |
||||
|
{X11Key.Q, Key.Q}, |
||||
|
{X11Key.R, Key.R}, |
||||
|
{X11Key.S, Key.S}, |
||||
|
{X11Key.T, Key.T}, |
||||
|
{X11Key.U, Key.U}, |
||||
|
{X11Key.V, Key.V}, |
||||
|
{X11Key.W, Key.W}, |
||||
|
{X11Key.X, Key.X}, |
||||
|
{X11Key.Y, Key.Y}, |
||||
|
{X11Key.Z, Key.Z}, |
||||
|
{X11Key.a, Key.A}, |
||||
|
{X11Key.b, Key.B}, |
||||
|
{X11Key.c, Key.C}, |
||||
|
{X11Key.d, Key.D}, |
||||
|
{X11Key.e, Key.E}, |
||||
|
{X11Key.f, Key.F}, |
||||
|
{X11Key.g, Key.G}, |
||||
|
{X11Key.h, Key.H}, |
||||
|
{X11Key.i, Key.I}, |
||||
|
{X11Key.j, Key.J}, |
||||
|
{X11Key.k, Key.K}, |
||||
|
{X11Key.l, Key.L}, |
||||
|
{X11Key.m, Key.M}, |
||||
|
{X11Key.n, Key.N}, |
||||
|
{X11Key.o, Key.O}, |
||||
|
{X11Key.p, Key.P}, |
||||
|
{X11Key.q, Key.Q}, |
||||
|
{X11Key.r, Key.R}, |
||||
|
{X11Key.s, Key.S}, |
||||
|
{X11Key.t, Key.T}, |
||||
|
{X11Key.u, Key.U}, |
||||
|
{X11Key.v, Key.V}, |
||||
|
{X11Key.w, Key.W}, |
||||
|
{X11Key.x, Key.X}, |
||||
|
{X11Key.y, Key.Y}, |
||||
|
{X11Key.z, Key.Z}, |
||||
|
//{ X11Key.?, Key.LWin }
|
||||
|
//{ X11Key.?, Key.RWin }
|
||||
|
{X11Key.Menu, Key.Apps}, |
||||
|
//{ X11Key.?, Key.Sleep }
|
||||
|
{X11Key.KP_0, Key.NumPad0}, |
||||
|
{X11Key.KP_1, Key.NumPad1}, |
||||
|
{X11Key.KP_2, Key.NumPad2}, |
||||
|
{X11Key.KP_3, Key.NumPad3}, |
||||
|
{X11Key.KP_4, Key.NumPad4}, |
||||
|
{X11Key.KP_5, Key.NumPad5}, |
||||
|
{X11Key.KP_6, Key.NumPad6}, |
||||
|
{X11Key.KP_7, Key.NumPad7}, |
||||
|
{X11Key.KP_8, Key.NumPad8}, |
||||
|
{X11Key.KP_9, Key.NumPad9}, |
||||
|
{X11Key.multiply, Key.Multiply}, |
||||
|
{X11Key.KP_Multiply, Key.Multiply}, |
||||
|
{X11Key.KP_Add, Key.Add}, |
||||
|
//{ X11Key.?, Key.Separator }
|
||||
|
{X11Key.KP_Subtract, Key.Subtract}, |
||||
|
{X11Key.KP_Decimal, Key.Decimal}, |
||||
|
{X11Key.KP_Divide, Key.Divide}, |
||||
|
{X11Key.F1, Key.F1}, |
||||
|
{X11Key.F2, Key.F2}, |
||||
|
{X11Key.F3, Key.F3}, |
||||
|
{X11Key.F4, Key.F4}, |
||||
|
{X11Key.F5, Key.F5}, |
||||
|
{X11Key.F6, Key.F6}, |
||||
|
{X11Key.F7, Key.F7}, |
||||
|
{X11Key.F8, Key.F8}, |
||||
|
{X11Key.F9, Key.F9}, |
||||
|
{X11Key.F10, Key.F10}, |
||||
|
{X11Key.F11, Key.F11}, |
||||
|
{X11Key.F12, Key.F12}, |
||||
|
{X11Key.L3, Key.F13}, |
||||
|
{X11Key.F14, Key.F14}, |
||||
|
{X11Key.L5, Key.F15}, |
||||
|
{X11Key.F16, Key.F16}, |
||||
|
{X11Key.F17, Key.F17}, |
||||
|
{X11Key.L8, Key.F18}, |
||||
|
{X11Key.L9, Key.F19}, |
||||
|
{X11Key.L10, Key.F20}, |
||||
|
{X11Key.R1, Key.F21}, |
||||
|
{X11Key.R2, Key.F22}, |
||||
|
{X11Key.F23, Key.F23}, |
||||
|
{X11Key.R4, Key.F24}, |
||||
|
{X11Key.Num_Lock, Key.NumLock}, |
||||
|
{X11Key.Scroll_Lock, Key.Scroll}, |
||||
|
{X11Key.Shift_L, Key.LeftShift}, |
||||
|
{X11Key.Shift_R, Key.RightShift}, |
||||
|
{X11Key.Control_L, Key.LeftCtrl}, |
||||
|
{X11Key.Control_R, Key.RightCtrl}, |
||||
|
{X11Key.Alt_L, Key.LeftAlt}, |
||||
|
{X11Key.Alt_R, Key.RightAlt}, |
||||
|
//{ X11Key.?, Key.BrowserBack }
|
||||
|
//{ X11Key.?, Key.BrowserForward }
|
||||
|
//{ X11Key.?, Key.BrowserRefresh }
|
||||
|
//{ X11Key.?, Key.BrowserStop }
|
||||
|
//{ X11Key.?, Key.BrowserSearch }
|
||||
|
//{ X11Key.?, Key.BrowserFavorites }
|
||||
|
//{ X11Key.?, Key.BrowserHome }
|
||||
|
//{ X11Key.?, Key.VolumeMute }
|
||||
|
//{ X11Key.?, Key.VolumeDown }
|
||||
|
//{ X11Key.?, Key.VolumeUp }
|
||||
|
//{ X11Key.?, Key.MediaNextTrack }
|
||||
|
//{ X11Key.?, Key.MediaPreviousTrack }
|
||||
|
//{ X11Key.?, Key.MediaStop }
|
||||
|
//{ X11Key.?, Key.MediaPlayPause }
|
||||
|
//{ X11Key.?, Key.LaunchMail }
|
||||
|
//{ X11Key.?, Key.SelectMedia }
|
||||
|
//{ X11Key.?, Key.LaunchApplication1 }
|
||||
|
//{ X11Key.?, Key.LaunchApplication2 }
|
||||
|
{X11Key.semicolon, Key.OemSemicolon}, |
||||
|
{X11Key.plus, Key.OemPlus}, |
||||
|
{X11Key.equal, Key.OemPlus}, |
||||
|
{X11Key.comma, Key.OemComma}, |
||||
|
{X11Key.minus, Key.OemMinus}, |
||||
|
{X11Key.period, Key.OemPeriod}, |
||||
|
{X11Key.slash, Key.Oem2}, |
||||
|
{X11Key.grave, Key.OemTilde}, |
||||
|
//{ X11Key.?, Key.AbntC1 }
|
||||
|
//{ X11Key.?, Key.AbntC2 }
|
||||
|
{X11Key.bracketleft, Key.OemOpenBrackets}, |
||||
|
{X11Key.backslash, Key.OemPipe}, |
||||
|
{X11Key.bracketright, Key.OemCloseBrackets}, |
||||
|
{X11Key.apostrophe, Key.OemQuotes}, |
||||
|
//{ X11Key.?, Key.Oem8 }
|
||||
|
//{ X11Key.?, Key.Oem102 }
|
||||
|
//{ X11Key.?, Key.ImeProcessed }
|
||||
|
//{ X11Key.?, Key.System }
|
||||
|
//{ X11Key.?, Key.OemAttn }
|
||||
|
//{ X11Key.?, Key.OemFinish }
|
||||
|
//{ X11Key.?, Key.DbeHiragana }
|
||||
|
//{ X11Key.?, Key.OemAuto }
|
||||
|
//{ X11Key.?, Key.DbeDbcsChar }
|
||||
|
//{ X11Key.?, Key.OemBackTab }
|
||||
|
//{ X11Key.?, Key.Attn }
|
||||
|
//{ X11Key.?, Key.DbeEnterWordRegisterMode }
|
||||
|
//{ X11Key.?, Key.DbeEnterImeConfigureMode }
|
||||
|
//{ X11Key.?, Key.EraseEof }
|
||||
|
//{ X11Key.?, Key.Play }
|
||||
|
//{ X11Key.?, Key.Zoom }
|
||||
|
//{ X11Key.?, Key.NoName }
|
||||
|
//{ X11Key.?, Key.DbeEnterDialogConversionMode }
|
||||
|
//{ X11Key.?, Key.OemClear }
|
||||
|
//{ X11Key.?, Key.DeadCharProcessed }
|
||||
|
}; |
||||
|
|
||||
|
public static Key ConvertKey(IntPtr key) |
||||
|
{ |
||||
|
var ikey = key.ToInt32(); |
||||
|
Key result; |
||||
|
return KeyDic.TryGetValue((X11Key)ikey, out result) ? result : Key.None; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Gtk3; |
||||
|
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 |
||||
|
{ |
||||
|
class AvaloniaX11Platform : IWindowingPlatform |
||||
|
{ |
||||
|
private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice()); |
||||
|
private Lazy<MouseDevice> _mouseDevice = new Lazy<MouseDevice>(() => new MouseDevice()); |
||||
|
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value; |
||||
|
public MouseDevice MouseDevice => _mouseDevice.Value; |
||||
|
public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>(); |
||||
|
public XI2Manager XI2; |
||||
|
public X11Info Info { get; private set; } |
||||
|
public IX11Screens X11Screens { get; private set; } |
||||
|
public IScreenImpl Screens { 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<IWindowingPlatform>().ToConstant(this) |
||||
|
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this)) |
||||
|
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) |
||||
|
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) |
||||
|
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control)) |
||||
|
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice) |
||||
|
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display)) |
||||
|
.Bind<IClipboard>().ToConstant(new X11Clipboard(this)) |
||||
|
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub()) |
||||
|
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info)) |
||||
|
.Bind<ISystemDialogImpl>().ToConstant(new Gtk3ForeignX11SystemDialog()); |
||||
|
|
||||
|
X11Screens = Avalonia.X11.X11Screens.Init(this); |
||||
|
Screens = new X11Screens(X11Screens); |
||||
|
if (Info.XInputVersion != null) |
||||
|
{ |
||||
|
var xi2 = new XI2Manager(); |
||||
|
if (xi2.Init(this)) |
||||
|
XI2 = xi2; |
||||
|
} |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
public static class AvaloniaX11PlatformExtensions |
||||
|
{ |
||||
|
public static T UseX11<T>(this T builder) where T : AppBuilderBase<T>, new() |
||||
|
{ |
||||
|
builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); |
||||
|
return builder; |
||||
|
} |
||||
|
|
||||
|
public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,285 @@ |
|||||
|
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 |
||||
|
{ |
||||
|
unsafe class X11PlatformThreading : IPlatformThreadingInterface |
||||
|
{ |
||||
|
private readonly AvaloniaX11Platform _platform; |
||||
|
private readonly IntPtr _display; |
||||
|
private readonly Dictionary<IntPtr, Action<XEvent>> _eventHandlers; |
||||
|
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<X11Timer> _timers = new List<X11Timer>(); |
||||
|
|
||||
|
public X11PlatformThreading(AvaloniaX11Platform platform) |
||||
|
{ |
||||
|
_platform = platform; |
||||
|
_display = platform.Display; |
||||
|
_eventHandlers = platform.Windows; |
||||
|
_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; |
||||
|
} |
||||
|
|
||||
|
void CheckSignaled() |
||||
|
{ |
||||
|
int buf = 0; |
||||
|
while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
DispatcherPriority prio; |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (!_signaled) |
||||
|
return; |
||||
|
_signaled = false; |
||||
|
prio = _signaledPriority; |
||||
|
_signaledPriority = DispatcherPriority.MinValue; |
||||
|
} |
||||
|
|
||||
|
Signaled?.Invoke(prio); |
||||
|
} |
||||
|
|
||||
|
void HandleX11(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
var pending = XPending(_display); |
||||
|
if (pending == 0) |
||||
|
break; |
||||
|
while (pending > 0) |
||||
|
{ |
||||
|
if (cancellationToken.IsCancellationRequested) |
||||
|
return; |
||||
|
XNextEvent(_display, out var xev); |
||||
|
if (xev.type == XEventName.GenericEvent) |
||||
|
XGetEventData(_display, &xev.GenericEventCookie); |
||||
|
pending--; |
||||
|
try |
||||
|
{ |
||||
|
if (xev.type == XEventName.GenericEvent) |
||||
|
{ |
||||
|
if (_platform.XI2 != null && _platform.Info.XInputOpcode == |
||||
|
xev.GenericEventCookie.extension) |
||||
|
{ |
||||
|
_platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data); |
||||
|
} |
||||
|
} |
||||
|
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler)) |
||||
|
handler(xev); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null) |
||||
|
XFreeEventData(_display, &xev.GenericEventCookie); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
Dispatcher.UIThread.RunJobs(); |
||||
|
} |
||||
|
|
||||
|
public void RunLoop(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var readyTimers = new List<X11Timer>(); |
||||
|
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; |
||||
|
//Flush whatever requests were made to XServer
|
||||
|
XFlush(_display); |
||||
|
epoll_event ev; |
||||
|
if (XPending(_display) == 0) |
||||
|
epoll_wait(_epoll, &ev, 1, |
||||
|
nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds)); |
||||
|
if (cancellationToken.IsCancellationRequested) |
||||
|
return; |
||||
|
CheckSignaled(); |
||||
|
HandleX11(cancellationToken); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
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<DispatcherPriority?> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,252 @@ |
|||||
|
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; |
||||
|
|
||||
|
public 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 = CreateEventWindow(platform, 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[c] = 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 IX11Screens 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); |
||||
|
|
||||
|
return 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<string, double> 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 rv; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
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)); |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,873 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
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; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Threading; |
||||
|
using static Avalonia.X11.XLib; |
||||
|
// ReSharper disable IdentifierTypo
|
||||
|
// ReSharper disable StringLiteralTypo
|
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client |
||||
|
{ |
||||
|
private readonly AvaloniaX11Platform _platform; |
||||
|
private readonly bool _popup; |
||||
|
private readonly X11Info _x11; |
||||
|
private bool _invalidated; |
||||
|
private XConfigureEvent? _configure; |
||||
|
private Point? _configurePoint; |
||||
|
private bool _triggeredExpose; |
||||
|
private IInputRoot _inputRoot; |
||||
|
private readonly IMouseDevice _mouse; |
||||
|
private readonly IKeyboardDevice _keyboard; |
||||
|
private Point? _position; |
||||
|
private PixelSize _realSize; |
||||
|
private IntPtr _handle; |
||||
|
private IntPtr _xic; |
||||
|
private IntPtr _renderHandle; |
||||
|
private bool _mapped; |
||||
|
private HashSet<X11Window> _transientChildren = new HashSet<X11Window>(); |
||||
|
private X11Window _transientParent; |
||||
|
public object SyncRoot { get; } = new object(); |
||||
|
|
||||
|
class InputEventContainer |
||||
|
{ |
||||
|
public RawInputEventArgs Event; |
||||
|
} |
||||
|
private readonly Queue<InputEventContainer> _inputQueue = new Queue<InputEventContainer>(); |
||||
|
private InputEventContainer _lastEvent; |
||||
|
|
||||
|
public X11Window(AvaloniaX11Platform platform, bool popup) |
||||
|
{ |
||||
|
_platform = platform; |
||||
|
_popup = popup; |
||||
|
_x11 = platform.Info; |
||||
|
_mouse = platform.MouseDevice; |
||||
|
_keyboard = platform.KeyboardDevice; |
||||
|
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing, |
||||
|
XNames.XNClientWindow, _handle, IntPtr.Zero); |
||||
|
|
||||
|
XSetWindowAttributes attr = new XSetWindowAttributes(); |
||||
|
var valueMask = default(SetWindowValuemask); |
||||
|
|
||||
|
attr.backing_store = 1; |
||||
|
attr.bit_gravity = Gravity.NorthWestGravity; |
||||
|
attr.win_gravity = Gravity.NorthWestGravity; |
||||
|
valueMask |= 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, |
||||
|
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"); |
||||
|
_realSize = new PixelSize(300, 200); |
||||
|
platform.Windows[_handle] = OnEvent; |
||||
|
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask |
||||
|
| XEventMask.ResizeRedirectMask |
||||
|
| XEventMask.PointerMotionHintMask; |
||||
|
if (platform.XI2 != null) |
||||
|
ignoredMask |= platform.XI2.AddWindow(_handle, this); |
||||
|
var mask = new IntPtr(0xffffff ^ (int)ignoredMask); |
||||
|
XSelectInput(_x11.Display, _handle, mask); |
||||
|
var protocols = new[] |
||||
|
{ |
||||
|
_x11.Atoms.WM_DELETE_WINDOW |
||||
|
}; |
||||
|
XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length); |
||||
|
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM, |
||||
|
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1); |
||||
|
|
||||
|
var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>(); |
||||
|
var surfaces = new List<object> |
||||
|
{ |
||||
|
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling) |
||||
|
}; |
||||
|
if (feature != null) |
||||
|
surfaces.Insert(0, |
||||
|
new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, |
||||
|
new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); |
||||
|
Surfaces = surfaces.ToArray(); |
||||
|
UpdateMotifHints(); |
||||
|
XFlush(_x11.Display); |
||||
|
} |
||||
|
|
||||
|
class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo |
||||
|
{ |
||||
|
private readonly X11Window _window; |
||||
|
private readonly IntPtr _display; |
||||
|
private readonly IntPtr _parent; |
||||
|
|
||||
|
public SurfaceInfo(X11Window window, IntPtr display, IntPtr parent, IntPtr xid) |
||||
|
{ |
||||
|
_window = window; |
||||
|
_display = display; |
||||
|
_parent = parent; |
||||
|
Handle = xid; |
||||
|
} |
||||
|
public IntPtr Handle { get; } |
||||
|
|
||||
|
public PixelSize Size |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
XLockDisplay(_display); |
||||
|
XGetGeometry(_display, _parent, out var geo); |
||||
|
XResizeWindow(_display, Handle, geo.width, geo.height); |
||||
|
XFlush(_display); |
||||
|
XSync(_display, true); |
||||
|
XUnlockDisplay(_display); |
||||
|
return new PixelSize(geo.width, geo.height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double Scaling => _window.Scaling; |
||||
|
} |
||||
|
|
||||
|
void UpdateMotifHints() |
||||
|
{ |
||||
|
var functions = MotifFunctions.Move | MotifFunctions.Close | MotifFunctions.Resize | |
||||
|
MotifFunctions.Minimize | MotifFunctions.Maximize; |
||||
|
var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border | |
||||
|
MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH; |
||||
|
|
||||
|
if (_popup || !_systemDecorations) |
||||
|
{ |
||||
|
decorations = 0; |
||||
|
} |
||||
|
|
||||
|
if (!_canResize) |
||||
|
{ |
||||
|
functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize); |
||||
|
decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH); |
||||
|
} |
||||
|
|
||||
|
var hints = new MotifWmHints |
||||
|
{ |
||||
|
flags = new IntPtr((int)(MotifFlags.Decorations | MotifFlags.Functions)), |
||||
|
decorations = new IntPtr((int)decorations), |
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
void UpdateSizeHints(PixelSize? preResize) |
||||
|
{ |
||||
|
var min = _minMaxSize.minSize; |
||||
|
var max = _minMaxSize.maxSize; |
||||
|
|
||||
|
if (!_canResize) |
||||
|
max = min = _realSize; |
||||
|
|
||||
|
if (preResize.HasValue) |
||||
|
{ |
||||
|
var desired = preResize.Value; |
||||
|
max = new PixelSize(Math.Max(desired.Width, max.Width), Math.Max(desired.Height, max.Height)); |
||||
|
min = new PixelSize(Math.Min(desired.Width, min.Width), Math.Min(desired.Height, min.Height)); |
||||
|
} |
||||
|
|
||||
|
var hints = new XSizeHints |
||||
|
{ |
||||
|
min_width = min.Width, |
||||
|
min_height = min.Height |
||||
|
}; |
||||
|
hints.height_inc = hints.width_inc = 1; |
||||
|
var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; |
||||
|
// People might be passing double.MaxValue
|
||||
|
if (max.Width < 100000 && max.Height < 100000) |
||||
|
{ |
||||
|
hints.max_width = max.Width; |
||||
|
hints.max_height = max.Height; |
||||
|
flags |= XSizeHintsFlags.PMaxSize; |
||||
|
} |
||||
|
|
||||
|
hints.flags = (IntPtr)flags; |
||||
|
|
||||
|
XSetWMNormalHints(_x11.Display, _handle, ref hints); |
||||
|
} |
||||
|
|
||||
|
public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling); |
||||
|
|
||||
|
public double Scaling |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
lock (SyncRoot) |
||||
|
return _scaling; |
||||
|
|
||||
|
} |
||||
|
private set => _scaling = value; |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<object> Surfaces { get; } |
||||
|
public Action<RawInputEventArgs> Input { get; set; } |
||||
|
public Action<Rect> Paint { get; set; } |
||||
|
public Action<Size> Resized { get; set; } |
||||
|
//TODO
|
||||
|
public Action<double> ScalingChanged { get; set; } |
||||
|
public Action Deactivated { get; set; } |
||||
|
public Action Activated { get; set; } |
||||
|
public Func<bool> Closing { get; set; } |
||||
|
public Action<WindowState> WindowStateChanged { get; set; } |
||||
|
public Action Closed { get; set; } |
||||
|
public Action<Point> PositionChanged { get; set; } |
||||
|
|
||||
|
public IRenderer CreateRenderer(IRenderRoot root) => |
||||
|
new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>()); |
||||
|
|
||||
|
void OnEvent(XEvent ev) |
||||
|
{ |
||||
|
lock (SyncRoot) |
||||
|
OnEventSync(ev); |
||||
|
} |
||||
|
void OnEventSync(XEvent ev) |
||||
|
{ |
||||
|
if(XFilterEvent(ref ev, _handle)) |
||||
|
return; |
||||
|
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) |
||||
|
{ |
||||
|
if (!_triggeredExpose) |
||||
|
{ |
||||
|
_triggeredExpose = true; |
||||
|
Dispatcher.UIThread.Post(() => |
||||
|
{ |
||||
|
_triggeredExpose = false; |
||||
|
DoPaint(); |
||||
|
}, DispatcherPriority.Render); |
||||
|
} |
||||
|
} |
||||
|
else if (ev.type == XEventName.FocusIn) |
||||
|
{ |
||||
|
if (ActivateTransientChildIfNeeded()) |
||||
|
return; |
||||
|
Activated?.Invoke(); |
||||
|
} |
||||
|
else if (ev.type == XEventName.FocusOut) |
||||
|
Deactivated?.Invoke(); |
||||
|
else if (ev.type == XEventName.MotionNotify) |
||||
|
MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state); |
||||
|
else if (ev.type == XEventName.LeaveNotify) |
||||
|
MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state); |
||||
|
else if (ev.type == XEventName.PropertyNotify) |
||||
|
{ |
||||
|
OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0); |
||||
|
} |
||||
|
else if (ev.type == XEventName.ButtonPress) |
||||
|
{ |
||||
|
if (ActivateTransientChildIfNeeded()) |
||||
|
return; |
||||
|
if (ev.ButtonEvent.button < 4) |
||||
|
MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown |
||||
|
: ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown |
||||
|
: RawMouseEventType.RightButtonDown, ref 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)), ref ev); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
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, ref ev, ev.ButtonEvent.state); |
||||
|
} |
||||
|
else if (ev.type == XEventName.ConfigureNotify) |
||||
|
{ |
||||
|
if (ev.ConfigureEvent.window != _handle) |
||||
|
return; |
||||
|
var needEnqueue = (_configure == null); |
||||
|
_configure = ev.ConfigureEvent; |
||||
|
if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event) |
||||
|
_configurePoint = new Point(ev.ConfigureEvent.x, ev.ConfigureEvent.y); |
||||
|
else |
||||
|
{ |
||||
|
XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow, |
||||
|
0, 0, |
||||
|
out var tx, out var ty, out _); |
||||
|
_configurePoint = new Point(tx, ty); |
||||
|
} |
||||
|
if (needEnqueue) |
||||
|
Dispatcher.UIThread.Post(() => |
||||
|
{ |
||||
|
if (_configure == null) |
||||
|
return; |
||||
|
var cev = _configure.Value; |
||||
|
var npos = _configurePoint.Value; |
||||
|
_configure = null; |
||||
|
_configurePoint = null; |
||||
|
|
||||
|
var nsize = new PixelSize(cev.width, cev.height); |
||||
|
var changedSize = _realSize != nsize; |
||||
|
var changedPos = _position == null || npos != _position; |
||||
|
_realSize = nsize; |
||||
|
_position = npos; |
||||
|
bool updatedSizeViaScaling = false; |
||||
|
if (changedPos) |
||||
|
{ |
||||
|
PositionChanged?.Invoke(npos); |
||||
|
updatedSizeViaScaling = UpdateScaling(); |
||||
|
} |
||||
|
|
||||
|
if (changedSize && !updatedSizeViaScaling) |
||||
|
Resized?.Invoke(ClientSize); |
||||
|
|
||||
|
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); |
||||
|
}, DispatcherPriority.Layout); |
||||
|
XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height); |
||||
|
} |
||||
|
else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle) |
||||
|
{ |
||||
|
Cleanup(); |
||||
|
} |
||||
|
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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease) |
||||
|
{ |
||||
|
if (ActivateTransientChildIfNeeded()) |
||||
|
return; |
||||
|
var buffer = stackalloc byte[40]; |
||||
|
|
||||
|
var latinKeysym = XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, 0); |
||||
|
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), |
||||
|
ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, |
||||
|
X11KeyTransform.ConvertKey(latinKeysym), TranslateModifiers(ev.KeyEvent.state)), ref ev); |
||||
|
|
||||
|
if (ev.type == XEventName.KeyPress) |
||||
|
{ |
||||
|
var len = Xutf8LookupString(_xic, ref ev, buffer, 40, out _, out _); |
||||
|
if (len != 0) |
||||
|
{ |
||||
|
var text = Encoding.UTF8.GetString(buffer, len); |
||||
|
if (text.Length == 1) |
||||
|
{ |
||||
|
if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
|
||||
|
return; |
||||
|
} |
||||
|
ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), text), |
||||
|
ref ev); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private bool UpdateScaling() |
||||
|
{ |
||||
|
lock (SyncRoot) |
||||
|
{ |
||||
|
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) |
||||
|
.FirstOrDefault(m => m.Bounds.Contains(Position)); |
||||
|
var newScaling = monitor?.PixelDensity ?? Scaling; |
||||
|
if (Scaling != newScaling) |
||||
|
{ |
||||
|
Console.WriteLine( |
||||
|
$"Updating scaling from {Scaling} to {newScaling} as a response to position change to {Position}"); |
||||
|
var oldScaledSize = ClientSize; |
||||
|
Scaling = newScaling; |
||||
|
ScalingChanged?.Invoke(Scaling); |
||||
|
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize); |
||||
|
Resize(oldScaledSize, true); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private WindowState _lastWindowState; |
||||
|
public WindowState WindowState |
||||
|
{ |
||||
|
get => _lastWindowState; |
||||
|
set |
||||
|
{ |
||||
|
if(_lastWindowState == value) |
||||
|
return; |
||||
|
_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 _, out _, out var nitems, out _, |
||||
|
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) |
||||
|
{ |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
private bool _systemDecorations = true; |
||||
|
private bool _canResize = true; |
||||
|
private (Size minSize, Size maxSize) _scaledMinMaxSize; |
||||
|
private (PixelSize minSize, PixelSize maxSize) _minMaxSize; |
||||
|
private double _scaling = 1; |
||||
|
|
||||
|
void ScheduleInput(RawInputEventArgs args, ref XEvent xev) |
||||
|
{ |
||||
|
_x11.LastActivityTimestamp = xev.ButtonEvent.time; |
||||
|
ScheduleInput(args); |
||||
|
} |
||||
|
|
||||
|
public void ScheduleInput(RawInputEventArgs args) |
||||
|
{ |
||||
|
if (args is RawMouseEventArgs mouse) |
||||
|
mouse.Position = mouse.Position / Scaling; |
||||
|
if (args is RawDragEvent drag) |
||||
|
drag.Location = drag.Location / Scaling; |
||||
|
|
||||
|
_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, ref XEvent ev, XModifierMask mods) |
||||
|
{ |
||||
|
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, ref ev); |
||||
|
} |
||||
|
|
||||
|
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 IInputRoot InputRoot => _inputRoot; |
||||
|
|
||||
|
public void SetInputRoot(IInputRoot inputRoot) |
||||
|
{ |
||||
|
_inputRoot = inputRoot; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if (_handle != IntPtr.Zero) |
||||
|
{ |
||||
|
XDestroyWindow(_x11.Display, _handle); |
||||
|
Cleanup(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Cleanup() |
||||
|
{ |
||||
|
SetTransientParent(null, false); |
||||
|
if (_xic != IntPtr.Zero) |
||||
|
{ |
||||
|
XDestroyIC(_xic); |
||||
|
_xic = IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
if (_handle != IntPtr.Zero) |
||||
|
{ |
||||
|
XDestroyWindow(_x11.Display, _handle); |
||||
|
_platform.Windows.Remove(_handle); |
||||
|
_platform.XI2?.OnWindowDestroyed(_handle); |
||||
|
_handle = IntPtr.Zero; |
||||
|
Closed?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
if (_renderHandle != IntPtr.Zero) |
||||
|
{ |
||||
|
XDestroyWindow(_x11.Display, _renderHandle); |
||||
|
_renderHandle = IntPtr.Zero; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool ActivateTransientChildIfNeeded() |
||||
|
{ |
||||
|
if (_transientChildren.Count == 0) |
||||
|
return false; |
||||
|
var child = _transientChildren.First(); |
||||
|
if (!child.ActivateTransientChildIfNeeded()) |
||||
|
child.Activate(); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void SetTransientParent(X11Window window, bool informServer = true) |
||||
|
{ |
||||
|
_transientParent?._transientChildren.Remove(this); |
||||
|
_transientParent = window; |
||||
|
_transientParent?._transientChildren.Add(this); |
||||
|
if (informServer) |
||||
|
XSetTransientForHint(_x11.Display, _handle, _transientParent?._handle ?? IntPtr.Zero); |
||||
|
} |
||||
|
|
||||
|
public void Show() |
||||
|
{ |
||||
|
SetTransientParent(null); |
||||
|
ShowCore(); |
||||
|
} |
||||
|
|
||||
|
void ShowCore() |
||||
|
{ |
||||
|
XMapWindow(_x11.Display, _handle); |
||||
|
XFlush(_x11.Display); |
||||
|
} |
||||
|
|
||||
|
public void Hide() => XUnmapWindow(_x11.Display, _handle); |
||||
|
|
||||
|
|
||||
|
public Point PointToClient(Point point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); |
||||
|
|
||||
|
public Point PointToScreen(Point point) => new Point(point.X * Scaling + Position.X, point.Y * Scaling + Position.Y); |
||||
|
|
||||
|
public void SetSystemDecorations(bool enabled) |
||||
|
{ |
||||
|
_systemDecorations = enabled; |
||||
|
UpdateMotifHints(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void Resize(Size clientSize) => Resize(clientSize, false); |
||||
|
|
||||
|
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling)); |
||||
|
|
||||
|
void Resize(Size clientSize, bool force) |
||||
|
{ |
||||
|
if (!force && clientSize == ClientSize) |
||||
|
return; |
||||
|
|
||||
|
var needImmediatePopupResize = clientSize != ClientSize; |
||||
|
|
||||
|
var pixelSize = ToPixelSize(clientSize); |
||||
|
UpdateSizeHints(pixelSize); |
||||
|
XConfigureResizeWindow(_x11.Display, _handle, pixelSize); |
||||
|
XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize); |
||||
|
XFlush(_x11.Display); |
||||
|
|
||||
|
if (force || (_popup && needImmediatePopupResize)) |
||||
|
{ |
||||
|
_realSize = pixelSize; |
||||
|
Resized?.Invoke(ClientSize); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void CanResize(bool value) |
||||
|
{ |
||||
|
_canResize = value; |
||||
|
UpdateMotifHints(); |
||||
|
UpdateSizeHints(null); |
||||
|
} |
||||
|
|
||||
|
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; } |
||||
|
|
||||
|
public Point Position |
||||
|
{ |
||||
|
get => _position ?? default; |
||||
|
set |
||||
|
{ |
||||
|
var changes = new XWindowChanges |
||||
|
{ |
||||
|
x = (int)value.X, |
||||
|
y = (int)value.Y |
||||
|
}; |
||||
|
XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY, |
||||
|
ref changes); |
||||
|
XFlush(_x11.Display); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IMouseDevice MouseDevice => _mouse; |
||||
|
|
||||
|
public void Activate() |
||||
|
{ |
||||
|
if (_x11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) |
||||
|
{ |
||||
|
SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp, |
||||
|
IntPtr.Zero); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
XRaiseWindow(_x11.Display, _handle); |
||||
|
XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public IScreenImpl Screen => _platform.Screens; |
||||
|
|
||||
|
public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size / s.PixelDensity) |
||||
|
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); |
||||
|
|
||||
|
|
||||
|
void SendNetWMMessage(IntPtr message_type, IntPtr l0, |
||||
|
IntPtr? l1 = null, IntPtr? l2 = null, IntPtr? l3 = null, IntPtr? l4 = null) |
||||
|
{ |
||||
|
var xev = new XEvent |
||||
|
{ |
||||
|
ClientMessageEvent = |
||||
|
{ |
||||
|
type = XEventName.ClientMessage, |
||||
|
send_event = true, |
||||
|
window = _handle, |
||||
|
message_type = message_type, |
||||
|
format = 32, |
||||
|
ptr1 = l0, |
||||
|
ptr2 = l1 ?? IntPtr.Zero, |
||||
|
ptr3 = l2 ?? IntPtr.Zero, |
||||
|
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, new IntPtr(0)); |
||||
|
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) |
||||
|
{ |
||||
|
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) |
||||
|
{ |
||||
|
_scaledMinMaxSize = (minSize, maxSize); |
||||
|
var min = new PixelSize( |
||||
|
(int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling), |
||||
|
(int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling)); |
||||
|
|
||||
|
const int maxDim = 100000; |
||||
|
var max = new PixelSize( |
||||
|
(int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, minSize.Width * Scaling)), |
||||
|
(int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, minSize.Height * Scaling))); |
||||
|
|
||||
|
_minMaxSize = (min, max); |
||||
|
UpdateSizeHints(null); |
||||
|
} |
||||
|
|
||||
|
public void SetTopmost(bool value) |
||||
|
{ |
||||
|
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, |
||||
|
(IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero); |
||||
|
} |
||||
|
|
||||
|
public void ShowDialog(IWindowImpl parent) |
||||
|
{ |
||||
|
SetTransientParent((X11Window)parent); |
||||
|
ShowCore(); |
||||
|
} |
||||
|
|
||||
|
public void SetIcon(IWindowIconImpl icon) |
||||
|
{ |
||||
|
var data = ((X11IconData)icon).Data; |
||||
|
fixed (void* pdata = data) |
||||
|
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON, |
||||
|
new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace, |
||||
|
pdata, data.Length); |
||||
|
} |
||||
|
|
||||
|
public void ShowTaskbarIcon(bool value) |
||||
|
{ |
||||
|
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, |
||||
|
(IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,269 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Text; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using static Avalonia.X11.XLib; |
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
unsafe class XI2Manager |
||||
|
{ |
||||
|
private X11Info _x11; |
||||
|
private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>(); |
||||
|
class DeviceInfo |
||||
|
{ |
||||
|
public int Id { get; } |
||||
|
public XIValuatorClassInfo[] Valuators { get; private set; } |
||||
|
public XIScrollClassInfo[] Scrollers { get; private set; } |
||||
|
public DeviceInfo(XIDeviceInfo info) |
||||
|
{ |
||||
|
Id = info.Deviceid; |
||||
|
Update(info.Classes, info.NumClasses); |
||||
|
} |
||||
|
|
||||
|
public virtual void Update(XIAnyClassInfo** classes, int num) |
||||
|
{ |
||||
|
var valuators = new List<XIValuatorClassInfo>(); |
||||
|
var scrollers = new List<XIScrollClassInfo>(); |
||||
|
for (var c = 0; c < num; c++) |
||||
|
{ |
||||
|
if (classes[c]->Type == XiDeviceClass.XIValuatorClass) |
||||
|
valuators.Add(*((XIValuatorClassInfo**)classes)[c]); |
||||
|
if (classes[c]->Type == XiDeviceClass.XIScrollClass) |
||||
|
scrollers.Add(*((XIScrollClassInfo**)classes)[c]); |
||||
|
} |
||||
|
|
||||
|
Valuators = valuators.ToArray(); |
||||
|
Scrollers = scrollers.ToArray(); |
||||
|
} |
||||
|
|
||||
|
public void UpdateValuators(Dictionary<int, double> valuators) |
||||
|
{ |
||||
|
foreach (var v in valuators) |
||||
|
{ |
||||
|
if (Valuators.Length > v.Key) |
||||
|
Valuators[v.Key].Value = v.Value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class PointerDeviceInfo : DeviceInfo |
||||
|
{ |
||||
|
public PointerDeviceInfo(XIDeviceInfo info) : base(info) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool HasScroll(ParsedDeviceEvent ev) |
||||
|
{ |
||||
|
foreach (var val in ev.Valuators) |
||||
|
if (Scrollers.Any(s => s.Number == val.Key)) |
||||
|
return true; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public bool HasMotion(ParsedDeviceEvent ev) |
||||
|
{ |
||||
|
foreach (var val in ev.Valuators) |
||||
|
if (Scrollers.All(s => s.Number != val.Key)) |
||||
|
return true; |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private PointerDeviceInfo _pointerDevice; |
||||
|
private AvaloniaX11Platform _platform; |
||||
|
|
||||
|
public bool Init(AvaloniaX11Platform platform) |
||||
|
{ |
||||
|
_platform = platform; |
||||
|
_x11 = platform.Info; |
||||
|
var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display, |
||||
|
(int)XiPredefinedDeviceId.XIAllMasterDevices, out int num); |
||||
|
for (var c = 0; c < num; c++) |
||||
|
{ |
||||
|
if (devices[c].Use == XiDeviceType.XIMasterPointer) |
||||
|
{ |
||||
|
_pointerDevice = new PointerDeviceInfo(devices[c]); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
if(_pointerDevice == null) |
||||
|
return false; |
||||
|
/* |
||||
|
int mask = 0; |
||||
|
|
||||
|
XISetMask(ref mask, XiEventType.XI_DeviceChanged); |
||||
|
var emask = new XIEventMask |
||||
|
{ |
||||
|
Mask = &mask, |
||||
|
Deviceid = _pointerDevice.Id, |
||||
|
MaskLen = XiEventMaskLen |
||||
|
}; |
||||
|
|
||||
|
if (XISelectEvents(_x11.Display, _x11.RootWindow, &emask, 1) != Status.Success) |
||||
|
return false; |
||||
|
return true; |
||||
|
*/ |
||||
|
return XiSelectEvents(_x11.Display, _x11.RootWindow, new Dictionary<int, List<XiEventType>> |
||||
|
{ |
||||
|
[_pointerDevice.Id] = new List<XiEventType> |
||||
|
{ |
||||
|
XiEventType.XI_DeviceChanged |
||||
|
} |
||||
|
}) == Status.Success; |
||||
|
} |
||||
|
|
||||
|
public XEventMask AddWindow(IntPtr xid, IXI2Client window) |
||||
|
{ |
||||
|
_clients[xid] = window; |
||||
|
|
||||
|
XiSelectEvents(_x11.Display, xid, new Dictionary<int, List<XiEventType>> |
||||
|
{ |
||||
|
[_pointerDevice.Id] = new List<XiEventType>() |
||||
|
{ |
||||
|
XiEventType.XI_Motion, |
||||
|
XiEventType.XI_ButtonPress, |
||||
|
XiEventType.XI_ButtonRelease, |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// We are taking over mouse input handling from here
|
||||
|
return XEventMask.PointerMotionMask |
||||
|
| XEventMask.ButtonMotionMask |
||||
|
| XEventMask.Button1MotionMask |
||||
|
| XEventMask.Button2MotionMask |
||||
|
| XEventMask.Button3MotionMask |
||||
|
| XEventMask.Button4MotionMask |
||||
|
| XEventMask.Button5MotionMask |
||||
|
| XEventMask.ButtonPressMask |
||||
|
| XEventMask.ButtonReleaseMask; |
||||
|
} |
||||
|
|
||||
|
public void OnWindowDestroyed(IntPtr xid) => _clients.Remove(xid); |
||||
|
|
||||
|
public void OnEvent(XIEvent* xev) |
||||
|
{ |
||||
|
if (xev->evtype == XiEventType.XI_DeviceChanged) |
||||
|
{ |
||||
|
var changed = (XIDeviceChangedEvent*)xev; |
||||
|
_pointerDevice.Update(changed->Classes, changed->NumClasses); |
||||
|
} |
||||
|
|
||||
|
//TODO: this should only be used for non-touch devices
|
||||
|
if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion) |
||||
|
{ |
||||
|
var dev = (XIDeviceEvent*)xev; |
||||
|
if (_clients.TryGetValue(dev->EventWindow, out var client)) |
||||
|
OnDeviceEvent(client, new ParsedDeviceEvent(dev)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev) |
||||
|
{ |
||||
|
if (ev.Type == XiEventType.XI_Motion) |
||||
|
{ |
||||
|
Vector scrollDelta = default; |
||||
|
foreach (var v in ev.Valuators) |
||||
|
{ |
||||
|
foreach (var scroller in _pointerDevice.Scrollers) |
||||
|
{ |
||||
|
if (scroller.Number == v.Key) |
||||
|
{ |
||||
|
var old = _pointerDevice.Valuators[scroller.Number].Value; |
||||
|
// Value was zero after reset, ignore the event and use it as a reference next time
|
||||
|
if (old == 0) |
||||
|
continue; |
||||
|
var diff = (old - v.Value) / scroller.Increment; |
||||
|
if (scroller.ScrollType == XiScrollType.Horizontal) |
||||
|
scrollDelta = scrollDelta.WithX(scrollDelta.X + diff); |
||||
|
else |
||||
|
scrollDelta = scrollDelta.WithY(scrollDelta.Y + diff); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
if (scrollDelta != default) |
||||
|
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp, |
||||
|
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers)); |
||||
|
if (_pointerDevice.HasMotion(ev)) |
||||
|
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, |
||||
|
RawMouseEventType.Move, ev.Position, ev.Modifiers)); |
||||
|
} |
||||
|
|
||||
|
if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease) |
||||
|
{ |
||||
|
var down = ev.Type == XiEventType.XI_ButtonPress; |
||||
|
var type = |
||||
|
ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp) |
||||
|
: ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp) |
||||
|
: ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp) |
||||
|
: (RawMouseEventType?)null; |
||||
|
if (type.HasValue) |
||||
|
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot, |
||||
|
type.Value, ev.Position, ev.Modifiers)); |
||||
|
} |
||||
|
|
||||
|
_pointerDevice.UpdateValuators(ev.Valuators); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
unsafe class ParsedDeviceEvent |
||||
|
{ |
||||
|
public XiEventType Type { get; } |
||||
|
public InputModifiers Modifiers { get; } |
||||
|
public ulong Timestamp { get; } |
||||
|
public Point Position { get; } |
||||
|
public int Button { get; set; } |
||||
|
public Dictionary<int, double> Valuators { get; } |
||||
|
public ParsedDeviceEvent(XIDeviceEvent* ev) |
||||
|
{ |
||||
|
Type = ev->evtype; |
||||
|
Timestamp = (ulong)ev->time.ToInt64(); |
||||
|
var state = (XModifierMask)ev->mods.Effective; |
||||
|
if (state.HasFlag(XModifierMask.ShiftMask)) |
||||
|
Modifiers |= InputModifiers.Shift; |
||||
|
if (state.HasFlag(XModifierMask.ControlMask)) |
||||
|
Modifiers |= InputModifiers.Control; |
||||
|
if (state.HasFlag(XModifierMask.Mod1Mask)) |
||||
|
Modifiers |= InputModifiers.Alt; |
||||
|
if (state.HasFlag(XModifierMask.Mod4Mask)) |
||||
|
Modifiers |= InputModifiers.Windows; |
||||
|
|
||||
|
if (ev->buttons.MaskLen > 0) |
||||
|
{ |
||||
|
var buttons = ev->buttons.Mask; |
||||
|
if (XIMaskIsSet(buttons, 1)) |
||||
|
Modifiers |= InputModifiers.LeftMouseButton; |
||||
|
|
||||
|
if (XIMaskIsSet(buttons, 2)) |
||||
|
Modifiers |= InputModifiers.MiddleMouseButton; |
||||
|
|
||||
|
if (XIMaskIsSet(buttons, 3)) |
||||
|
Modifiers |= InputModifiers.RightMouseButton; |
||||
|
} |
||||
|
|
||||
|
Valuators = new Dictionary<int, double>(); |
||||
|
Position = new Point(ev->event_x, ev->event_y); |
||||
|
var values = ev->valuators.Values; |
||||
|
for (var c = 0; c < ev->valuators.MaskLen * 8; c++) |
||||
|
if (XIMaskIsSet(ev->valuators.Mask, c)) |
||||
|
Valuators[c] = *values++; |
||||
|
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease) |
||||
|
Button = ev->detail; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
interface IXI2Client |
||||
|
{ |
||||
|
IInputRoot InputRoot { get; } |
||||
|
void ScheduleInput(RawInputEventArgs args); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,283 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Bool = System.Boolean; |
||||
|
using Atom = System.IntPtr; |
||||
|
// ReSharper disable IdentifierTypo
|
||||
|
// ReSharper disable FieldCanBeMadeReadOnly.Global
|
||||
|
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
#pragma warning disable 649
|
||||
|
|
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIAddMasterInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public IntPtr Name; |
||||
|
public Bool SendCore; |
||||
|
public Bool Enable; |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIRemoveMasterInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Deviceid; |
||||
|
public int ReturnMode; /* AttachToMaster, Floating */ |
||||
|
public int ReturnPointer; |
||||
|
public int ReturnKeyboard; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIAttachSlaveInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Deviceid; |
||||
|
public int NewMaster; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIDetachSlaveInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Deviceid; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Explicit)] |
||||
|
struct XIAnyHierarchyChangeInfo |
||||
|
{ |
||||
|
[FieldOffset(0)] |
||||
|
public int type; /* must be first element */ |
||||
|
[FieldOffset(4)] |
||||
|
public XIAddMasterInfo add; |
||||
|
[FieldOffset(4)] |
||||
|
public XIRemoveMasterInfo remove; |
||||
|
[FieldOffset(4)] |
||||
|
public XIAttachSlaveInfo attach; |
||||
|
[FieldOffset(4)] |
||||
|
public XIDetachSlaveInfo detach; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIModifierState |
||||
|
{ |
||||
|
public int Base; |
||||
|
public int Latched; |
||||
|
public int Locked; |
||||
|
public int Effective; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIButtonState |
||||
|
{ |
||||
|
public int MaskLen; |
||||
|
public byte* Mask; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIValuatorState |
||||
|
{ |
||||
|
public int MaskLen; |
||||
|
public byte* Mask; |
||||
|
public double* Values; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIEventMask |
||||
|
{ |
||||
|
public int Deviceid; |
||||
|
public int MaskLen; |
||||
|
public int* Mask; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIAnyClassInfo |
||||
|
{ |
||||
|
public XiDeviceClass Type; |
||||
|
public int Sourceid; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIButtonClassInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Sourceid; |
||||
|
public int NumButtons; |
||||
|
public IntPtr* Labels; |
||||
|
public XIButtonState State; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIKeyClassInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Sourceid; |
||||
|
public int NumKeycodes; |
||||
|
public int* Keycodes; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIValuatorClassInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Sourceid; |
||||
|
public int Number; |
||||
|
public IntPtr Label; |
||||
|
public double Min; |
||||
|
public double Max; |
||||
|
public double Value; |
||||
|
public int Resolution; |
||||
|
public int Mode; |
||||
|
}; |
||||
|
|
||||
|
/* new in XI 2.1 */ |
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIScrollClassInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Sourceid; |
||||
|
public int Number; |
||||
|
public XiScrollType ScrollType; |
||||
|
public double Increment; |
||||
|
public int Flags; |
||||
|
}; |
||||
|
|
||||
|
enum XiScrollType |
||||
|
{ |
||||
|
Vertical = 1, |
||||
|
Horizontal = 2 |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XITouchClassInfo |
||||
|
{ |
||||
|
public int Type; |
||||
|
public int Sourceid; |
||||
|
public int Mode; |
||||
|
public int NumTouches; |
||||
|
}; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIDeviceInfo |
||||
|
{ |
||||
|
public int Deviceid; |
||||
|
public IntPtr Name; |
||||
|
public XiDeviceType Use; |
||||
|
public int Attachment; |
||||
|
public Bool Enabled; |
||||
|
public int NumClasses; |
||||
|
public XIAnyClassInfo** Classes; |
||||
|
} |
||||
|
|
||||
|
enum XiDeviceType |
||||
|
{ |
||||
|
XIMasterPointer = 1, |
||||
|
XIMasterKeyboard = 2, |
||||
|
XISlavePointer = 3, |
||||
|
XISlaveKeyboard = 4, |
||||
|
XIFloatingSlave = 5 |
||||
|
} |
||||
|
|
||||
|
enum XiPredefinedDeviceId : int |
||||
|
{ |
||||
|
XIAllDevices = 0, |
||||
|
XIAllMasterDevices = 1 |
||||
|
} |
||||
|
|
||||
|
enum XiDeviceClass |
||||
|
{ |
||||
|
XIKeyClass = 0, |
||||
|
XIButtonClass = 1, |
||||
|
XIValuatorClass = 2, |
||||
|
XIScrollClass = 3, |
||||
|
XITouchClass = 8, |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIDeviceChangedEvent |
||||
|
{ |
||||
|
public int Type; /* GenericEvent */ |
||||
|
public ulong Serial; /* # of last request processed by server */ |
||||
|
public Bool SendEvent; /* true if this came from a SendEvent request */ |
||||
|
public IntPtr Display; /* Display the event was read from */ |
||||
|
public int Extension; /* XI extension offset */ |
||||
|
public int Evtype; /* XI_DeviceChanged */ |
||||
|
public IntPtr Time; |
||||
|
public int Deviceid; /* id of the device that changed */ |
||||
|
public int Sourceid; /* Source for the new classes. */ |
||||
|
public int Reason; /* Reason for the change */ |
||||
|
public int NumClasses; |
||||
|
public XIAnyClassInfo** Classes; /* same as in XIDeviceInfo */ |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
struct XIDeviceEvent |
||||
|
{ |
||||
|
public XEventName type; /* GenericEvent */ |
||||
|
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 int extension; /* XI extension offset */ |
||||
|
public XiEventType evtype; |
||||
|
public IntPtr time; |
||||
|
public int deviceid; |
||||
|
public int sourceid; |
||||
|
public int detail; |
||||
|
public IntPtr RootWindow; |
||||
|
public IntPtr EventWindow; |
||||
|
public IntPtr ChildWindow; |
||||
|
public double root_x; |
||||
|
public double root_y; |
||||
|
public double event_x; |
||||
|
public double event_y; |
||||
|
public int flags; |
||||
|
public XIButtonState buttons; |
||||
|
public XIValuatorState valuators; |
||||
|
public XIModifierState mods; |
||||
|
public XIModifierState group; |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
unsafe struct XIEvent |
||||
|
{ |
||||
|
public int type; /* GenericEvent */ |
||||
|
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 int extension; /* XI extension offset */ |
||||
|
public XiEventType evtype; |
||||
|
public IntPtr time; |
||||
|
} |
||||
|
|
||||
|
enum XiEventType |
||||
|
{ |
||||
|
XI_DeviceChanged = 1, |
||||
|
XI_KeyPress = 2, |
||||
|
XI_KeyRelease = 3, |
||||
|
XI_ButtonPress = 4, |
||||
|
XI_ButtonRelease = 5, |
||||
|
XI_Motion = 6, |
||||
|
XI_Enter = 7, |
||||
|
XI_Leave = 8, |
||||
|
XI_FocusIn = 9, |
||||
|
XI_FocusOut = 10, |
||||
|
XI_HierarchyChanged = 11, |
||||
|
XI_PropertyEvent = 12, |
||||
|
XI_RawKeyPress = 13, |
||||
|
XI_RawKeyRelease = 14, |
||||
|
XI_RawButtonPress = 15, |
||||
|
XI_RawButtonRelease = 16, |
||||
|
XI_RawMotion = 17, |
||||
|
XI_TouchBegin = 18 /* XI 2.2 */, |
||||
|
XI_TouchUpdate = 19, |
||||
|
XI_TouchEnd = 20, |
||||
|
XI_TouchOwnership = 21, |
||||
|
XI_RawTouchBegin = 22, |
||||
|
XI_RawTouchUpdate = 23, |
||||
|
XI_RawTouchEnd = 24, |
||||
|
XI_BarrierHit = 25 /* XI 2.3 */, |
||||
|
XI_BarrierLeave = 26, |
||||
|
XI_LASTEVENT = XI_BarrierLeave, |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,632 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
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 |
||||
|
{ |
||||
|
internal unsafe static class XLib |
||||
|
{ |
||||
|
const string libX11 = "libX11.so.6"; |
||||
|
const string libX11Randr = "libXrandr.so.2"; |
||||
|
const string libX11Ext = "libXext.so.6"; |
||||
|
const string libXInput = "libXi.so.6"; |
||||
|
|
||||
|
[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); |
||||
|
|
||||
|
public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, PixelSize size) |
||||
|
=> XConfigureResizeWindow(display, window, size.Width, size.Height); |
||||
|
|
||||
|
public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, int width, int height) |
||||
|
{ |
||||
|
var changes = new XWindowChanges |
||||
|
{ |
||||
|
width = width, |
||||
|
height = height |
||||
|
}; |
||||
|
|
||||
|
return XConfigureWindow(display, window, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth, |
||||
|
ref changes); |
||||
|
} |
||||
|
|
||||
|
[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 IntPtr XGetAtomName(IntPtr display, IntPtr atom); |
||||
|
|
||||
|
public static string GetAtomName(IntPtr display, IntPtr atom) |
||||
|
{ |
||||
|
var ptr = XGetAtomName(display, atom); |
||||
|
if (ptr == IntPtr.Zero) |
||||
|
return null; |
||||
|
var s = Marshal.PtrToStringAnsi(ptr); |
||||
|
XFree(ptr); |
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
[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 parent); |
||||
|
|
||||
|
[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, void* 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, out 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)] |
||||
|
public static extern void XMatchVisualInfo(IntPtr display, int screen, int depth, int klass, out XVisualInfo info); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern IntPtr XLockDisplay(IntPtr display); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern IntPtr XUnlockDisplay(IntPtr display); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern int XInitImage(ref XImage image); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern int XDestroyImage(ref XImage image); |
||||
|
|
||||
|
[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)] |
||||
|
public static extern IntPtr XCreateColormap(IntPtr display, IntPtr window, IntPtr visual, int create); |
||||
|
|
||||
|
public enum XLookupStatus |
||||
|
{ |
||||
|
XBufferOverflow = -1, |
||||
|
XLookupNone = 1, |
||||
|
XLookupChars = 2, |
||||
|
XLookupKeySym = 3, |
||||
|
XLookupBoth = 4 |
||||
|
} |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern unsafe int XLookupString(ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern unsafe IntPtr XKeycodeToKeysym(IntPtr display, int keycode, int index); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern unsafe IntPtr XSetLocaleModifiers(string modifiers); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern IntPtr XOpenIM (IntPtr display, IntPtr rdb, IntPtr res_name, IntPtr res_class); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, IntPtr terminator); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, string name3, IntPtr value3, IntPtr terminator); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern void XCloseIM (IntPtr xim); |
||||
|
|
||||
|
[DllImport (libX11)] |
||||
|
public static extern void XDestroyIC (IntPtr xic); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern bool XQueryExtension(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string name, |
||||
|
out int majorOpcode, out int firstEvent, out int firstError); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern bool XGetEventData(IntPtr display, void* cookie); |
||||
|
|
||||
|
[DllImport(libX11)] |
||||
|
public static extern void XFreeEventData(IntPtr display, void* cookie); |
||||
|
|
||||
|
[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); |
||||
|
|
||||
|
[DllImport(libXInput)] |
||||
|
public static extern Status XIQueryVersion(IntPtr dpy, ref int major, ref int minor); |
||||
|
|
||||
|
[DllImport(libXInput)] |
||||
|
public static extern IntPtr XIQueryDevice(IntPtr dpy, int deviceid, out int ndevices_return); |
||||
|
|
||||
|
[DllImport(libXInput)] |
||||
|
public static extern void XIFreeDeviceInfo(XIDeviceInfo* info); |
||||
|
|
||||
|
public static void XISetMask(ref int mask, XiEventType ev) |
||||
|
{ |
||||
|
mask |= (1 << (int)ev); |
||||
|
} |
||||
|
|
||||
|
public static int XiEventMaskLen { get; } = 4; |
||||
|
|
||||
|
public static bool XIMaskIsSet(void* ptr, int shift) => |
||||
|
(((byte*)(ptr))[(shift) >> 3] & (1 << (shift & 7))) != 0; |
||||
|
|
||||
|
[DllImport(libXInput)] |
||||
|
public static extern Status XISelectEvents( |
||||
|
IntPtr dpy, |
||||
|
IntPtr win, |
||||
|
XIEventMask* masks, |
||||
|
int num_masks |
||||
|
); |
||||
|
|
||||
|
public static Status XiSelectEvents(IntPtr display, IntPtr window, Dictionary<int, List<XiEventType>> devices) |
||||
|
{ |
||||
|
var masks = stackalloc int[devices.Count]; |
||||
|
var emasks = stackalloc XIEventMask[devices.Count]; |
||||
|
int c = 0; |
||||
|
foreach (var d in devices) |
||||
|
{ |
||||
|
foreach (var ev in d.Value) |
||||
|
XISetMask(ref masks[c], ev); |
||||
|
emasks[c] = new XIEventMask |
||||
|
{ |
||||
|
Mask = &masks[c], |
||||
|
Deviceid = d.Key, |
||||
|
MaskLen = XiEventMaskLen |
||||
|
}; |
||||
|
c++; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
return XISelectEvents(display, window, emasks, devices.Count); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public struct XGeometry |
||||
|
{ |
||||
|
public IntPtr root; |
||||
|
public int x; |
||||
|
public int y; |
||||
|
public int width; |
||||
|
public int height; |
||||
|
public int bw; |
||||
|
public int d; |
||||
|
} |
||||
|
|
||||
|
public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo) |
||||
|
{ |
||||
|
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 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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static IntPtr CreateEventWindow(AvaloniaX11Platform plat, Action<XEvent> handler) |
||||
|
{ |
||||
|
var win = XCreateSimpleWindow(plat.Display, plat.Info.DefaultRootWindow, |
||||
|
0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero); |
||||
|
plat.Windows[win] = handler; |
||||
|
return win; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
using Avalonia.Gtk3.Interop; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Platform.Interop; |
||||
|
|
||||
|
namespace Avalonia.Gtk3 |
||||
|
{ |
||||
|
public class Gtk3ForeignX11SystemDialog : ISystemDialogImpl |
||||
|
{ |
||||
|
private Task<bool> _initialized; |
||||
|
private SystemDialogBase _inner = new SystemDialogBase(); |
||||
|
|
||||
|
|
||||
|
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) |
||||
|
{ |
||||
|
await EnsureInitialized(); |
||||
|
var xid = parent.Handle.Handle; |
||||
|
return await await RunOnGtkThread( |
||||
|
() => _inner.ShowFileDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid))); |
||||
|
} |
||||
|
|
||||
|
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) |
||||
|
{ |
||||
|
await EnsureInitialized(); |
||||
|
var xid = parent.Handle.Handle; |
||||
|
return await await RunOnGtkThread( |
||||
|
() => _inner.ShowFolderDialogAsync(dialog, GtkWindow.Null, chooser => UpdateParent(chooser, xid))); |
||||
|
} |
||||
|
|
||||
|
void UpdateParent(GtkFileChooser chooser, IntPtr xid) |
||||
|
{ |
||||
|
Native.GtkWidgetRealize(chooser); |
||||
|
var window = Native.GtkWidgetGetWindow(chooser); |
||||
|
var parent = Native.GdkWindowForeignNewForDisplay(GdkDisplay, xid); |
||||
|
if (window != IntPtr.Zero && parent != IntPtr.Zero) |
||||
|
Native.GdkWindowSetTransientFor(window, parent); |
||||
|
} |
||||
|
|
||||
|
async Task EnsureInitialized() |
||||
|
{ |
||||
|
if (_initialized == null) |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<bool>(); |
||||
|
_initialized = tcs.Task; |
||||
|
new Thread(() => GtkThread(tcs)) |
||||
|
{ |
||||
|
IsBackground = true |
||||
|
}.Start(); |
||||
|
} |
||||
|
|
||||
|
if (!(await _initialized)) |
||||
|
throw new Exception("Unable to initialize GTK on separate thread"); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
Task<T> RunOnGtkThread<T>(Func<T> action) |
||||
|
{ |
||||
|
var tcs = new TaskCompletionSource<T>(); |
||||
|
GlibTimeout.Add(0, 0, () => |
||||
|
{ |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
tcs.SetResult(action()); |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
tcs.TrySetException(e); |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
}); |
||||
|
return tcs.Task; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void GtkThread(TaskCompletionSource<bool> tcs) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
X11.XInitThreads(); |
||||
|
}catch{} |
||||
|
Resolver.Resolve(); |
||||
|
if (Native.GdkWindowForeignNewForDisplay == null) |
||||
|
throw new Exception("gdk_x11_window_foreign_new_for_display is not found in your libgdk-3.so"); |
||||
|
using (var backends = new Utf8Buffer("x11")) |
||||
|
Native.GdkSetAllowedBackends?.Invoke(backends); |
||||
|
if (!Native.GtkInitCheck(0, IntPtr.Zero)) |
||||
|
{ |
||||
|
tcs.SetResult(false); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid().ToString("N")}")) |
||||
|
App = Native.GtkApplicationNew(utf, 0); |
||||
|
if (App == IntPtr.Zero) |
||||
|
{ |
||||
|
tcs.SetResult(false); |
||||
|
return; |
||||
|
} |
||||
|
GdkDisplay = Native.GdkGetDefaultDisplay(); |
||||
|
tcs.SetResult(true); |
||||
|
while (true) |
||||
|
Native.GtkMainIteration(); |
||||
|
} |
||||
|
|
||||
|
private IntPtr GdkDisplay { get; set; } |
||||
|
private IntPtr App { get; set; } |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue