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"> |
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0</TargetFrameworks> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="../../src/Windows/Avalonia.Win32/Avalonia.Win32.csproj" /> |
|||
<ProjectReference Include="../../src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.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="../../packages/Avalonia/Avalonia.csproj" /> |
|||
<ProjectReference Include="../Avalonia.X11/Avalonia.X11.csproj" /> |
|||
</ItemGroup> |
|||
</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