mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
What the title says. 😄
Rather than relying on the potentially overloaded `!=` or `==` operators
when checking for null, now we'll use the `is` expression (possibly
combined with the `not` operator) to ensure correct checking. Probably
overkill for many of these classes, but decided to err on the side of
consistency. Would matter more on classes that may be inherited or
extended.
Using `is` and `is not` will provide us a guarantee that no
user-overloaded equality operators (`==`/`!=`) is invoked when a
`expression is null` is evaluated.
In code form, changed all instances of:
```c#
something != null
something == null
```
to:
```c#
something is not null
something is null
```
The one exception was checking null on a `KeyChord`. `KeyChord` is a
struct which is never null so VS will raise an error when trying this
versus just providing a warning when using `keyChord != null`. In
reality, we shouldn't do this check because it can't ever be null. In
the case of a `KeyChord` it **would** be a `KeyChord` equivalent to:
```c#
KeyChord keyChord = new ()
{
Modifiers = 0,
Vkey = 0,
ScanCode = 0
};
```
299 lines
8.4 KiB
C#
299 lines
8.4 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
|
|
|
// shamelessly from https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs
|
|
public static partial class ClipboardHelper
|
|
{
|
|
private static readonly bool? _clipboardSupported = true;
|
|
|
|
// Used if an external clipboard is not available, e.g. if xclip is missing.
|
|
// This is useful for testing in CI as well.
|
|
private static string? _internalClipboard;
|
|
|
|
public static string GetText()
|
|
{
|
|
if (_clipboardSupported == false)
|
|
{
|
|
return _internalClipboard ?? string.Empty;
|
|
}
|
|
|
|
var tool = string.Empty;
|
|
var args = string.Empty;
|
|
var clipboardText = string.Empty;
|
|
|
|
ExecuteOnStaThread(() => GetTextImpl(out clipboardText));
|
|
return clipboardText;
|
|
}
|
|
|
|
public static void SetText(string text)
|
|
{
|
|
if (_clipboardSupported == false)
|
|
{
|
|
_internalClipboard = text;
|
|
return;
|
|
}
|
|
|
|
var tool = string.Empty;
|
|
var args = string.Empty;
|
|
ExecuteOnStaThread(() => SetClipboardData(Tuple.Create(text, CF_UNICODETEXT)));
|
|
return;
|
|
}
|
|
|
|
public static void SetRtf(string plainText, string rtfText)
|
|
{
|
|
if (s_CF_RTF == 0)
|
|
{
|
|
s_CF_RTF = RegisterClipboardFormat("Rich Text Format");
|
|
}
|
|
|
|
ExecuteOnStaThread(() => SetClipboardData(
|
|
Tuple.Create(plainText, CF_UNICODETEXT),
|
|
Tuple.Create(rtfText, s_CF_RTF)));
|
|
}
|
|
|
|
#pragma warning disable SA1310 // Field names should not contain underscore
|
|
private const uint GMEM_MOVEABLE = 0x0002;
|
|
private const uint GMEM_ZEROINIT = 0x0040;
|
|
#pragma warning restore SA1310 // Field names should not contain underscore
|
|
private const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT;
|
|
|
|
[LibraryImport("kernel32.dll")]
|
|
private static partial IntPtr GlobalAlloc(uint flags, UIntPtr dwBytes);
|
|
|
|
[LibraryImport("kernel32.dll")]
|
|
private static partial IntPtr GlobalFree(IntPtr hMem);
|
|
|
|
[LibraryImport("kernel32.dll")]
|
|
private static partial IntPtr GlobalLock(IntPtr hMem);
|
|
|
|
[LibraryImport("kernel32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool GlobalUnlock(IntPtr hMem);
|
|
|
|
[LibraryImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
|
|
private static partial void CopyMemory(IntPtr dest, IntPtr src, uint count);
|
|
|
|
[LibraryImport("user32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool IsClipboardFormatAvailable(uint format);
|
|
|
|
[LibraryImport("user32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool OpenClipboard(IntPtr hWndNewOwner);
|
|
|
|
[LibraryImport("user32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool CloseClipboard();
|
|
|
|
[LibraryImport("user32.dll")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
private static partial bool EmptyClipboard();
|
|
|
|
[LibraryImport("user32.dll")]
|
|
private static partial IntPtr GetClipboardData(uint format);
|
|
|
|
[LibraryImport("user32.dll")]
|
|
private static partial IntPtr SetClipboardData(uint format, IntPtr data);
|
|
|
|
[LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
|
|
private static partial uint RegisterClipboardFormat(string lpszFormat);
|
|
|
|
#pragma warning disable SA1310 // Field names should not contain underscore
|
|
private const uint CF_TEXT = 1;
|
|
private const uint CF_UNICODETEXT = 13;
|
|
|
|
#pragma warning disable SA1308 // Variable names should not be prefixed
|
|
private static uint s_CF_RTF;
|
|
#pragma warning restore SA1308 // Variable names should not be prefixed
|
|
#pragma warning restore SA1310 // Field names should not contain underscore
|
|
|
|
private static bool GetTextImpl(out string text)
|
|
{
|
|
try
|
|
{
|
|
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
|
|
{
|
|
if (OpenClipboard(IntPtr.Zero))
|
|
{
|
|
var data = GetClipboardData(CF_UNICODETEXT);
|
|
if (data != IntPtr.Zero)
|
|
{
|
|
data = GlobalLock(data);
|
|
text = Marshal.PtrToStringUni(data) ?? string.Empty;
|
|
GlobalUnlock(data);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (IsClipboardFormatAvailable(CF_TEXT))
|
|
{
|
|
if (OpenClipboard(IntPtr.Zero))
|
|
{
|
|
var data = GetClipboardData(CF_TEXT);
|
|
if (data != IntPtr.Zero)
|
|
{
|
|
data = GlobalLock(data);
|
|
text = Marshal.PtrToStringAnsi(data) ?? string.Empty;
|
|
GlobalUnlock(data);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore exceptions
|
|
}
|
|
finally
|
|
{
|
|
CloseClipboard();
|
|
}
|
|
|
|
text = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
private static bool SetClipboardData(params Tuple<string, uint>[] data)
|
|
{
|
|
try
|
|
{
|
|
if (!OpenClipboard(IntPtr.Zero))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
EmptyClipboard();
|
|
|
|
foreach (var d in data)
|
|
{
|
|
if (!SetSingleClipboardData(d.Item1, d.Item2))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
CloseClipboard();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool SetSingleClipboardData(string text, uint format)
|
|
{
|
|
var hGlobal = IntPtr.Zero;
|
|
var data = IntPtr.Zero;
|
|
|
|
try
|
|
{
|
|
uint bytes;
|
|
if (format == s_CF_RTF || format == CF_TEXT)
|
|
{
|
|
bytes = (uint)(text.Length + 1);
|
|
data = Marshal.StringToHGlobalAnsi(text);
|
|
}
|
|
else if (format == CF_UNICODETEXT)
|
|
{
|
|
bytes = (uint)((text.Length + 1) * 2);
|
|
data = Marshal.StringToHGlobalUni(text);
|
|
}
|
|
else
|
|
{
|
|
// Not yet supported format.
|
|
return false;
|
|
}
|
|
|
|
if (data == IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
hGlobal = GlobalAlloc(GHND, (UIntPtr)bytes);
|
|
if (hGlobal == IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var dataCopy = GlobalLock(hGlobal);
|
|
if (dataCopy == IntPtr.Zero)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CopyMemory(dataCopy, data, bytes);
|
|
GlobalUnlock(hGlobal);
|
|
|
|
if (SetClipboardData(format, hGlobal) != IntPtr.Zero)
|
|
{
|
|
// The clipboard owns this memory now, so don't free it.
|
|
hGlobal = IntPtr.Zero;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore failures
|
|
}
|
|
finally
|
|
{
|
|
if (data != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeHGlobal(data);
|
|
}
|
|
|
|
if (hGlobal != IntPtr.Zero)
|
|
{
|
|
GlobalFree(hGlobal);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void ExecuteOnStaThread(Func<bool> action)
|
|
{
|
|
const int RetryCount = 5;
|
|
var tries = 0;
|
|
|
|
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
|
|
{
|
|
while (tries++ < RetryCount && !action())
|
|
{
|
|
// wait until RetryCount or action
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Exception? exception = null;
|
|
var thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
while (tries++ < RetryCount && !action())
|
|
{
|
|
// wait until RetryCount or action
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
exception = e;
|
|
}
|
|
});
|
|
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
thread.Start();
|
|
thread.Join();
|
|
|
|
if (exception is not null)
|
|
{
|
|
throw exception;
|
|
}
|
|
}
|
|
}
|