Skip to content

Commit 7fb6ff7

Browse files
Merge release: installable, testable, ready to publish
MIT licence, an Inno Setup installer that removes the autostart task on uninstall while keeping the user's profile and settings, a single-instance mutex the installer also uses to notice a running copy, CI on every push and a release workflow that turns a vX.Y.Z tag into an installer, a portable build and checksums. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154ydCSH6y6ha5Dp3GUQHKA
2 parents 6c9990d + 41a6358 commit 7fb6ff7

22 files changed

Lines changed: 1450 additions & 33 deletions

.editorconfig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = crlf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.{cs,csproj,slnx}]
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.{json,yml,yaml,xaml,resx}]
14+
indent_style = space
15+
indent_size = 2
16+
17+
[*.md]
18+
trim_trailing_whitespace = false
19+
20+
[*.cs]
21+
csharp_new_line_before_open_brace = all
22+
csharp_style_namespace_declarations = file_scoped:warning
23+
csharp_style_var_when_type_is_apparent = true:suggestion
24+
dotnet_sort_system_directives_first = true
25+
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning

.github/ISSUE_TEMPLATE/bug.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Bug report
2+
description: Something does not work
3+
labels: [bug]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
MateFan only supports Huawei laptops. Reports about machines from other
9+
manufacturers will be closed — that is a deliberate limit, not an oversight.
10+
11+
- type: input
12+
id: model
13+
attributes:
14+
label: Machine
15+
description: The model MateFan shows, or the output of `Get-CimInstance Win32_ComputerSystemProduct | Select-Object Name`
16+
placeholder: FLMH-XX
17+
validations:
18+
required: true
19+
20+
- type: input
21+
id: version
22+
attributes:
23+
label: MateFan version
24+
placeholder: 1.0.0
25+
validations:
26+
required: true
27+
28+
- type: textarea
29+
id: what
30+
attributes:
31+
label: What happened
32+
description: What you did, what you expected, what happened instead.
33+
validations:
34+
required: true
35+
36+
- type: textarea
37+
id: log
38+
attributes:
39+
label: Log
40+
description: |
41+
The tail of `%LOCALAPPDATA%\MateFan\matefan.log`. It contains no personal data
42+
beyond the model name. Without it most reports cannot be acted on.
43+
render: text
44+
validations:
45+
required: false

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
blank_issues_enabled: true
2+
contact_links:
3+
- name: Question about how MateFan works
4+
url: https://github.com/shockwave-coder/matefan/blob/main/docs/discovery.md
5+
about: The protocol notes answer most questions about the firmware interface.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Device profile
2+
description: Contribute the measurements for a Huawei machine MateFan does not know yet
3+
title: "Device profile: "
4+
labels: [device-profile]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Thank you — this is the contribution that makes MateFan work for the next person
10+
with your laptop, without them having to sit through the measurement.
11+
12+
Run the calibration wizard, then press **Copy as JSON** on the result page and
13+
paste it below.
14+
15+
- type: textarea
16+
id: profile
17+
attributes:
18+
label: The profile, as copied from the wizard
19+
render: json
20+
placeholder: |
21+
{
22+
"model": "FLMH-XX",
23+
"displayName": null,
24+
"fanCount": 2,
25+
"minRpm": 2000,
26+
"maxRpm": 8300,
27+
"cpuSensorIndex": 0
28+
}
29+
validations:
30+
required: true
31+
32+
- type: input
33+
id: marketing
34+
attributes:
35+
label: What is this laptop called in shops?
36+
description: Becomes `displayName`, so the model code is not the only thing people see.
37+
placeholder: MateBook 14 (2024)
38+
validations:
39+
required: false
40+
41+
- type: checkboxes
42+
id: sanity
43+
attributes:
44+
label: Before submitting
45+
options:
46+
- label: The fans really do reach the maximum speed in the profile (set it by hand and listen).
47+
required: true
48+
- label: The temperature MateFan shows matches what another tool reports for the CPU.
49+
required: true

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: windows-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-dotnet@v4
16+
with:
17+
dotnet-version: '10.0.x'
18+
19+
- name: Restore
20+
run: dotnet restore matefan.slnx
21+
22+
- name: Build
23+
run: dotnet build matefan.slnx --configuration Release --no-restore
24+
25+
# The suite is hardware-free on purpose: no WMI, no schtasks, no Process.Start.
26+
# That is what lets it run on a runner at all.
27+
- name: Test
28+
run: dotnet test tests/MateFan.Tests --configuration Release --no-build --logger "console;verbosity=normal"

.github/workflows/release.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
release:
12+
runs-on: windows-latest
13+
14+
env:
15+
# Steps that need a certificate check this instead of `secrets` directly,
16+
# which is not available in every `if` context.
17+
HAS_CERT: ${{ secrets.CODE_SIGNING_PFX != '' }}
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- uses: actions/setup-dotnet@v4
23+
with:
24+
dotnet-version: '10.0.x'
25+
26+
# The tag is the single source of the version number. There is deliberately no
27+
# version in any project file that could drift away from it.
28+
- name: Derive the version from the tag
29+
id: version
30+
shell: pwsh
31+
run: |
32+
$version = '${{ github.ref_name }}' -replace '^v', ''
33+
if ($version -notmatch '^\d+\.\d+\.\d+$') {
34+
throw "Tag '${{ github.ref_name }}' is not vMAJOR.MINOR.PATCH"
35+
}
36+
"value=$version" >> $env:GITHUB_OUTPUT
37+
38+
- name: Test
39+
run: dotnet test tests/MateFan.Tests --configuration Release
40+
41+
# Self-contained but not single-file: the installer owns a folder, so there is
42+
# nothing to unpack at every start.
43+
- name: Publish the installer payload
44+
run: >
45+
dotnet publish src/MateFan.App --configuration Release --runtime win-x64
46+
--self-contained true -p:PublishSingleFile=false
47+
-p:Version=${{ steps.version.outputs.value }}
48+
--output publish/installer
49+
50+
# Portable: here self-extraction is the fair price for being one file.
51+
- name: Publish the portable build
52+
run: >
53+
dotnet publish src/MateFan.App --configuration Release --runtime win-x64
54+
--self-contained true -p:PublishSingleFile=true
55+
-p:IncludeNativeLibrariesForSelfExtract=true
56+
-p:Version=${{ steps.version.outputs.value }}
57+
--output publish/portable
58+
59+
- name: Build the installer
60+
shell: pwsh
61+
run: |
62+
# Inno Setup lands in a different place depending on how it was installed: the
63+
# runner image ships it under Program Files (x86), chocolatey agrees, but a
64+
# per-user winget install puts it under LOCALAPPDATA. Look in all three rather
65+
# than hard-coding one and finding out on release day.
66+
$candidates = @(
67+
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
68+
"$env:ProgramFiles\Inno Setup 6\ISCC.exe",
69+
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe"
70+
)
71+
$iscc = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
72+
if (-not $iscc) {
73+
choco install innosetup -y --no-progress
74+
$iscc = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
75+
}
76+
if (-not $iscc) {
77+
throw "ISCC.exe not found in any of: $($candidates -join ', ')"
78+
}
79+
Write-Host "Using $iscc"
80+
& $iscc /DAppVersion=${{ steps.version.outputs.value }} installer\matefan.iss
81+
if ($LASTEXITCODE -ne 0) { throw "ISCC failed with $LASTEXITCODE" }
82+
83+
- name: Collect the release files
84+
shell: pwsh
85+
run: |
86+
New-Item -ItemType Directory -Force dist | Out-Null
87+
$v = '${{ steps.version.outputs.value }}'
88+
Copy-Item "installer\Output\MateFan-Setup-$v.exe" dist\
89+
Copy-Item "publish\portable\MateFan.exe" "dist\MateFan-$v-portable.exe"
90+
91+
- name: Sign
92+
if: env.HAS_CERT == 'true'
93+
shell: pwsh
94+
env:
95+
PFX: ${{ secrets.CODE_SIGNING_PFX }}
96+
PFX_PASSWORD: ${{ secrets.CODE_SIGNING_PASSWORD }}
97+
run: |
98+
[IO.File]::WriteAllBytes('cert.pfx', [Convert]::FromBase64String($env:PFX))
99+
Get-ChildItem dist\*.exe | ForEach-Object {
100+
& signtool sign /f cert.pfx /p $env:PFX_PASSWORD /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 $_.FullName
101+
if ($LASTEXITCODE -ne 0) { throw "signtool failed for $($_.Name)" }
102+
}
103+
Remove-Item cert.pfx
104+
105+
# Checksums come last so they cover the signed files when signing is on.
106+
- name: Write the checksums
107+
shell: pwsh
108+
run: |
109+
Get-ChildItem dist\*.exe |
110+
Get-FileHash -Algorithm SHA256 |
111+
ForEach-Object { "$($_.Hash.ToLower()) $(Split-Path $_.Path -Leaf)" } |
112+
Set-Content dist\SHA256SUMS.txt -Encoding ascii
113+
114+
- name: Create the release
115+
shell: pwsh
116+
env:
117+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118+
run: |
119+
gh release create '${{ github.ref_name }}' (Get-ChildItem dist\* | ForEach-Object FullName) `
120+
--title 'MateFan ${{ github.ref_name }}' `
121+
--generate-notes

.gitignore

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
1-
analysis/
2-
*.aml
3-
*.dsl
1+
# Build output
42
bin/
53
obj/
64
publish/
5+
publish-standalone/
6+
installer/Output/
7+
# Release staging, built by .github/workflows/release.yml
8+
dist/
9+
10+
# IDE
11+
.vs/
712
*.user
13+
*.suo
14+
15+
# Scratch space for the subagent-driven execution of these plans
816
.superpowers/
17+
18+
# The German design and planning documents stay local. They carry the author's name,
19+
# home directory and internal decision notes, none of which belong in a public repo.
20+
docs/superpowers/
21+
22+
# Analysis: the measurements are published because docs/discovery.md cites them.
23+
# The ACPI dumps are not: 4.5 MB of Huawei firmware, and anyone can produce their
24+
# own with tools/Dump-AcpiTables.ps1.
25+
analysis/*
26+
!analysis/*.csv
27+
!analysis/*.jsonl
28+
!analysis/*.txt
29+
30+
# Belt and braces: Dump-AcpiTables.ps1 defaults to analysis/acpi, but -OutDir points
31+
# anywhere, and a stray firmware dump is exactly what the rule above exists to stop.
32+
*.aml
33+
*.dsl

0 commit comments

Comments
 (0)