4 changed files with 442 additions and 1 deletions
@ -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 |
|||
); |
|||
} |
|||
} |
|||
@ -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 |
|||
); |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue