Skip to content

Commit fd3a062

Browse files
committed
Add Windows installer support with Inno Setup and startup registration
1 parent df7605a commit fd3a062

File tree

8 files changed

+267
-26
lines changed

8 files changed

+267
-26
lines changed

.github/workflows/build.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ jobs:
3434
- name: Publish (self-contained)
3535
run: dotnet publish ScreenGrid.csproj -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o ./artifacts/self-contained
3636

37+
- name: Install Inno Setup
38+
run: choco install innosetup -y --no-progress
39+
40+
- name: Build Installer
41+
run: |
42+
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer/ScreenGridSetup.iss
43+
shell: pwsh
44+
45+
- name: Upload installer artifact
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: ScreenGrid-installer
49+
path: installer-output/ScreenGridSetup.exe
50+
3751
- name: Upload framework-dependent artifact
3852
uses: actions/upload-artifact@v4
3953
with:
@@ -65,10 +79,16 @@ jobs:
6579
name: ScreenGrid-self-contained
6680
path: ./self-contained
6781

82+
- uses: actions/download-artifact@v4
83+
with:
84+
name: ScreenGrid-installer
85+
path: ./installer
86+
6887
- name: Rename artifacts
6988
run: |
7089
mv ./framework-dependent/ScreenGrid.exe ./ScreenGrid-small.exe
7190
mv ./self-contained/ScreenGrid.exe ./ScreenGrid-standalone.exe
91+
mv ./installer/ScreenGridSetup.exe ./ScreenGridSetup.exe
7292
7393
- name: Create/Update rolling release
7494
uses: softprops/action-gh-release@v2
@@ -82,9 +102,11 @@ jobs:
82102
|------|------|----------------|
83103
| `ScreenGrid-small.exe` | ~200 KB | Yes (.NET 9) |
84104
| `ScreenGrid-standalone.exe` | ~70 MB | No |
105+
| `ScreenGridSetup.exe` | ~66 MB | No (installer) |
85106
files: |
86107
ScreenGrid-small.exe
87108
ScreenGrid-standalone.exe
109+
ScreenGridSetup.exe
88110
prerelease: true
89111
make_latest: true
90112

@@ -107,6 +129,11 @@ jobs:
107129
name: ScreenGrid-self-contained
108130
path: ./self-contained
109131

