Skip to content

Instantly share code, notes, and snippets.

@lahma0
Last active March 16, 2024 07:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lahma0/a66a573273a779ebcb9bd43a898bfc03 to your computer and use it in GitHub Desktop.
Save lahma0/a66a573273a779ebcb9bd43a898bfc03 to your computer and use it in GitHub Desktop.
ShareXNamedPipe
public static class ShareXNamedPipeClient
{
public static string PipeName => "ShareXPatcherPipe";
public static async Task<string?> GetLastRegionBoundsXmlAsync(int connectTimeoutMs = 2000)
{
if (!ShareXUtils.IsShareXRunning())
throw new ShareXProcessNotFoundException(
"No running ShareX process found. Please ensure ShareX is running and try again.");
if (!ShareXUtils.ShareXPipeExists() || !(await IsServerListening()))
{
// ShareX is running but pipe either doesn't exist or server isn't listening so inject the patcher.
await ShareXUtils.InjectPatcherAndWaitForServerAsync();
}
var clientPipe = await ConnectToServerAsync(connectTimeoutMs);
var serverResponse = await ReadDataFromServerAsync(clientPipe);
if (string.IsNullOrWhiteSpace(serverResponse) || serverResponse == "null")
throw new ShareXLastRegionInvalidException(
"'Last Region' data returned from the ShareX server is null. This likely indicates that " +
"no screenshots have been taken since the ShareX process started.");
if (!serverResponse.StartsWith("<?xml"))
throw new ShareXLastRegionInvalidException(
"'Last Region' data returned from the ShareX server is invalid or in an unexpected format.");
return serverResponse;
}
private static async Task<NamedPipeClientStream> ConnectToServerAsync(int connectTimeoutMs)
{
try
{
var clientPipe = new NamedPipeClientStream(PipeName);
await clientPipe.ConnectAsync(connectTimeoutMs);
return clientPipe;
}
catch (TimeoutException timeoutEx)
{
throw new ShareXServerTimeoutException(
$"The ShareX server did not respond within {connectTimeoutMs}ms.", timeoutEx);
}
}
private static async Task<string?> ReadDataFromServerAsync(NamedPipeClientStream clientPipe)
{
using var reader = new StreamReader(clientPipe);
var serverResponse = await reader.ReadToEndAsync();
await clientPipe.DisposeAsync();
return serverResponse;
}
public static async Task<bool> IsServerListening(int connectTimeoutMs = 2000)
{
// If ShareX isn't running or the pipe doesn't exist, the server definitely isn't listening.
if (!ShareXUtils.IsShareXRunning() || !ShareXUtils.ShareXPipeExists())
return false;
try
{
var clientPipe = await ConnectToServerAsync(connectTimeoutMs);
// Response is irrelevant but server expects us to read its response after connecting so do it anyways.
await ReadDataFromServerAsync(clientPipe);
}
catch
{
return false;
}
return true;
}
}
public class ShareXNamedPipeServer
{
public static string PipeName => "ShareXPatcherPipe";
public static bool IsListening { get; private set; }
public static NamedPipeServerStream ServerPipe { get; private set; }
public static async Task RunServerAsync()
{
if (ServerPipe != null)
{
ShareXPatcher.WriteDebugLine("Server is already running");
return;
}
try
{
if (ServerPipe == null)
{
ServerPipe = new NamedPipeServerStream(PipeName,
PipeDirection.InOut,
1,
PipeTransmissionMode.Message,
PipeOptions.Asynchronous);
// Timeouts are not supported when using Message mode and ReadAsync/WriteAsync. You must use
// BeginRead/BeginWrite with callbacks to properly support timeouts.
//ServerPipe.ReadTimeout = timeout;
//ServerPipe.WriteTimeout = timeout;
}
IsListening = true;
StreamWriter writer = null;
do
{
try
{
ShareXPatcher.WriteDebugLine("Server is waiting for new client connections...");
await ServerPipe.WaitForConnectionAsync();
ShareXPatcher.WriteDebugLine("Client connected to server");
// Only create writer once and reuse it for each client connection. Disposing it causes
// ServerPipe to also be disposed. No good reason to create a new one every loop.
if (writer == null)
writer = new StreamWriter(ServerPipe) { AutoFlush = true };
if (!string.IsNullOrWhiteSpace(ShareXPatcher.LastRegionBoundsXml))
await writer.WriteAsync(ShareXPatcher.LastRegionBoundsXml);
else
await writer.WriteAsync("null");
ServerPipe.WaitForPipeDrain();
}
catch (IOException ex)
{
ShareXPatcher.WriteDebugLine(
$"An error occurred while communicating with a client: {ex.Message}");
}
catch (ObjectDisposedException)
{
ShareXPatcher.WriteDebugLine(
"Server has been disposed so pipe server will not resume listening for clients");
// ServerPipe is disposed so we must break out of the loop (bc we must recreate ServerPipe).
break;
}
catch (Exception ex)
{
ShareXPatcher.WriteDebugLine(
$"An unknown error occurred within pipe server loop: {ex.Message}");
}
finally
{
if (ServerPipe != null && ServerPipe.IsConnected)
{
ShareXPatcher.WriteDebugLine("Disconnecting client...");
ServerPipe.Disconnect();
}
}
if (IsListening)
ShareXPatcher.WriteDebugLine("Restarting pipe server...");
} while (IsListening);
}
finally
{
// This 'finally' ensures that the server pipe will be disposed of regardless.
ShareXPatcher.WriteDebugLine($"Calling {nameof(StopServer)}...");
StopServer();
}
}
public static void StopServer()
{
IsListening = false;
if (ServerPipe == null)
{
ShareXPatcher.WriteDebugLine("Server is already stopped");
return;
}
try
{
if (ServerPipe.IsConnected)
{
ShareXPatcher.WriteDebugLine("Disconnecting client...");
ServerPipe?.Disconnect();
}
ShareXPatcher.WriteDebugLine("Closing server pipe...");
ServerPipe?.Close();
ShareXPatcher.WriteDebugLine("Disposing server pipe...");
ServerPipe?.Dispose();
}
catch { }
finally
{
ServerPipe = null;
}
ShareXPatcher.WriteDebugLine("Server is stopped");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment