Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions GlobalKeyLogger/GlobalKeyLogger.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
Expand All @@ -7,7 +7,8 @@
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>false</PublishTrimmed> <!-- Optional, don't trim if you don't want to risk issues -->
<PublishTrimmed>false</PublishTrimmed>
<Nullable>enable</Nullable>
</PropertyGroup>


Expand Down
152 changes: 87 additions & 65 deletions GlobalKeyLogger/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
Expand All @@ -8,48 +8,64 @@

class GlobalKeyLogger : ApplicationContext
{
private static Stopwatch stopwatch = Stopwatch.StartNew();
private static StreamWriter writer;
private static readonly Stopwatch stopwatch = Stopwatch.StartNew();
private static StreamWriter? writer;
private static long lastKeyDownTime = 0;
private static bool firstKey = true;
private static long flightTime = -1;
private static IntPtr _hookID = IntPtr.Zero;
private static Dictionary<int, long> keyDownTimes = new Dictionary<int, long>();
private static NotifyIcon trayIcon;
private static readonly Dictionary<int, long> keyDownTimes = new Dictionary<int, long>();
private static NotifyIcon? trayIcon;
private static bool isRecording = true;
private static Icon recordIcon;
private static Icon pauseIcon;
private static string logFileName;
private static int keystrokeCount = 0; // 🆕 Track number of logged keystrokes
private static Icon? recordIcon;
private static Icon? pauseIcon;
private static string logFileName = string.Empty;
private static int keystrokeCount = 0;
private bool _disposed = false;

private static LowLevelKeyboardProc _proc = HookCallback;
private static readonly LowLevelKeyboardProc _proc = HookCallback;

public GlobalKeyLogger()
{
// Load icons
recordIcon = CreateRedDotIcon();
pauseIcon = CreatePauseIcon();

// Create tray icon
trayIcon = new NotifyIcon()
{
Icon = recordIcon,
Visible = true,
Text = "Global Keylogger (0)" // Initial tooltip
Text = "Global Keylogger (0)"
};

// Create context menu
trayIcon.ContextMenuStrip = new ContextMenuStrip();
trayIcon.ContextMenuStrip.Items.Add("Pause", null, PauseLogging);
trayIcon.ContextMenuStrip.Items.Add("Exit", null, Exit);

trayIcon.DoubleClick += TrayIcon_DoubleClick;

// Setup keylogger
CreateNewLogFile();
_hookID = SetHook(_proc);
}

protected override void Dispose(bool disposing)
{
if (_disposed) return;
_disposed = true;
if (disposing)
{
if (_hookID != IntPtr.Zero)
{
UnhookWindowsHookEx(_hookID);
_hookID = IntPtr.Zero;
}
writer?.Dispose();
trayIcon?.Dispose();
recordIcon?.Dispose();
pauseIcon?.Dispose();
}
base.Dispose(disposing);
}

private void CreateNewLogFile()
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
Expand All @@ -60,81 +76,75 @@ private void CreateNewLogFile()
writer.Flush();
}

private void TrayIcon_DoubleClick(object sender, EventArgs e)
private void TrayIcon_DoubleClick(object? sender, EventArgs e)
{
MessageBox.Show($"Keylogger is {(isRecording ? "recording" : "paused")}.\nLogged {keystrokeCount} keystrokes.\n\nLogging to:\n{logFileName}\n\nRight-click tray icon for options.", "Global Keylogger");
MessageBox.Show(
$"Keylogger is {(isRecording ? "recording" : "paused")}.\nLogged {keystrokeCount} keystrokes.\n\nLogging to:\n{logFileName}\n\nRight-click tray icon for options.",
"Global Keylogger");
}

private void PauseLogging(object sender, EventArgs e)
private void PauseLogging(object? sender, EventArgs e)
{
isRecording = !isRecording;
trayIcon.Icon = isRecording ? recordIcon : pauseIcon;
trayIcon.ContextMenuStrip.Items[0].Text = isRecording ? "Pause" : "Resume";
UpdateTooltip(); // 🆕 Update tooltip on pause/resume
trayIcon!.Icon = isRecording ? recordIcon : pauseIcon;
trayIcon.ContextMenuStrip!.Items[0].Text = isRecording ? "Pause" : "Resume";
UpdateTooltip();
}

