Skip to content
Merged
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
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.0] - 2026-07-15

### Added

- Redesigned settings as a sidebar window (General, Event Visibility, Visual Style,
Keyboard Shortcuts, Profiles, Activity, Menu Layout).
- A live, interactive Preview Pad: click and drag inside it to see your current
pulse, laser, and drag settings without touching the rest of the screen.
- Profiles: save named visual setups and switch between them from a dropdown
(with a built-in Default), and import/export them as JSON to move between PCs.
- Activity: a local, on-device view of your daily clicks with a seven-day chart
and a Left/Right/Middle/Drags breakdown.
- Menu Layout: hide individual system-tray menu items and drag to reorder them
(Quit is always shown).
- A designed app icon (tray, window, and installer) and a wordmark on the
General settings tab.

### Changed

- Settings changes now apply when you click OK and can be discarded with Cancel,
instead of taking effect live as you edit.
- The settings color and profile dropdowns are dark-themed to match the window.

## [1.3.0] - 2026-07-15

### Added
Expand Down Expand Up @@ -97,7 +120,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Initial release: fading click pulses drawn at the cursor, a system-tray
presence, a transparent click-through overlay, and a system-wide mouse hook.

[Unreleased]: https://github.com/dvdstelt/ClickLightWin/compare/v1.3.0...HEAD
[Unreleased]: https://github.com/dvdstelt/ClickLightWin/compare/v1.4.0...HEAD
[1.4.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/dvdstelt/ClickLightWin/compare/v1.0.0...v1.1.0
Expand Down
65 changes: 65 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Contributing to ClickLight for Windows

Thanks for helping improve ClickLight.

## Get Started

1. Fork the repository and create a branch for your change.
2. Build and run the app (see below).
3. Keep changes focused, and update the docs and [CHANGELOG.md](CHANGELOG.md) when behavior changes.
4. Open a pull request against `main`.

## Requirements

- Windows 10 21H2+ or Windows 11 (WPF builds and runs on Windows only)
- [.NET 10 SDK](https://dotnet.microsoft.com/download) (`dotnet --version` shows `10.x`)

## Build and Run

From the repository root:

```powershell
dotnet build src\ClickLightWin.sln
dotnet run --project src\ClickLightWin\ClickLightWin.csproj
```

The app starts in the system tray. Right-click the tray icon for the menu, or
press **Ctrl+Shift+L** to toggle highlighting.

## Verify Changes

Run the unit tests:

```powershell
dotnet test src\ClickLightWin.sln
```

The tests cover the pure seams (coordinate mapping, settings persistence, shortcut
formatting). Most of the app is UI and system hooks, so also **run the app and
manually exercise what you changed**. Useful checks:

- the tray menu and the Settings window (including the live Preview Pad)
- click highlights in another app (press, release, right-click, drag)
- laser-pointer and drawing modes, and any annotation gesture
- any new keyboard shortcut or pointer interaction

Include your manual test steps in the pull request.

## Code Style

- Style is enforced by [.editorconfig](.editorconfig); please keep to it.
- Nullable reference types are enabled, and Release builds treat warnings as errors.
- Match the surrounding code: the project uses current C# features and favors small,
well-named methods over comments.
- Commit messages use short, imperative, gitmoji-style prefixes (e.g. `✨ Add ...`,
`🐛 Fix ...`, `📝 Update ...`) — match the existing history.

## Pull Requests

- Explain what changed and why.
- Include screenshots or a short recording for visible UI changes.
- Do not commit signing credentials, private keys, build output, or local system files.
- Keep changes to the release workflow separate unless that is what you are working on.

For security-sensitive reports, please contact the maintainer privately rather than
opening a public issue.
94 changes: 51 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,63 @@
# ClickLightWin

A Windows menu-bar (system-tray) app that highlights your mouse clicks on screen
during live demos, screen sharing, and UX reviews. It is a ground-up C# / .NET
(WPF) reimplementation of the macOS app [ClickLight](https://github.com/aurorascharff/ClickLight),
not a port: every macOS primitive is mapped to a clean Windows equivalent.

On each click, a short animated pulse is drawn at the cursor and fades out. The
app runs headless apart from a tray icon; there is no main window.

## Status

**1.1.0.** Feature-complete for daily use:

- System-wide click capture via a low-level mouse hook
- Transparent, click-through, topmost overlay per monitor, DPI-correct (Per-Monitor v2)
- Fading pulse on press, a contracting ring on hold + release, and a fading drag trail
- **Laser-pointer mode**: a glowing cursor that trails the pointer during presentations
- **Annotations**: hold Ctrl+Shift and drag — left-drag draws an arrow, right-drag a box; they persist until Ctrl+Shift+C clears them
- **Live shortcut display**: shows keyboard shortcuts (Ctrl+C, Alt+Tab, …) as key-cap pills for screencasts — modifier combos only, off by default
- Per-button and annotation colors, Size/Duration presets
- A modern settings window and a dark tray menu (feature toggles, preset submenus)
- Global toggle hotkey **Ctrl+Shift+L**, launch-at-login, single-instance guard
- Settings persisted to `%APPDATA%\ClickLightWin\settings.json`
- **Auto-update** (installer build only): checks GitHub Releases in the background
and, when a newer version is out, shows a "Restart to update" prompt in the tray.
Nothing installs until you click it; the portable exe updates manually.
# ClickLight for Windows

A small Windows application in the system-tray that highlights your clicks during live demos, screen sharing, and other moments where people need to be able to follow what you are doing.

Screen recorders can add click effects after the fact. ClickLight is for the live moment itself, when you need the audience to see exactly when you clicked without interrupting your flow.

It is a ground-up C# / .NET (WPF) reimplementation of the macOS app [ClickLight](https://github.com/aurorascharff/ClickLight), not a port: every macOS primitive is mapped to a clean Windows equivalent.

On each click, a short animated pulse is drawn at the cursor and fades out. The app runs headless apart from a tray icon; there is no main window.

## Demo

-- soon --

## Features

- Click highlights across macOS apps
- Separate visuals for press, release, right-click, and drag
- Optional laser pointer mode with drawing mode
- Optional keyboard shortcuts
- Local daily click activity chart with a resettable seven-day history
- Dedicated settings window with presets, profiles, and a sidebar preview pad with Randomize
- Custom color picker in Settings
- One default ClickLight toggle shortcut, with optional shortcuts for other actions
- System-tray menu
- Quick presets for size, duration
- Customizable menu for hiding optional controls you do not use

- Test pulse for verifying overlay behavior

See [docs/04-build-checklist.md](docs/04-build-checklist.md) for the roadmap this was built against.

> Note: multi-monitor click routing and mixed-DPI placement are implemented but have
> only been verified on a single monitor (100% and 150%). Worth a pass on multi-monitor
> and mixed-DPI hardware.
> Note: multi-monitor click routing and mixed-DPI placement are implemented but have only been verified on a single monitor (100% and 150%). Worth a pass on multi-monitor and mixed-DPI hardware.

## Keyboard Shortcuts

ClickLight includes one default global shortcut for quick toggles during demos. Other actions can be assigned shortcuts in Settings if you want them.

| Shortcut | Action |
| --- | --- |
| `Control + Shift + L` | Toggle ClickLight on/off |
| `Control + Shift + D` | Toggle drawing mode, cleared by exiting drawing mode. |
| `Control + Shift + left-click` & drag | Draw an arrow |
| `Control + Shift + right-click` & drag | Draw a box |
| `Control + SHift + C` | Clear drawings |

All shortcuts can be changed in Settings.

## Download

Grab the latest from the [Releases](../../releases) page. Two options:

- **`ClickLight-vX.Y.Z-Setup.exe`** (installer, ~7 MB) — recommended. Installs the app,
adds Start-menu/desktop shortcuts, and installs the .NET 10 Desktop Runtime for you if
it is missing.
- **`ClickLight-vX.Y.Z-win-x64.exe`** (bare app, ~250 KB) — for machines that already
have the .NET 10 Desktop Runtime. Just run it.
- **`ClickLight-vX.Y.Z-win-arm64.exe`** — the same bare app for Windows-on-Arm
(needs the arm64 .NET 10 Desktop Runtime).
- **`ClickLight-vX.Y.Z-Setup.exe`** (installer, ~7 MB) — recommended. Installs the app, adds Start-menu/desktop shortcuts, and installs the .NET 10 Desktop Runtime for you if it is missing.
- **`ClickLight-vX.Y.Z-win-x64.exe`** (bare app, ~250 KB) — for machines that already have the .NET 10 Desktop Runtime. Just run it.
- **`ClickLight-vX.Y.Z-win-arm64.exe`** — the same bare app for Windows-on-Arm (needs the arm64 .NET 10 Desktop Runtime).

Until code signing is set up (see below), both are unsigned, so on first launch Windows
SmartScreen may say "Windows protected your PC" — click **More info -> Run anyway**.
> [!NOTE]
>
> Until code signing is set up (see below), both are unsigned, so on first launch Windows
> SmartScreen may say "Windows protected your PC" — click **More info -> Run anyway**.

## Known limitations

Expand All @@ -64,7 +76,3 @@ SmartScreen may say "Windows protected your PC" — click **More info -> Run any
- **Mixed-DPI multi-monitor.** Placement is verified at 100% and 150% on a single
monitor. Cross-monitor routing and mixed-DPI setups (e.g. a 150% laptop panel
plus a 100% external) should be verified on that hardware.

The macOS Swift sources under `ClickLight/Sources/ClickLight/` are the source of
truth for behavior (pulse shapes, timing, presets). The mapping from Swift files
to Windows components is in [docs/01-architecture.md](docs/01-architecture.md).
108 changes: 108 additions & 0 deletions src/ClickLightWin/ActivityStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Globalization;
using System.IO;
using System.Text.Json;

namespace ClickLightWin;

/// <summary>
/// Tracks per-day click tallies, persisted to %APPDATA%\ClickLightWin\activity.json.
/// Everything stays on this machine. Records are cheap in-memory increments; the file
/// is written on a throttle by the caller (and on exit). Mirrors ClickActivityStore.
/// </summary>
public sealed class ActivityStore
{
private static readonly JsonSerializerOptions Options = new() { WriteIndented = true };

private readonly string _path;
private readonly Dictionary<string, ClickActivityDay> _days = [];
private bool _dirty;

public ActivityStore()
{
_path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ClickLightWin", "activity.json");
Load();
}

private static string Key(DateTime date) => date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);

private ClickActivityDay Today()
{
var key = Key(DateTime.Now);
if (!_days.TryGetValue(key, out var day))
{
day = new ClickActivityDay { Date = key };
_days[key] = day;
}
return day;
}

public void RecordClick(ClickButton button)
{
var day = Today();
switch (button)
{
case ClickButton.Left: day.Left++; break;
case ClickButton.Right: day.Right++; break;
case ClickButton.Middle: day.Middle++; break;
}
_dirty = true;
}

public void RecordDrag()
{
Today().Drags++;
_dirty = true;
}

/// <summary>The seven days ending today, oldest first, with gaps filled by empty days.</summary>
public IReadOnlyList<ClickActivityDay> LastSevenDays()
{
var list = new List<ClickActivityDay>(7);
for (var offset = 6; offset >= 0; offset--)
{
var key = Key(DateTime.Now.AddDays(-offset));
list.Add(_days.TryGetValue(key, out var day) ? day : new ClickActivityDay { Date = key });
}
return list;
}

public void Reset()
{
_days.Clear();
_dirty = true;
Save();
}

/// <summary>Write to disk if anything changed since the last save. Best-effort.</summary>
public void Save()
{
if (!_dirty) return;
try
{
Directory.CreateDirectory(Path.GetDirectoryName(_path)!);
File.WriteAllText(_path, JsonSerializer.Serialize(_days.Values, Options));
_dirty = false;
}
catch
{
// Never crash the app over a failed activity write.
}
}

private void Load()
{
try
{
if (!File.Exists(_path)) return;
var loaded = JsonSerializer.Deserialize<List<ClickActivityDay>>(File.ReadAllText(_path));
if (loaded is null) return;
foreach (var day in loaded) _days[day.Date] = day;
}
catch
{
// Corrupt or unreadable file: start with no history.
}
}
}
Loading
Loading