132+
- uses: actions/download-artifact@v4
133+
with:
134+
name: ScreenGrid-installer
135+
path: ./installer
136+
110137
- name: Get version from tag
111138
id: version
112139
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
@@ -115,6 +142,7 @@ jobs:
115142
run: |
116143
mv ./framework-dependent/ScreenGrid.exe ./ScreenGrid-small.exe
117144
mv ./self-contained/ScreenGrid.exe ./ScreenGrid-standalone.exe
145+
mv ./installer/ScreenGridSetup.exe ./ScreenGridSetup.exe
118146
119147
- name: Create versioned release
120148
uses: softprops/action-gh-release@v2
@@ -128,6 +156,7 @@ jobs:
128156
129157
| File | Size | Requires .NET? |
130158
|------|------|----------------|
159+
| `ScreenGridSetup.exe` | ~66 MB | No (installer, auto-startup) |
131160
| `ScreenGrid-small.exe` | ~200 KB | Yes (.NET 9 Desktop Runtime) |
132161
| `ScreenGrid-standalone.exe` | ~70 MB | No |
133162
@@ -136,5 +165,6 @@ jobs:
136165
files: |
137166
ScreenGrid-small.exe
138167
ScreenGrid-standalone.exe
168+
ScreenGridSetup.exe
139169
prerelease: ${{ startsWith(github.ref_name, 'Beta') }}
140170
make_latest: ${{ !startsWith(github.ref_name, 'Beta') }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ bin/
33
obj/
44
publish/
55
publish-small/
6+
installer-output/
67

78
# IDE
89
.vs/

App.xaml.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,34 @@ private void SetupTrayIcon()
5757
menu.Items.Add(header);
5858
menu.Items.Add(new ToolStripSeparator());
5959
menu.Items.Add("Create / Edit Grid", null, (_, _) => ShowGridEditor());
60-
menu.Items.Add("Load Grid from File…", null, (_, _) => LoadGridFromFile()); menu.Items.Add("Reset Grid to Defaults", null, (_, _) => ResetGridToDefaults()); menu.Items.Add(new ToolStripSeparator());
60+
menu.Items.Add("Load Grid from File…", null, (_, _) => LoadGridFromFile());
61+
menu.Items.Add("Reset Grid to Defaults", null, (_, _) => ResetGridToDefaults());
62+
menu.Items.Add(new ToolStripSeparator());
63+
64+
var startupItem = new ToolStripMenuItem("Run at Startup")
65+
{
66+
CheckOnClick = true,
67+
Checked = StartupManager.IsRegistered()
68+
};
69+
startupItem.CheckedChanged += (_, _) =>
70+
{
71+
try
72+
{
73+
if (startupItem.Checked)
74+
StartupManager.Register();
75+
else
76+
StartupManager.Unregister();
77+
}
78+
catch (Exception ex)
79+
{
80+
MessageBox.Show($"Failed to update startup setting:\n{ex.Message}",
81+
"ScreenGrid", MessageBoxButton.OK, MessageBoxImage.Warning);
82+
startupItem.Checked = StartupManager.IsRegistered();
83+
}
84+
};
85+
menu.Items.Add(startupItem);
86+
87+
menu.Items.Add(new ToolStripSeparator());
6188
menu.Items.Add("How to use", null, (_, _) => ShowUsageInfo());
6289
menu.Items.Add(new ToolStripSeparator());
6390
menu.Items.Add("Exit", null, (_, _) => ExitApp());

README.md

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,31 @@ Built for displays like 5120×1440 where Windows' built-in snap (halves/quarters
1616
---
1717
## ⬇️ Download
1818

19-
> **[Download the latest release](https://github.com/TtesseractT/ScreenGrid/releases/latest)** no build tools needed, just run the `.exe`.
19+
> **[Download the latest release](https://github.com/TtesseractT/ScreenGrid/releases/latest)** - no build tools needed.
2020
21-
| File | Size | Requires .NET? |
22-
|------|------|----------------|
23-
| **ScreenGrid-standalone.exe** | ~70 MB | No — runs anywhere |
24-
| **ScreenGrid-small.exe** | ~200 KB | Yes — needs [.NET 9 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/9.0) |
21+
| File | Size | Description |
22+
|------|------|-------------|
23+
| **ScreenGridSetup.exe** | ~66 MB | **Installer** - installs to Program Files, adds to Windows startup, creates Start Menu & optional desktop shortcut. Includes uninstaller. |
24+
| **ScreenGrid-standalone.exe** | ~70 MB | Portable single-file exe, no .NET required |
25+
| **ScreenGrid-small.exe** | ~200 KB | Portable single-file exe, needs [.NET 9 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/9.0) |
2526

26-
Both are single-file executables. Download, run, and it appears in your system tray.
27+
**Recommended:** Use the installer for an automatic startup experience.
2728

2829
---
2930
## Features
3031

31-
- **Shift + Drag** to activate zero interference with normal window management
32+
- **Shift + Drag** to activate - zero interference with normal window management
3233
- **5 built-in grid rows**: Halves, Thirds, 4:3, Quarters, Fifths
3334
- **3 × 4:3 variants**: left, center, and right positions
3435
- **Height splits**: top/bottom halves and height thirds for partial-height zones
35-
- **Custom grids** create any ratio (2:1, 3:2:1, 16:9, etc.) via the built-in editor
36+
- **Custom grids** - create any ratio (2:1, 3:2:1, 16:9, etc.) via the built-in editor
3637
- **Save / Load** grid layouts as `.screengrid` JSON files
3738
- **Full-height snap preview** with pixel dimensions shown on hover
38-
- **DPI-aware** (PerMonitorV2) — works on mixed-DPI multi-monitor setups
39-
- **Click-through overlay** — never steals focus or interferes with your drag
40-
- **System tray only** — no visible window, runs silently in the background
39+
- **DPI-aware** (PerMonitorV2) - works on mixed-DPI multi-monitor setups
40+
- **Click-through overlay** - never steals focus or interferes with your drag
41+
- **System tray only** - no visible window, runs silently in the background
42+
- **Run at Startup** toggle - enable/disable from the tray menu or via the installer
43+
- **Windows installer** - proper install/uninstall with auto-startup support
4144
- **~200 KB** framework-dependent exe (or ~70 MB self-contained)
4245

4346
---
@@ -67,7 +70,7 @@ dotnet run -c Release
6770
| **Shift + Drag** a window | Grid overlay appears |
6871
| **Hover** over a zone | Zone highlights, full-height snap preview shown |
6972
| **Release mouse** on a zone | Window snaps to that column (full height) |
70-
| **Release Shift** while dragging | Cancel overlay hides, no snap |
73+
| **Release Shift** while dragging | Cancel - overlay hides, no snap |
7174

7275
### System Tray Menu (right-click)
7376

@@ -76,6 +79,7 @@ dotnet run -c Release
7679
| **Create / Edit Grid** | Open the grid editor to add, remove, reorder rows |
7780
| **Load Grid from File…** | Import a `.screengrid` JSON layout |
7881
| **Reset Grid to Defaults** | Restore all 5 built-in grid rows |
82+
| **Run at Startup** | Toggle Windows startup registration (checked = enabled) |
7983
| **How to use** | Quick usage guide |
8084
| **Exit** | Close ScreenGrid |
8185

@@ -86,8 +90,8 @@ dotnet run -c Release
8690
Right-click the tray icon → **Create / Edit Grid** to open the editor:
8791

8892
- Use **preset buttons** to quickly add common rows (Halves, Thirds, 4:3, etc.)
89-
- Click **+ Custom…** to enter any ratio e.g. `3:2:1` or `16:9`
90-
- **Reorder** rows with ▲/▼ top row appears at the top of the overlay
93+
- Click **+ Custom…** to enter any ratio - e.g. `3:2:1` or `16:9`
94+
- **Reorder** rows with ▲/▼ - top row appears at the top of the overlay
9195
- **Rename** rows to anything you like
9296
- Click **Apply & Close** to activate immediately
9397
- Click **Save to File…** to export and share your layout
@@ -174,13 +178,32 @@ dotnet publish -c Release -r win-x64 --no-self-contained -p:PublishSingleFile=tr
174178
dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o ./publish
175179
```
176180

181+
### Installer (~66 MB, includes auto-startup)
182+
183+
Requires [Inno Setup 6](https://jrsoftware.org/isinfo.php):
184+
185+
```powershell
186+
# First publish the self-contained exe (above), then:
187+
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer/ScreenGridSetup.iss
188+
# Output: installer-output/ScreenGridSetup.exe
189+
```
190+
177191
---
178192

179193
## Run at Windows Startup
180194

181-
1. Build or download the exe
182-
2. Press `Win+R` → type `shell:startup` → Enter
183-
3. Create a shortcut to `ScreenGrid.exe` in that folder
195+
**Option A: Use the installer** (recommended)
196+
197+
Download `ScreenGridSetup.exe` — during installation, the "Run at startup" option is checked by default.
198+
199+
**Option B: Toggle from the app**
200+
201+
Right-click the tray icon → check **Run at Startup**. This writes to `HKCU\...\Run` (current user only, no admin required).
202+
203+
**Option C: Manual shortcut**
204+
205+
1. Press `Win+R` → type `shell:startup` → Enter
206+
2. Create a shortcut to `ScreenGrid.exe` in that folder
184207

185208
---
186209

@@ -195,17 +218,19 @@ ScreenGrid/
195218
├── GridConfig.cs # Grid layout model, JSON serialization
196219
├── GridZone.cs # Individual snap zone model
197220
├── NativeMethods.cs # Win32 P/Invoke declarations
221+
├── StartupManager.cs # Windows startup registry management
198222
├── ScreenGrid.csproj # .NET 9 WPF project
199-
└── tests/ # xUnit test suite (54 tests)
223+
├── installer/ # Inno Setup installer script
224+
└── tests/ # xUnit test suite
200225
```
201226

202227
**Key Win32 APIs:**
203-
- `SetWinEventHook` detects window drag start/end system-wide
204-
- `GetAsyncKeyState` polls Shift key state at 60 fps
205-
- `GetCursorPos` tracks cursor position during drag
206-
- `MoveWindow` snaps the window to the target zone
207-
- `DwmGetWindowAttribute` compensates for invisible DWM borders
208-
- `GetMonitorInfo` / `GetDpiForMonitor` multi-monitor and DPI support
228+
- `SetWinEventHook` - detects window drag start/end system-wide
229+
- `GetAsyncKeyState` - polls Shift key state at 60 fps
230+
- `GetCursorPos` - tracks cursor position during drag
231+
- `MoveWindow` - snaps the window to the target zone
232+
- `DwmGetWindowAttribute` - compensates for invisible DWM borders
233+
- `GetMonitorInfo` / `GetDpiForMonitor` - multi-monitor and DPI support
209234

210235
---
211236

@@ -217,4 +242,4 @@ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines
217242

218243
## License
219244

220-
[MIT](LICENSE) free to use, modify, and distribute.
245+
[MIT](LICENSE) - free to use, modify, and distribute.

ScreenGrid.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<RootNamespace>ScreenGrid</RootNamespace>
1111
<AssemblyName>ScreenGrid</AssemblyName>
1212
<Nullable>enable</Nullable>
13+
<ApplicationIcon>docs\screengrid.ico</ApplicationIcon>
1314

1415
<!-- Publish optimization -->
1516
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>

StartupManager.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Diagnostics;
3+
using Microsoft.Win32;
4+
5+
namespace ScreenGrid
6+
{
7+
/// <summary>
8+
/// Manages Windows startup registration via the current-user Run registry key.
9+
/// </summary>
10+
internal static class StartupManager
11+
{
12+
private const string RunKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
13+
private const string AppName = "ScreenGrid";
14+
15+
/// <summary>Returns true if ScreenGrid is registered to run at Windows startup.</summary>
16+
public static bool IsRegistered()
17+
{
18+
try
19+
{
20+
using var key = Registry.CurrentUser.OpenSubKey(RunKey, false);
21+
return key?.GetValue(AppName) is string;
22+
}
23+
catch (Exception ex)
24+
{
25+
Debug.WriteLine($"StartupManager.IsRegistered error: {ex.Message}");
26+
return false;
27+
}
28+
}
29+
30+
/// <summary>Registers the current exe to run at Windows startup.</summary>
31+
public static void Register()
32+
{
33+
try
34+
{
35+
string exePath = Environment.ProcessPath
36+
?? Process.GetCurrentProcess().MainModule?.FileName
37+
?? throw new InvalidOperationException("Cannot determine exe path");
38+
39+
using var key = Registry.CurrentUser.OpenSubKey(RunKey, true)
40+
?? throw new InvalidOperationException("Cannot open Run registry key");
41+
42+
key.SetValue(AppName, $"\"{exePath}\"");
43+
}
44+
catch (Exception ex)
45+
{
46+
Debug.WriteLine($"StartupManager.Register error: {ex.Message}");
47+
throw;
48+
}
49+
}
50+
51+
/// <summary>Removes the startup registration.</summary>
52+
public static void Unregister()
53+
{
54+
try
55+
{
56+
using var key = Registry.CurrentUser.OpenSubKey(RunKey, true);
57+
if (key?.GetValue(AppName) != null)
58+
key.DeleteValue(AppName, false);
59+
}
60+
catch (Exception ex)
61+
{
62+
Debug.WriteLine($"StartupManager.Unregister error: {ex.Message}");
63+
throw;
64+
}
65+
}
66+
67+
/// <summary>Toggles the startup registration and returns the new state.</summary>
68+
public static bool Toggle()
69+
{
70+
if (IsRegistered())
71+
{
72+
Unregister();
73+
return false;
74+
}
75+
else
76+
{
77+
Register();
78+
return true;
79+
}
80+
}
81+
}
82+
}

docs/screengrid.ico

766 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)