Browse Source

Implement IPlatformLifetimeEvents for X11/Linux

pull/6883/head
Jumar Macato 4 years ago
parent
commit
1ae2e360b4
No known key found for this signature in database GPG Key ID: 85076C4D9D3155A3
  1. 60
      src/Avalonia.X11/ICELib.cs
  2. 134
      src/Avalonia.X11/SMLib.cs
  3. 3
      src/Avalonia.X11/X11Platform.cs
  4. 246
      src/Avalonia.X11/X11PlatformLifetimeEvents.cs

60
src/Avalonia.X11/ICELib.cs

@ -0,0 +1,60 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.X11
{
internal static class ICELib
{
private const string LibIce = "libICE.so.6";
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern int IceAddConnectionWatch(
IntPtr watchProc,
IntPtr clientData
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern void IceRemoveConnectionWatch(
IntPtr watchProc,
IntPtr clientData
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern IceProcessMessagesStatus IceProcessMessages(
IntPtr iceConn,
out IntPtr replyWait,
out bool replyReadyRet
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr IceSetErrorHandler(
IntPtr handler
);
[DllImport(LibIce, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr IceSetIOErrorHandler(
IntPtr handler
);
public enum IceProcessMessagesStatus
{
IceProcessMessagesIoError = 1
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void IceErrorHandler(
IntPtr iceConn,
bool swap,
int offendingMinorOpcode,
ulong offendingSequence,
int errorClass,
int severity,
IntPtr values
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void IceIOErrorHandler(
IntPtr iceConn
);
}
}

134
src/Avalonia.X11/SMLib.cs

@ -0,0 +1,134 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.X11
{
internal static unsafe class SMLib
{
private const string LibSm = "libSM.so.6";
[DllImport(LibSm, CharSet = CharSet.Ansi)]
public static extern IntPtr SmcOpenConnection(
[MarshalAs(UnmanagedType.LPWStr)] string networkId,
IntPtr content,
int xsmpMajorRev,
int xsmpMinorRev,
ulong mask,
ref SmcCallbacks callbacks,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
out string previousId,
[MarshalAs(UnmanagedType.LPWStr)] [Out]
out string clientIdRet,
int errorLength,
[Out] char[] errorStringRet);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern int SmcCloseConnection(
IntPtr smcConn,
int count,
string[] reasonMsgs
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern void SmcSaveYourselfDone(
IntPtr smcConn,
bool success
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern int SmcInteractRequest(
IntPtr smcConn,
SmDialogValue dialogType,
IntPtr interactProc,
IntPtr clientData
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern void SmcInteractDone(
IntPtr smcConn,
bool success
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SmcGetIceConnection(
IntPtr smcConn
);
[DllImport(LibSm, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SmcSetErrorHandler(
IntPtr handler
);
public enum SmDialogValue
{
SmDialogError = 0
}
[StructLayout(LayoutKind.Sequential)]
public struct SmcCallbacks
{
public IntPtr SaveYourself;
private readonly IntPtr Unused0;
public IntPtr Die;
private readonly IntPtr Unused1;
public IntPtr SaveComplete;
private readonly IntPtr Unused2;
public IntPtr ShutdownCancelled;
private readonly IntPtr Unused3;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void IceWatchProc(
IntPtr iceConn,
IntPtr clientData,
bool opening,
IntPtr* watchData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcDieProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcInteractProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcSaveCompleteProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcSaveYourselfProc(
IntPtr smcConn,
IntPtr clientData,
int saveType,
bool shutdown,
int interactStyle,
bool fast
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcShutdownCancelledProc(
IntPtr smcConn,
IntPtr clientData
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SmcErrorHandler(
IntPtr smcConn,
bool swap,
int offendingMinorOpcode,
ulong offendingSequence,
int errorClass,
int severity,
IntPtr values
);
}
}

3
src/Avalonia.X11/X11Platform.cs

@ -80,7 +80,8 @@ namespace Avalonia.X11
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider());
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(new X11PlatformLifetimeEvents());
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);

246
src/Avalonia.X11/X11PlatformLifetimeEvents.cs

@ -0,0 +1,246 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Logging;
using Avalonia.Platform;
namespace Avalonia.X11
{
public unsafe class X11PlatformLifetimeEvents : IDisposable, IPlatformLifetimeEventsImpl
{
private const ulong SmcSaveYourselfProcMask = 1L;
private const ulong SmcDieProcMask = 2L;
private const ulong SmcSaveCompleteProcMask = 4L;
private const ulong SmcShutdownCancelledProcMask = 8L;
private static readonly ConcurrentDictionary<IntPtr, X11PlatformLifetimeEvents> s_nativeToManagedMapper = new ConcurrentDictionary<IntPtr, X11PlatformLifetimeEvents>();
private static readonly SMLib.SmcSaveYourselfProc s_saveYourselfProcDelegate = SmcSaveYourselfHandler;
private static readonly SMLib.SmcDieProc s_dieDelegate = SmcDieHandler;
private static readonly SMLib.SmcShutdownCancelledProc
s_shutdownCancelledDelegate = SmcShutdownCancelledHandler;
private static readonly SMLib.SmcSaveCompleteProc s_saveCompleteDelegate = SmcSaveCompleteHandler;
private static readonly SMLib.SmcInteractProc s_smcInteractDelegate = StaticInteractHandler;
private static readonly SMLib.SmcErrorHandler s_smcErrorHandlerDelegate = StaticErrorHandler;
private static readonly ICELib.IceErrorHandler s_iceErrorHandlerDelegate = StaticErrorHandler;
private static readonly ICELib.IceIOErrorHandler s_iceIoErrorHandlerDelegate = StaticIceIOErrorHandler;
private static readonly SMLib.IceWatchProc s_iceWatchProcDelegate = IceWatchHandler;
private static SMLib.SmcCallbacks s_callbacks = new SMLib.SmcCallbacks()
{
ShutdownCancelled = Marshal.GetFunctionPointerForDelegate(s_shutdownCancelledDelegate),
Die = Marshal.GetFunctionPointerForDelegate(s_dieDelegate),
SaveYourself = Marshal.GetFunctionPointerForDelegate(s_saveYourselfProcDelegate),
SaveComplete = Marshal.GetFunctionPointerForDelegate(s_saveCompleteDelegate)
};
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private readonly IntPtr _currentIceConn;
private readonly IntPtr _currentSmcConn;
private bool _saveYourselfPhase;
public X11PlatformLifetimeEvents()
{
if (ICELib.IceAddConnectionWatch(
Marshal.GetFunctionPointerForDelegate(s_iceWatchProcDelegate),
IntPtr.Zero) == 0)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib was unable to add an ICE connection watcher.");
return;
}
var errorBuf = new char[255];
var smcConn = SMLib.SmcOpenConnection(null!,
IntPtr.Zero, 1, 0,
SmcSaveYourselfProcMask |
SmcSaveCompleteProcMask |
SmcShutdownCancelledProcMask |
SmcDieProcMask,
ref s_callbacks,
out _,
out _,
errorBuf.Length,
errorBuf);
if (smcConn == IntPtr.Zero)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
$"SMLib/ICELib reported a new error: {new string(errorBuf)}");
return;
}
if (!s_nativeToManagedMapper.TryAdd(smcConn, this))
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib was unable to add this instance to the native to managed map.");
return;
}
_ = SMLib.SmcSetErrorHandler(Marshal.GetFunctionPointerForDelegate(s_smcErrorHandlerDelegate));
_ = ICELib.IceSetErrorHandler(Marshal.GetFunctionPointerForDelegate(s_iceErrorHandlerDelegate));
_ = ICELib.IceSetIOErrorHandler(Marshal.GetFunctionPointerForDelegate(s_iceIoErrorHandlerDelegate));
_currentSmcConn = smcConn;
_currentIceConn = SMLib.SmcGetIceConnection(smcConn);
Task.Run(() =>
{
var token = _cancellationTokenSource.Token;
while (!token.IsCancellationRequested) HandleRequests();
}, _cancellationTokenSource.Token);
}
public void Dispose()
{
if (_currentSmcConn == IntPtr.Zero) return;
s_nativeToManagedMapper.TryRemove(_currentSmcConn, out _);
_ = SMLib.SmcCloseConnection(_currentSmcConn, 1,
new[] { $"{nameof(X11PlatformLifetimeEvents)} was disposed in managed code." });
}
private static void SmcSaveCompleteHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.SaveCompleteHandler();
}
private static X11PlatformLifetimeEvents? GetInstance(IntPtr smcConn)
{
return s_nativeToManagedMapper.TryGetValue(smcConn, out var instance) ? instance : null;
}
private static void SmcShutdownCancelledHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.ShutdownCancelledHandler();
}
private static void SmcDieHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.DieHandler();
}
private static void SmcSaveYourselfHandler(IntPtr smcConn, IntPtr clientData, int saveType,
bool shutdown, int interactStyle, bool fast)
{
GetInstance(smcConn)?.SaveYourselfHandler(smcConn, clientData, shutdown, fast);
}
private static void StaticInteractHandler(IntPtr smcConn, IntPtr clientData)
{
GetInstance(smcConn)?.InteractHandler(smcConn);
}
private static void StaticIceIOErrorHandler(IntPtr iceConn)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(null,
"ICELib reported an unknown IO Error.");
}
private static void StaticErrorHandler(IntPtr smcConn, bool swap, int offendingMinorOpcode,
ulong offendingSequence, int errorClass, int severity, IntPtr values)
{
GetInstance(smcConn)
?.ErrorHandler(swap, offendingMinorOpcode, offendingSequence, errorClass, severity, values);
}
// ReSharper disable UnusedParameter.Local
private void ErrorHandler(bool swap, int offendingMinorOpcode, ulong offendingSequence, int errorClass,
int severity, IntPtr values)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib reported an error:" +
$" severity {severity:X}" +
$" mOpcode {offendingMinorOpcode:X}" +
$" mSeq {offendingSequence:X}" +
$" errClass {errorClass:X}.");
}
private void HandleRequests()
{
if (ICELib.IceProcessMessages(_currentIceConn, out _, out _) ==
ICELib.IceProcessMessagesStatus.IceProcessMessagesIoError)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,
"SMLib lost its underlying ICE connection.");
Dispose();
}
}
private void SaveCompleteHandler()
{
_saveYourselfPhase = false;
}
private void ShutdownCancelledHandler()
{
if (_saveYourselfPhase)
SMLib.SmcSaveYourselfDone(_currentSmcConn, true);
_saveYourselfPhase = false;
}
private void DieHandler()
{
Dispose();
}
private void SaveYourselfHandler(IntPtr smcConn, IntPtr clientData, bool shutdown, bool fast)
{
if (_saveYourselfPhase)
SMLib.SmcSaveYourselfDone(smcConn, true);
_saveYourselfPhase = true;
if (shutdown && !fast)
{
var _ = SMLib.SmcInteractRequest(smcConn, SMLib.SmDialogValue.SmDialogError,
Marshal.GetFunctionPointerForDelegate(s_smcInteractDelegate),
clientData);
}
else
{
SMLib.SmcSaveYourselfDone(smcConn, true);
_saveYourselfPhase = false;
}
}
private void InteractHandler(IntPtr smcConn)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (ShutdownRequested is null)
return;
var e = new ShutdownRequestedEventArgs();
ShutdownRequested(this, e);
var shutdownCancelled = e.Cancel;
SMLib.SmcInteractDone(smcConn, shutdownCancelled);
if (shutdownCancelled)
return;
_saveYourselfPhase = false;
SMLib.SmcSaveYourselfDone(smcConn, true);
}
private static void IceWatchHandler(IntPtr iceConn, IntPtr clientData, bool opening, IntPtr* watchData)
{
if (!opening) return;
ICELib.IceRemoveConnectionWatch(Marshal.GetFunctionPointerForDelegate(s_iceWatchProcDelegate),
IntPtr.Zero);
}
public event EventHandler<ShutdownRequestedEventArgs> ShutdownRequested = null!;
}
}
Loading…
Cancel
Save