private void Exit(object sender, EventArgs e)
private void Exit(object? sender, EventArgs e)
{
trayIcon.Visible = false;
writer?.Close();
UnhookWindowsHookEx(_hookID);
trayIcon!.Visible = false;
Dispose();
Application.Exit();
}

private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule!)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
using Process curProcess = Process.GetCurrentProcess();
using ProcessModule curModule = curProcess.MainModule!;
return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}

private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
if (nCode >= 0 && writer != null && isRecording)
{
int vkCode = Marshal.ReadInt32(lParam);

if (isRecording)
if (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
{
if (wParam == (IntPtr)WM_KEYDOWN)
{
long currDownTime = stopwatch.ElapsedMilliseconds;
long currDownTime = stopwatch.ElapsedMilliseconds;

if (firstKey)
{
if (firstKey)
{
flightTime = -1;
firstKey = false;
}
else
{
flightTime = currDownTime - lastKeyDownTime;
if (flightTime > 1500)
flightTime = -1;
firstKey = false;
}
else
{
flightTime = currDownTime - lastKeyDownTime;
if (flightTime > 1500)
flightTime = -1;
}

lastKeyDownTime = currDownTime;
keyDownTimes[vkCode] = currDownTime;
}
else if (wParam == (IntPtr)WM_KEYUP)

lastKeyDownTime = currDownTime;
keyDownTimes[vkCode] = currDownTime;
}
else if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
{
if (keyDownTimes.TryGetValue(vkCode, out long downTime))
{
if (keyDownTimes.TryGetValue(vkCode, out long downTime))
{
long releaseTime = stopwatch.ElapsedMilliseconds;
long holdTime = releaseTime - downTime;
long holdTime = stopwatch.ElapsedMilliseconds - downTime;

writer.WriteLine($"{vkCode},{holdTime},{flightTime}");
writer.Flush();
writer.WriteLine($"{vkCode},{holdTime},{flightTime}");
writer.Flush();

keystrokeCount++; // 🆕 Count each logged key
UpdateTooltip(); // 🆕 Update tray text
keystrokeCount++;
UpdateTooltip();

keyDownTimes.Remove(vkCode);
}
keyDownTimes.Remove(vkCode);
}
}
}
Expand All @@ -145,35 +155,43 @@ private static void UpdateTooltip()
{
string state = isRecording ? "Recording" : "Paused";
string tooltip = $"Keylogger ({state})\nKeys: {keystrokeCount}";
trayIcon.Text = tooltip.Length > 63 ? tooltip.Substring(0, 63) : tooltip;
trayIcon!.Text = tooltip.Length > 63 ? tooltip.Substring(0, 63) : tooltip;
}

private static Icon CreateRedDotIcon()
{
Bitmap bmp = new Bitmap(16, 16);
using Bitmap bmp = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
g.FillEllipse(Brushes.Red, 2, 2, 12, 12);
}
return Icon.FromHandle(bmp.GetHicon());
IntPtr hIcon = bmp.GetHicon();
Icon icon = (Icon)Icon.FromHandle(hIcon).Clone();
DestroyIcon(hIcon);
return icon;
}

private static Icon CreatePauseIcon()
{
Bitmap bmp = new Bitmap(16, 16);
using Bitmap bmp = new Bitmap(16, 16);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
g.FillRectangle(Brushes.RoyalBlue, 4, 2, 3, 12);
g.FillRectangle(Brushes.RoyalBlue, 9, 2, 3, 12);
}
return Icon.FromHandle(bmp.GetHicon());
IntPtr hIcon = bmp.GetHicon();
Icon icon = (Icon)Icon.FromHandle(hIcon).Clone();
DestroyIcon(hIcon);
return icon;
}

private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_SYSKEYUP = 0x0105;

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
Expand All @@ -190,6 +208,10 @@ private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DestroyIcon(IntPtr hIcon);

[STAThread]
static void Main()
{
Expand Down