mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-01 16:59:35 +01:00
Split more WindowDriver classes and #nullable enable.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// TODO: #nullable enable
|
||||
#nullable enable
|
||||
//
|
||||
// WindowsDriver.cs: Windows specific driver
|
||||
//
|
||||
@@ -9,14 +9,13 @@
|
||||
// 2) The values provided during Init (and the first WindowsConsole.EventType.WindowBufferSize) are not correct.
|
||||
//
|
||||
// If HACK_CHECK_WINCHANGED is defined then we ignore WindowsConsole.EventType.WindowBufferSize events
|
||||
// and instead check the console size every 500ms in a thread in WidowsMainLoop.
|
||||
// As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using
|
||||
// and instead check the console size every 500ms in a thread in WidowsMainLoop.
|
||||
// As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using
|
||||
// the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is
|
||||
// still incorrect so we still need this hack.
|
||||
|
||||
//#define HACK_CHECK_WINCHANGED
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -35,7 +34,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
private bool _isOneFingerDoubleClicked;
|
||||
|
||||
private WindowsConsole.ButtonState? _lastMouseButtonPressed;
|
||||
private WindowsMainLoop _mainLoopDriver;
|
||||
private WindowsMainLoop? _mainLoopDriver;
|
||||
private WindowsConsole.ExtendedCharInfo [] _outputBuffer;
|
||||
private Point? _point;
|
||||
private Point _pointMove;
|
||||
@@ -45,7 +44,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
{
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
{
|
||||
WinConsole = new WindowsConsole ();
|
||||
WinConsole = new ();
|
||||
|
||||
// otherwise we're probably running in unit tests
|
||||
Clipboard = new WindowsClipboard ();
|
||||
@@ -68,7 +67,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
|
||||
public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal);
|
||||
|
||||
public WindowsConsole WinConsole { get; private set; }
|
||||
public WindowsConsole? WinConsole { get; private set; }
|
||||
|
||||
public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
|
||||
{
|
||||
@@ -202,7 +201,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
|
||||
public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
|
||||
{
|
||||
if (_mainLoopDriver is null)
|
||||
{
|
||||
@@ -242,12 +241,12 @@ internal class WindowsDriver : ConsoleDriver
|
||||
{
|
||||
_mainLoopDriver._forceRead = false;
|
||||
|
||||
if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request))
|
||||
if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request))
|
||||
{
|
||||
if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0
|
||||
&& string.IsNullOrEmpty (request.AnsiRequest.Response))
|
||||
{
|
||||
lock (request!.AnsiRequest._responseLock)
|
||||
lock (request.AnsiRequest._responseLock)
|
||||
{
|
||||
// Bad request or no response at all
|
||||
_mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _);
|
||||
@@ -404,7 +403,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
|
||||
for (var row = 0; row < Rows; row++)
|
||||
{
|
||||
if (!_dirtyLines [row])
|
||||
if (!_dirtyLines! [row])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -414,7 +413,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
for (var col = 0; col < Cols; col++)
|
||||
{
|
||||
int position = row * Cols + col;
|
||||
_outputBuffer [position].Attribute = Contents [row, col].Attribute.GetValueOrDefault ();
|
||||
_outputBuffer [position].Attribute = Contents! [row, col].Attribute.GetValueOrDefault ();
|
||||
|
||||
if (Contents [row, col].IsDirty == false)
|
||||
{
|
||||
@@ -504,7 +503,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
{
|
||||
// BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init.
|
||||
// Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED
|
||||
Size winSize = WinConsole.GetConsoleOutputWindow (out Point pos);
|
||||
Size winSize = WinConsole.GetConsoleOutputWindow (out Point _);
|
||||
Cols = winSize.Width;
|
||||
Rows = winSize.Height;
|
||||
}
|
||||
@@ -592,7 +591,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
case WindowsConsole.EventType.Mouse:
|
||||
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
|
||||
|
||||
if (me is null || me.Flags == MouseFlags.None)
|
||||
if (me.Flags == MouseFlags.None)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -717,7 +716,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
if (mapResult == 0)
|
||||
{
|
||||
// There is no mapping - this should not happen
|
||||
Debug.Assert (mapResult != 0, $@"Unable to map the virtual key code {keyInfo.Key}.");
|
||||
Debug.Assert (true, $@"Unable to map the virtual key code {keyInfo.Key}.");
|
||||
|
||||
return KeyCode.Null;
|
||||
}
|
||||
@@ -727,13 +726,13 @@ internal class WindowsDriver : ConsoleDriver
|
||||
|
||||
if (keyInfo.KeyChar == 0)
|
||||
{
|
||||
// If the keyChar is 0, keyInfo.Key value is not a printable character.
|
||||
// If the keyChar is 0, keyInfo.Key value is not a printable character.
|
||||
|
||||
// Dead keys (diacritics) are indicated by setting the top bit of the return value.
|
||||
// Dead keys (diacritics) are indicated by setting the top bit of the return value.
|
||||
if ((mapResult & 0x80000000) != 0)
|
||||
{
|
||||
// Dead key (e.g. Oem2 '~'/'^' on POR keyboard)
|
||||
// Option 1: Throw it out.
|
||||
// Option 1: Throw it out.
|
||||
// - Apps will never see the dead keys
|
||||
// - If user presses a key that can be combined with the dead key ('a'), the right thing happens (app will see '<27>').
|
||||
// - NOTE: With Dead Keys, KeyDown != KeyUp. The KeyUp event will have just the base char ('a').
|
||||
@@ -754,7 +753,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
if (keyInfo.Modifiers != 0)
|
||||
{
|
||||
// These Oem keys have well-defined chars. We ensure the representative char is used.
|
||||
// If we don't do this, then on some keyboard layouts the wrong char is
|
||||
// If we don't do this, then on some keyboard layouts the wrong char is
|
||||
// returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
|
||||
// for key persistence ("Ctrl++" vs. "Ctrl+=").
|
||||
mappedChar = keyInfo.Key switch
|
||||
@@ -925,25 +924,25 @@ internal class WindowsDriver : ConsoleDriver
|
||||
{
|
||||
// When a user presses-and-holds, start generating pressed events every `startDelay`
|
||||
// After `iterationsUntilFast` iterations, speed them up to `fastDelay` ms
|
||||
const int startDelay = 500;
|
||||
const int iterationsUntilFast = 4;
|
||||
const int fastDelay = 50;
|
||||
const int START_DELAY = 500;
|
||||
const int ITERATIONS_UNTIL_FAST = 4;
|
||||
const int FAST_DELAY = 50;
|
||||
|
||||
int iterations = 0;
|
||||
int delay = startDelay;
|
||||
int delay = START_DELAY;
|
||||
while (_isButtonPressed)
|
||||
{
|
||||
// TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
|
||||
View view = Application.WantContinuousButtonPressedView;
|
||||
View? view = Application.WantContinuousButtonPressedView;
|
||||
|
||||
if (view is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (iterations++ >= iterationsUntilFast)
|
||||
if (iterations++ >= ITERATIONS_UNTIL_FAST)
|
||||
{
|
||||
delay = fastDelay;
|
||||
delay = FAST_DELAY;
|
||||
}
|
||||
await Task.Delay (delay);
|
||||
|
||||
@@ -1012,13 +1011,13 @@ internal class WindowsDriver : ConsoleDriver
|
||||
if (_isButtonDoubleClicked || _isOneFingerDoubleClicked)
|
||||
{
|
||||
// TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
|
||||
Application.MainLoop.AddIdle (
|
||||
() =>
|
||||
{
|
||||
Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
|
||||
Application.MainLoop!.AddIdle (
|
||||
() =>
|
||||
{
|
||||
Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button.
|
||||
@@ -1084,13 +1083,13 @@ internal class WindowsDriver : ConsoleDriver
|
||||
if ((mouseFlag & MouseFlags.ReportMousePosition) == 0)
|
||||
{
|
||||
// TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
|
||||
Application.MainLoop.AddIdle (
|
||||
() =>
|
||||
{
|
||||
Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag));
|
||||
Application.MainLoop!.AddIdle (
|
||||
() =>
|
||||
{
|
||||
Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag));
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (_lastMouseButtonPressed != null
|
||||
@@ -1254,420 +1253,3 @@ internal class WindowsDriver : ConsoleDriver
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mainloop intended to be used with the <see cref="WindowsDriver"/>, and can
|
||||
/// only be used on Windows.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This implementation is used for WindowsDriver.
|
||||
/// </remarks>
|
||||
internal class WindowsMainLoop : IMainLoopDriver
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the window is changed.
|
||||
/// </summary>
|
||||
public EventHandler<SizeChangedEventArgs> WinChanged;
|
||||
|
||||
private readonly ConsoleDriver _consoleDriver;
|
||||
private readonly ManualResetEventSlim _eventReady = new (false);
|
||||
|
||||
// The records that we keep fetching
|
||||
private readonly ConcurrentQueue<WindowsConsole.InputRecord []> _resultQueue = new ();
|
||||
internal readonly ManualResetEventSlim _waitForProbe = new (false);
|
||||
private readonly WindowsConsole _winConsole;
|
||||
private CancellationTokenSource _eventReadyTokenSource = new ();
|
||||
private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
|
||||
private MainLoop _mainLoop;
|
||||
|
||||
public WindowsMainLoop (ConsoleDriver consoleDriver = null)
|
||||
{
|
||||
_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
|
||||
|
||||
if (!ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
_winConsole = ((WindowsDriver)consoleDriver).WinConsole;
|
||||
_winConsole._mainLoop = this;
|
||||
}
|
||||
}
|
||||
|
||||
public AnsiEscapeSequenceRequests EscSeqRequests { get; } = new ();
|
||||
|
||||
void IMainLoopDriver.Setup (MainLoop mainLoop)
|
||||
{
|
||||
_mainLoop = mainLoop;
|
||||
|
||||
if (ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run (WindowsInputHandler, _inputHandlerTokenSource.Token);
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
Task.Run (CheckWinChange);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
|
||||
|
||||
bool IMainLoopDriver.EventsPending ()
|
||||
{
|
||||
_waitForProbe.Set ();
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
_winChange.Set ();
|
||||
#endif
|
||||
if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!_eventReadyTokenSource.IsCancellationRequested)
|
||||
{
|
||||
// Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
|
||||
// are no timers, but there IS an idle handler waiting.
|
||||
_eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_eventReady.Reset ();
|
||||
}
|
||||
|
||||
if (!_eventReadyTokenSource.IsCancellationRequested)
|
||||
{
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _) || _winChanged;
|
||||
#else
|
||||
return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
|
||||
#endif
|
||||
}
|
||||
|
||||
_eventReadyTokenSource.Dispose ();
|
||||
_eventReadyTokenSource = new CancellationTokenSource ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IMainLoopDriver.Iteration ()
|
||||
{
|
||||
while (_resultQueue.Count > 0)
|
||||
{
|
||||
if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord [] inputRecords))
|
||||
{
|
||||
if (inputRecords is { Length: > 0 })
|
||||
{
|
||||
((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
if (_winChanged)
|
||||
{
|
||||
_winChanged = false;
|
||||
WinChanged?.Invoke (this, new SizeChangedEventArgs (_windowSize));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void IMainLoopDriver.TearDown ()
|
||||
{
|
||||
_inputHandlerTokenSource?.Cancel ();
|
||||
_inputHandlerTokenSource?.Dispose ();
|
||||
|
||||
if (_winConsole is { })
|
||||
{
|
||||
var numOfEvents = _winConsole.GetNumberOfConsoleInputEvents ();
|
||||
|
||||
if (numOfEvents > 0)
|
||||
{
|
||||
_winConsole.FlushConsoleInputBuffer ();
|
||||
//Debug.WriteLine ($"Flushed {numOfEvents} events.");
|
||||
}
|
||||
}
|
||||
|
||||
_waitForProbe?.Dispose ();
|
||||
|
||||
_resultQueue?.Clear ();
|
||||
|
||||
_eventReadyTokenSource?.Cancel ();
|
||||
_eventReadyTokenSource?.Dispose ();
|
||||
_eventReady?.Dispose ();
|
||||
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
_winChange?.Dispose ();
|
||||
#endif
|
||||
|
||||
_mainLoop = null;
|
||||
}
|
||||
|
||||
internal bool _forceRead;
|
||||
|
||||
private void WindowsInputHandler ()
|
||||
{
|
||||
while (_mainLoop is { })
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead)
|
||||
{
|
||||
_waitForProbe.Wait (_inputHandlerTokenSource.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Wakes the _waitForProbe if it's waiting
|
||||
_waitForProbe.Set ();
|
||||
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// If IsCancellationRequested is true the code after
|
||||
// the `finally` block will not be executed.
|
||||
if (!_inputHandlerTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_waitForProbe.Reset ();
|
||||
}
|
||||
}
|
||||
|
||||
if (_resultQueue?.Count == 0 || _forceRead)
|
||||
{
|
||||
while (!_inputHandlerTokenSource.IsCancellationRequested)
|
||||
{
|
||||
WindowsConsole.InputRecord [] inpRec = _winConsole.ReadConsoleInput ();
|
||||
|
||||
if (inpRec is { })
|
||||
{
|
||||
_resultQueue!.Enqueue (inpRec);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_forceRead)
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_eventReady.Set ();
|
||||
}
|
||||
}
|
||||
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
private readonly ManualResetEventSlim _winChange = new (false);
|
||||
private bool _winChanged;
|
||||
private Size _windowSize;
|
||||
private void CheckWinChange ()
|
||||
{
|
||||
while (_mainLoop is { })
|
||||
{
|
||||
_winChange.Wait ();
|
||||
_winChange.Reset ();
|
||||
|
||||
// Check if the window size changed every half second.
|
||||
// We do this to minimize the weird tearing seen on Windows when resizing the console
|
||||
while (_mainLoop is { })
|
||||
{
|
||||
Task.Delay (500).Wait ();
|
||||
_windowSize = _winConsole.GetConsoleBufferWindow (out _);
|
||||
|
||||
if (_windowSize != Size.Empty
|
||||
&& (_windowSize.Width != _consoleDriver.Cols
|
||||
|| _windowSize.Height != _consoleDriver.Rows))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_winChanged = true;
|
||||
_eventReady.Set ();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal class WindowsClipboard : ClipboardBase
|
||||
{
|
||||
private const uint CF_UNICODE_TEXT = 13;
|
||||
|
||||
public override bool IsSupported { get; } = CheckClipboardIsAvailable ();
|
||||
|
||||
private static bool CheckClipboardIsAvailable ()
|
||||
{
|
||||
// Attempt to open the clipboard
|
||||
if (OpenClipboard (nint.Zero))
|
||||
{
|
||||
// Clipboard is available
|
||||
// Close the clipboard after use
|
||||
CloseClipboard ();
|
||||
|
||||
return true;
|
||||
}
|
||||
// Clipboard is not available
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override string GetClipboardDataImpl ()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!OpenClipboard (nint.Zero))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
nint handle = GetClipboardData (CF_UNICODE_TEXT);
|
||||
|
||||
if (handle == nint.Zero)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
nint pointer = nint.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
pointer = GlobalLock (handle);
|
||||
|
||||
if (pointer == nint.Zero)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int size = GlobalSize (handle);
|
||||
var buff = new byte [size];
|
||||
|
||||
Marshal.Copy (pointer, buff, 0, size);
|
||||
|
||||
return Encoding.Unicode.GetString (buff).TrimEnd ('\0');
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pointer != nint.Zero)
|
||||
{
|
||||
GlobalUnlock (handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseClipboard ();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetClipboardDataImpl (string text)
|
||||
{
|
||||
OpenClipboard ();
|
||||
|
||||
EmptyClipboard ();
|
||||
nint hGlobal = default;
|
||||
|
||||
try
|
||||
{
|
||||
int bytes = (text.Length + 1) * 2;
|
||||
hGlobal = Marshal.AllocHGlobal (bytes);
|
||||
|
||||
if (hGlobal == default (nint))
|
||||
{
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
nint target = GlobalLock (hGlobal);
|
||||
|
||||
if (target == default (nint))
|
||||
{
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.Copy (text.ToCharArray (), 0, target, text.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlobalUnlock (target);
|
||||
}
|
||||
|
||||
if (SetClipboardData (CF_UNICODE_TEXT, hGlobal) == default (nint))
|
||||
{
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
hGlobal = default (nint);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (hGlobal != default (nint))
|
||||
{
|
||||
Marshal.FreeHGlobal (hGlobal);
|
||||
}
|
||||
|
||||
CloseClipboard ();
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
private static extern bool CloseClipboard ();
|
||||
|
||||
[DllImport ("user32.dll")]
|
||||
private static extern bool EmptyClipboard ();
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
private static extern nint GetClipboardData (uint uFormat);
|
||||
|
||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||
private static extern nint GlobalLock (nint hMem);
|
||||
|
||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||
private static extern int GlobalSize (nint handle);
|
||||
|
||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
private static extern bool GlobalUnlock (nint hMem);
|
||||
|
||||
[DllImport ("User32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
private static extern bool IsClipboardFormatAvailable (uint format);
|
||||
|
||||
private void OpenClipboard ()
|
||||
{
|
||||
var num = 10;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (OpenClipboard (default (nint)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (--num == 0)
|
||||
{
|
||||
ThrowWin32 ();
|
||||
}
|
||||
|
||||
Thread.Sleep (100);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
private static extern bool OpenClipboard (nint hWndNewOwner);
|
||||
|
||||
[DllImport ("user32.dll", SetLastError = true)]
|
||||
private static extern nint SetClipboardData (uint uFormat, nint data);
|
||||
|
||||
private void ThrowWin32 () { throw new Win32Exception (Marshal.GetLastWin32Error ()); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user