多语支持
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,4 +4,9 @@ obj/
|
||||
*.user
|
||||
*.suo
|
||||
dist/
|
||||
dist-svc/
|
||||
dist-installer/
|
||||
*.log
|
||||
wix/obj/
|
||||
wix/Harvested.wxs
|
||||
wix/Harvested-Service.wxs
|
||||
|
||||
@@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChromeboxFanControl", "ChromeboxFanControl\ChromeboxFanControl.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChromeboxFanControlService", "ChromeboxFanControlService\ChromeboxFanControlService.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -13,5 +15,9 @@ Global
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace ChromeboxFanControl;
|
||||
|
||||
public sealed class AppConfig
|
||||
{
|
||||
/// <summary>UI language: "auto" (system), "en", "zh-Hans", "zh-Hant". Restart to apply.</summary>
|
||||
public string Language { get; set; } = "auto";
|
||||
public string EctoolPath { get; set; } = @"C:\Program Files\crosec\ectool.exe";
|
||||
public int PollIntervalMs { get; set; } = 1500;
|
||||
/// <summary>Call ectool for RPM every N control cycles (1 = every cycle).</summary>
|
||||
@@ -33,7 +35,9 @@ public sealed class AppConfig
|
||||
|
||||
public static string UserConfigPath =>
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
Environment.UserInteractive
|
||||
? Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
|
||||
: Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"ChromeboxFanControl",
|
||||
"config.json");
|
||||
|
||||
@@ -66,6 +70,8 @@ public sealed class AppConfig
|
||||
if (src == null)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(src.Language))
|
||||
dst.Language = src.Language;
|
||||
dst.EctoolPath = src.EctoolPath;
|
||||
dst.PollIntervalMs = Math.Clamp(src.PollIntervalMs, 250, 60_000);
|
||||
dst.FanRpmPollEveryNCycles = Math.Max(1, src.FanRpmPollEveryNCycles);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -24,4 +25,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en;zh-Hans;zh-Hant</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using ChromeboxFanControl.Properties;
|
||||
using ScottPlot;
|
||||
using ScottPlot.WinForms;
|
||||
|
||||
@@ -18,6 +19,7 @@ public sealed class MainForm : Form
|
||||
private readonly TextBox _txtFanDutyArgs = new();
|
||||
private readonly TextBox _txtAutoFanArgs = new();
|
||||
private readonly ComboBox _cmbTempSource = new();
|
||||
private readonly ComboBox _cmbLanguage = new();
|
||||
private readonly NumericUpDown _nudFailCount = new();
|
||||
private readonly NumericUpDown _nudFailPct = new();
|
||||
private readonly CheckBox _chkFailAuto = new();
|
||||
@@ -25,7 +27,7 @@ public sealed class MainForm : Form
|
||||
private readonly NumericUpDown _nudChartPoints = new();
|
||||
private readonly NotifyIcon _tray = new();
|
||||
private readonly ContextMenuStrip _trayMenu = new();
|
||||
private readonly ToolStripMenuItem _miPause = new("暂停控制");
|
||||
private readonly ToolStripMenuItem _miPause;
|
||||
private readonly object _seriesLock = new();
|
||||
private readonly List<double> _tSec = new();
|
||||
private readonly List<double> _temps = new();
|
||||
@@ -37,7 +39,8 @@ public sealed class MainForm : Form
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
Text = "Chromebox 风扇温控";
|
||||
_miPause = new ToolStripMenuItem(Resources.TrayPause);
|
||||
Text = Resources.AppTitle;
|
||||
Size = new Size(960, 700);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
MinimumSize = new Size(800, 500);
|
||||
@@ -62,9 +65,9 @@ public sealed class MainForm : Form
|
||||
{
|
||||
var tabs = new TabControl { Dock = DockStyle.Fill };
|
||||
|
||||
var tabMonitor = new TabPage("监控");
|
||||
var tabCurve = new TabPage("曲线");
|
||||
var tabAdv = new TabPage("高级");
|
||||
var tabMonitor = new TabPage(Resources.TabMonitor);
|
||||
var tabCurve = new TabPage(Resources.TabCurve);
|
||||
var tabAdv = new TabPage(Resources.TabAdvanced);
|
||||
|
||||
tabs.TabPages.Add(tabMonitor);
|
||||
tabs.TabPages.Add(tabCurve);
|
||||
@@ -75,7 +78,7 @@ public sealed class MainForm : Form
|
||||
_lblStatus.Dock = DockStyle.Bottom;
|
||||
_lblStatus.Padding = new Padding(8, 4, 8, 4);
|
||||
_lblStatus.AutoEllipsis = true;
|
||||
_lblStatus.Text = "启动中…";
|
||||
_lblStatus.Text = Resources.StatusStarting;
|
||||
|
||||
var panelChart = new Panel { Dock = DockStyle.Fill };
|
||||
panelChart.Controls.Add(_chart);
|
||||
@@ -91,14 +94,14 @@ public sealed class MainForm : Form
|
||||
_gridCurve.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colTemp",
|
||||
HeaderText = "温度 (°C)",
|
||||
HeaderText = Resources.GridColTemp,
|
||||
ReadOnly = true,
|
||||
Width = 80
|
||||
});
|
||||
_gridCurve.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colDuty",
|
||||
HeaderText = "占空比 0–100",
|
||||
HeaderText = Resources.GridColDuty,
|
||||
Width = 100
|
||||
});
|
||||
for (var i = 0; i < 14; i++)
|
||||
@@ -112,9 +115,9 @@ public sealed class MainForm : Form
|
||||
FlowDirection = FlowDirection.LeftToRight,
|
||||
Padding = new Padding(8)
|
||||
};
|
||||
var btnSave = new Button { Text = "保存并应用", AutoSize = true };
|
||||
var btnSave = new Button { Text = Resources.BtnSaveApply, AutoSize = true };
|
||||
btnSave.Click += (_, _) => SaveFromUi();
|
||||
var btnDefault = new Button { Text = "恢复默认曲线", AutoSize = true };
|
||||
var btnDefault = new Button { Text = Resources.BtnRestoreDefault, AutoSize = true };
|
||||
btnDefault.Click += (_, _) =>
|
||||
{
|
||||
var def = new AppConfig();
|
||||
@@ -138,41 +141,45 @@ public sealed class MainForm : Form
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 12,
|
||||
RowCount = 13,
|
||||
Padding = new Padding(12)
|
||||
};
|
||||
flp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 35));
|
||||
flp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 65));
|
||||
|
||||
var row = 0;
|
||||
AddRow(flp, row++, "ectool 路径", _txtEctool);
|
||||
AddRow(flp, row++, "轮询间隔 (ms)", _nudPoll);
|
||||
AddRow(flp, row++, Resources.LblLanguage, _cmbLanguage);
|
||||
_cmbLanguage.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
_cmbLanguage.Items.AddRange([Resources.LangAuto, Resources.LangEn, Resources.LangZhHans, Resources.LangZhHant]);
|
||||
|
||||
AddRow(flp, row++, Resources.LblEctoolPath, _txtEctool);
|
||||
AddRow(flp, row++, Resources.LblPollInterval, _nudPoll);
|
||||
_nudPoll.Minimum = 250;
|
||||
_nudPoll.Maximum = 60_000;
|
||||
_nudPoll.Increment = 250;
|
||||
|
||||
AddRow(flp, row++, "每 N 轮读 RPM", _nudRpmEvery);
|
||||
AddRow(flp, row++, Resources.LblRpmEvery, _nudRpmEvery);
|
||||
_nudRpmEvery.Minimum = 1;
|
||||
_nudRpmEvery.Maximum = 100;
|
||||
|
||||
AddRow(flp, row++, "读转速参数 (空格分隔)", _txtFanRpmArgs);
|
||||
AddRow(flp, row++, "读占空比参数 (空=跳过)", _txtFanDutyArgs);
|
||||
AddRow(flp, row++, Resources.LblFanRpmArgs, _txtFanRpmArgs);
|
||||
AddRow(flp, row++, Resources.LblFanDutyArgs, _txtFanDutyArgs);
|
||||
|
||||
AddRow(flp, row++, "恢复自动风扇参数", _txtAutoFanArgs);
|
||||
AddRow(flp, row++, Resources.LblAutoFanArgs, _txtAutoFanArgs);
|
||||
|
||||
AddRow(flp, row++, "温度来源", _cmbTempSource);
|
||||
AddRow(flp, row++, Resources.LblTempSource, _cmbTempSource);
|
||||
_cmbTempSource.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
_cmbTempSource.Items.AddRange(["AverageCore", "MaxCore"]);
|
||||
|
||||
AddRow(flp, row++, "连续失败进入安全模式 (次)", _nudFailCount);
|
||||
AddRow(flp, row++, Resources.LblFailCount, _nudFailCount);
|
||||
_nudFailCount.Minimum = 1;
|
||||
_nudFailCount.Maximum = 100;
|
||||
|
||||
AddRow(flp, row++, "安全模式固定占空比 (%)", _nudFailPct);
|
||||
AddRow(flp, row++, Resources.LblFailPct, _nudFailPct);
|
||||
_nudFailPct.Minimum = 0;
|
||||
_nudFailPct.Maximum = 100;
|
||||
|
||||
_chkFailAuto.Text = "安全模式使用 autofanctrl(不用固定占空比)";
|
||||
_chkFailAuto.Text = Resources.ChkFailAuto;
|
||||
_chkFailAuto.AutoSize = true;
|
||||
var failPanel = new FlowLayoutPanel
|
||||
{
|
||||
@@ -182,19 +189,19 @@ public sealed class MainForm : Form
|
||||
WrapContents = false
|
||||
};
|
||||
failPanel.Controls.Add(_chkFailAuto);
|
||||
flp.Controls.Add(new System.Windows.Forms.Label { Text = "安全模式策略", AutoSize = true, Anchor = AnchorStyles.Left }, 0, row);
|
||||
flp.Controls.Add(new System.Windows.Forms.Label { Text = Resources.LblFailStrategy, AutoSize = true, Anchor = AnchorStyles.Left }, 0, row);
|
||||
flp.Controls.Add(failPanel, 1, row++);
|
||||
|
||||
AddRow(flp, row++, "图表保留时长 (分钟)", _nudChartMin);
|
||||
AddRow(flp, row++, Resources.LblChartMin, _nudChartMin);
|
||||
_nudChartMin.Minimum = 1;
|
||||
_nudChartMin.Maximum = 120;
|
||||
|
||||
AddRow(flp, row++, "图表最大点数", _nudChartPoints);
|
||||
AddRow(flp, row++, Resources.LblChartPoints, _nudChartPoints);
|
||||
_nudChartPoints.Minimum = 100;
|
||||
_nudChartPoints.Maximum = 10_000;
|
||||
_nudChartPoints.Increment = 100;
|
||||
|
||||
var btnSaveAdv = new Button { Text = "保存高级设置", AutoSize = true, Dock = DockStyle.Bottom };
|
||||
var btnSaveAdv = new Button { Text = Resources.BtnSaveAdvanced, AutoSize = true, Dock = DockStyle.Bottom };
|
||||
btnSaveAdv.Click += (_, _) => SaveFromUi();
|
||||
tabAdv.Controls.Add(btnSaveAdv);
|
||||
tabAdv.Controls.Add(flp);
|
||||
@@ -211,7 +218,7 @@ public sealed class MainForm : Form
|
||||
|
||||
private void WireTray()
|
||||
{
|
||||
_tray.Text = "Chromebox 风扇温控";
|
||||
_tray.Text = Resources.AppTitle;
|
||||
_tray.Visible = true;
|
||||
_tray.Icon = SystemIcons.Application;
|
||||
_tray.DoubleClick += (_, _) => ShowFromTray();
|
||||
@@ -219,13 +226,11 @@ public sealed class MainForm : Form
|
||||
{
|
||||
TogglePause();
|
||||
};
|
||||
_trayMenu.Items.Add("打开主窗口", null, (_, _) => ShowFromTray());
|
||||
_trayMenu.Items.Add(Resources.TrayOpen, null, (_, _) => ShowFromTray());
|
||||
_trayMenu.Items.Add(_miPause);
|
||||
_trayMenu.Items.Add("关于", null, (_, _) =>
|
||||
MessageBox.Show(this,
|
||||
"Chromebox 风扇温控\n\n使用 LibreHardwareMonitor 读取 CPU 温度,通过 crosec ectool 控制风扇。\n\n请以管理员身份运行。",
|
||||
Text, MessageBoxButtons.OK, MessageBoxIcon.Information));
|
||||
_trayMenu.Items.Add("退出", null, (_, _) => Close());
|
||||
_trayMenu.Items.Add(Resources.TrayAbout, null, (_, _) =>
|
||||
MessageBox.Show(this, Resources.AboutMessage, Text, MessageBoxButtons.OK, MessageBoxIcon.Information));
|
||||
_trayMenu.Items.Add(Resources.TrayExit, null, (_, _) => Close());
|
||||
_tray.ContextMenuStrip = _trayMenu;
|
||||
}
|
||||
|
||||
@@ -242,11 +247,16 @@ public sealed class MainForm : Form
|
||||
if (_fan == null)
|
||||
return;
|
||||
_fan.Paused = !_fan.Paused;
|
||||
_miPause.Text = _fan.Paused ? "恢复控制" : "暂停控制";
|
||||
_miPause.Text = _fan.Paused ? Resources.TrayResume : Resources.TrayPause;
|
||||
}
|
||||
|
||||
private static readonly string[] LanguageValues = ["auto", "en", "zh-Hans", "zh-Hant"];
|
||||
|
||||
private void ApplyConfigToUi()
|
||||
{
|
||||
var langIdx = Array.IndexOf(LanguageValues, _config.Language);
|
||||
_cmbLanguage.SelectedIndex = langIdx >= 0 ? langIdx : 0;
|
||||
|
||||
_txtEctool.Text = _config.EctoolPath;
|
||||
_nudPoll.Value = _config.PollIntervalMs;
|
||||
_nudRpmEvery.Value = _config.FanRpmPollEveryNCycles;
|
||||
@@ -268,8 +278,12 @@ public sealed class MainForm : Form
|
||||
|
||||
private AppConfig CloneConfigFromUi()
|
||||
{
|
||||
var langIdx = _cmbLanguage.SelectedIndex;
|
||||
var lang = langIdx >= 0 && langIdx < LanguageValues.Length ? LanguageValues[langIdx] : "auto";
|
||||
|
||||
var c = new AppConfig
|
||||
{
|
||||
Language = lang,
|
||||
EctoolPath = _txtEctool.Text.Trim(),
|
||||
PollIntervalMs = (int)_nudPoll.Value,
|
||||
FanRpmPollEveryNCycles = (int)_nudRpmEvery.Value,
|
||||
@@ -313,8 +327,8 @@ public sealed class MainForm : Form
|
||||
|
||||
var plt = _curveChart.Plot;
|
||||
plt.Clear();
|
||||
plt.Axes.Left.Label.Text = "占空比 (%)";
|
||||
plt.Axes.Bottom.Label.Text = "温度 (°C)";
|
||||
plt.Axes.Left.Label.Text = Resources.CurveChartDuty;
|
||||
plt.Axes.Bottom.Label.Text = Resources.CurveChartTemp;
|
||||
plt.Axes.SetLimitsX(0, 100);
|
||||
plt.Axes.SetLimitsY(0, 100);
|
||||
|
||||
@@ -326,6 +340,7 @@ public sealed class MainForm : Form
|
||||
scatter.MarkerSize = 6;
|
||||
}
|
||||
|
||||
plt.Font.Automatic();
|
||||
_curveChart.Refresh();
|
||||
}
|
||||
|
||||
@@ -336,21 +351,25 @@ public sealed class MainForm : Form
|
||||
var rpmArgs = SplitArgs(_txtFanRpmArgs.Text);
|
||||
if (rpmArgs.Length == 0)
|
||||
{
|
||||
MessageBox.Show(this, "读转速参数不能为空。", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
MessageBox.Show(this, Resources.ErrRpmArgsEmpty, Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var autoArgs = SplitArgs(_txtAutoFanArgs.Text);
|
||||
if (autoArgs.Length == 0)
|
||||
{
|
||||
MessageBox.Show(this, "恢复自动风扇参数不能为空。", Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
MessageBox.Show(this, Resources.ErrAutoFanArgsEmpty, Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var langBefore = _config.Language;
|
||||
_config = CloneConfigFromUi();
|
||||
_config.SaveUser();
|
||||
_fan?.UpdateConfig(_config);
|
||||
MessageBox.Show(this, "已保存。", Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
var msg = Resources.MsgSaved;
|
||||
if (langBefore != _config.Language)
|
||||
msg = Resources.MsgRestartToApplyLanguage;
|
||||
MessageBox.Show(this, msg, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -388,11 +407,11 @@ public sealed class MainForm : Form
|
||||
var dVal = e.ActualDutyPercent ?? e.TargetDutyPercent;
|
||||
var dStr = dVal.HasValue ? $"{dVal} %" : "—";
|
||||
var rStr = e.Rpm.HasValue ? $"{e.Rpm} RPM" : "—";
|
||||
var state = e.Paused ? "已暂停" : e.FailSafe ? "安全模式" : "运行中";
|
||||
var state = e.Paused ? Resources.StatusPaused : e.FailSafe ? Resources.StatusFailSafe : Resources.StatusRunning;
|
||||
_lblStatus.Text =
|
||||
$"{state} 温度: {tStr} 目标占空比: {dStr} 转速: {rStr} ectool: {e.LastEctoolMessage ?? "—"}";
|
||||
$"{state} {Resources.LabelTemp} {tStr} {Resources.LabelTargetDuty} {dStr} {Resources.LabelRpm} {rStr} ectool: {e.LastEctoolMessage ?? "—"}";
|
||||
|
||||
_tray.Text = $"Chromebox {tStr} {dStr}";
|
||||
_tray.Text = $"{Resources.AppTitle} {tStr} {dStr}";
|
||||
|
||||
RebuildChart();
|
||||
}
|
||||
@@ -435,14 +454,14 @@ public sealed class MainForm : Form
|
||||
var plt = _chart.Plot;
|
||||
plt.Clear();
|
||||
|
||||
plt.Axes.Left.Label.Text = "Temperature (°C)";
|
||||
plt.Axes.Bottom.Label.Text = "Time (s)";
|
||||
plt.Axes.Left.Label.Text = Resources.ChartTemp;
|
||||
plt.Axes.Bottom.Label.Text = Resources.ChartTime;
|
||||
plt.Axes.Right.IsVisible = true;
|
||||
plt.Axes.Right.Label.Text = "Duty (%)";
|
||||
plt.Axes.Right.Label.Text = Resources.ChartDuty;
|
||||
|
||||
if (_yRpmAxis == null)
|
||||
_yRpmAxis = plt.Axes.AddRightAxis();
|
||||
_yRpmAxis.Label.Text = "RPM";
|
||||
_yRpmAxis.Label.Text = Resources.ChartRpm;
|
||||
|
||||
if (x.Length > 0)
|
||||
{
|
||||
@@ -509,12 +528,13 @@ public sealed class MainForm : Form
|
||||
|
||||
LegendItem[] leg =
|
||||
[
|
||||
new() { LineColor = Colors.Blue, LineWidth = 2, LabelText = "°C" },
|
||||
new() { LineColor = Colors.Orange, LineWidth = 2, LabelText = "Duty %" },
|
||||
new() { LineColor = Colors.Green, LineWidth = 2, LabelText = "RPM" }
|
||||
new() { LineColor = Colors.Blue, LineWidth = 2, LabelText = Resources.LegendCelsius },
|
||||
new() { LineColor = Colors.Orange, LineWidth = 2, LabelText = Resources.LegendDuty },
|
||||
new() { LineColor = Colors.Green, LineWidth = 2, LabelText = Resources.ChartRpm }
|
||||
];
|
||||
plt.ShowLegend(leg, Alignment.UpperRight);
|
||||
plt.Legend.Orientation = ScottPlot.Orientation.Horizontal;
|
||||
plt.Font.Automatic();
|
||||
_chart.Refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,22 @@ internal static class Program
|
||||
private static void Main()
|
||||
{
|
||||
ApplicationConfiguration.Initialize();
|
||||
|
||||
var config = AppConfig.LoadMerged();
|
||||
var lang = config.Language;
|
||||
if (string.IsNullOrEmpty(lang) || lang.Equals("auto", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var culture = System.Globalization.CultureInfo.CurrentUICulture;
|
||||
if (culture.Name is "zh-TW" or "zh-HK" or "zh-MO" or "zh-Hant")
|
||||
lang = "zh-Hant";
|
||||
else if (culture.Name.StartsWith("zh", StringComparison.OrdinalIgnoreCase))
|
||||
lang = "zh-Hans";
|
||||
else
|
||||
lang = "en";
|
||||
}
|
||||
|
||||
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(lang);
|
||||
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
|
||||
63
ChromeboxFanControl/Properties/Resources.Designer.cs
generated
Normal file
63
ChromeboxFanControl/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Resources;
|
||||
|
||||
namespace ChromeboxFanControl.Properties;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _rm = new(
|
||||
"ChromeboxFanControl.Properties.Resources",
|
||||
typeof(Resources).Assembly);
|
||||
|
||||
public static string AppTitle => _rm.GetString("AppTitle") ?? "Chromebox Fan Control";
|
||||
public static string TabMonitor => _rm.GetString("TabMonitor") ?? "Monitoring";
|
||||
public static string TabCurve => _rm.GetString("TabCurve") ?? "Curve";
|
||||
public static string TabAdvanced => _rm.GetString("TabAdvanced") ?? "Advanced";
|
||||
public static string StatusStarting => _rm.GetString("StatusStarting") ?? "Starting…";
|
||||
public static string StatusRunning => _rm.GetString("StatusRunning") ?? "Running";
|
||||
public static string StatusPaused => _rm.GetString("StatusPaused") ?? "Paused";
|
||||
public static string StatusFailSafe => _rm.GetString("StatusFailSafe") ?? "Fail-safe mode";
|
||||
public static string GridColTemp => _rm.GetString("GridColTemp") ?? "Temp (°C)";
|
||||
public static string GridColDuty => _rm.GetString("GridColDuty") ?? "Duty 0–100";
|
||||
public static string BtnSaveApply => _rm.GetString("BtnSaveApply") ?? "Save & Apply";
|
||||
public static string BtnRestoreDefault => _rm.GetString("BtnRestoreDefault") ?? "Restore Default";
|
||||
public static string LblEctoolPath => _rm.GetString("LblEctoolPath") ?? "ectool path";
|
||||
public static string LblPollInterval => _rm.GetString("LblPollInterval") ?? "Poll interval (ms)";
|
||||
public static string LblRpmEvery => _rm.GetString("LblRpmEvery") ?? "Read RPM every N";
|
||||
public static string LblFanRpmArgs => _rm.GetString("LblFanRpmArgs") ?? "RPM args";
|
||||
public static string LblFanDutyArgs => _rm.GetString("LblFanDutyArgs") ?? "Duty args";
|
||||
public static string LblAutoFanArgs => _rm.GetString("LblAutoFanArgs") ?? "Auto fan args";
|
||||
public static string LblTempSource => _rm.GetString("LblTempSource") ?? "Temp source";
|
||||
public static string LblFailCount => _rm.GetString("LblFailCount") ?? "Fail count";
|
||||
public static string LblFailPct => _rm.GetString("LblFailPct") ?? "Fail-safe %";
|
||||
public static string ChkFailAuto => _rm.GetString("ChkFailAuto") ?? "Use autofanctrl";
|
||||
public static string LblFailStrategy => _rm.GetString("LblFailStrategy") ?? "Fail-safe strategy";
|
||||
public static string LblChartMin => _rm.GetString("LblChartMin") ?? "Chart minutes";
|
||||
public static string LblChartPoints => _rm.GetString("LblChartPoints") ?? "Chart points";
|
||||
public static string BtnSaveAdvanced => _rm.GetString("BtnSaveAdvanced") ?? "Save Advanced";
|
||||
public static string TrayOpen => _rm.GetString("TrayOpen") ?? "Show window";
|
||||
public static string TrayPause => _rm.GetString("TrayPause") ?? "Pause";
|
||||
public static string TrayResume => _rm.GetString("TrayResume") ?? "Resume";
|
||||
public static string TrayAbout => _rm.GetString("TrayAbout") ?? "About";
|
||||
public static string TrayExit => _rm.GetString("TrayExit") ?? "Exit";
|
||||
public static string AboutMessage => _rm.GetString("AboutMessage") ?? "";
|
||||
public static string ErrRpmArgsEmpty => _rm.GetString("ErrRpmArgsEmpty") ?? "RPM args required.";
|
||||
public static string ErrAutoFanArgsEmpty => _rm.GetString("ErrAutoFanArgsEmpty") ?? "Auto fan args required.";
|
||||
public static string MsgSaved => _rm.GetString("MsgSaved") ?? "Saved.";
|
||||
public static string LabelTemp => _rm.GetString("LabelTemp") ?? "Temp:";
|
||||
public static string LabelTargetDuty => _rm.GetString("LabelTargetDuty") ?? "Target duty:";
|
||||
public static string LabelRpm => _rm.GetString("LabelRpm") ?? "RPM:";
|
||||
public static string ChartTemp => _rm.GetString("ChartTemp") ?? "Temperature (°C)";
|
||||
public static string ChartTime => _rm.GetString("ChartTime") ?? "Time (s)";
|
||||
public static string ChartDuty => _rm.GetString("ChartDuty") ?? "Duty (%)";
|
||||
public static string ChartRpm => _rm.GetString("ChartRpm") ?? "RPM";
|
||||
public static string LegendCelsius => _rm.GetString("LegendCelsius") ?? "°C";
|
||||
public static string LegendDuty => _rm.GetString("LegendDuty") ?? "Duty %";
|
||||
public static string CurveChartDuty => _rm.GetString("CurveChartDuty") ?? "Duty (%)";
|
||||
public static string CurveChartTemp => _rm.GetString("CurveChartTemp") ?? "Temp (°C)";
|
||||
public static string LblLanguage => _rm.GetString("LblLanguage") ?? "Language";
|
||||
public static string LangAuto => _rm.GetString("LangAuto") ?? "Auto";
|
||||
public static string LangEn => _rm.GetString("LangEn") ?? "English";
|
||||
public static string LangZhHans => _rm.GetString("LangZhHans") ?? "Simplified Chinese";
|
||||
public static string LangZhHant => _rm.GetString("LangZhHant") ?? "Traditional Chinese";
|
||||
public static string MsgRestartToApplyLanguage => _rm.GetString("MsgRestartToApplyLanguage") ?? "Restart to apply.";
|
||||
}
|
||||
117
ChromeboxFanControl/Properties/Resources.resx
Normal file
117
ChromeboxFanControl/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AppTitle" xml:space="preserve"><value>Chromebox Fan Control</value></data>
|
||||
<data name="TabMonitor" xml:space="preserve"><value>Monitoring</value></data>
|
||||
<data name="TabCurve" xml:space="preserve"><value>Curve</value></data>
|
||||
<data name="TabAdvanced" xml:space="preserve"><value>Advanced</value></data>
|
||||
<data name="StatusStarting" xml:space="preserve"><value>Starting…</value></data>
|
||||
<data name="StatusRunning" xml:space="preserve"><value>Running</value></data>
|
||||
<data name="StatusPaused" xml:space="preserve"><value>Paused</value></data>
|
||||
<data name="StatusFailSafe" xml:space="preserve"><value>Fail-safe mode</value></data>
|
||||
<data name="GridColTemp" xml:space="preserve"><value>Temp (°C)</value></data>
|
||||
<data name="GridColDuty" xml:space="preserve"><value>Duty 0–100</value></data>
|
||||
<data name="BtnSaveApply" xml:space="preserve"><value>Save & Apply</value></data>
|
||||
<data name="BtnRestoreDefault" xml:space="preserve"><value>Restore Default Curve</value></data>
|
||||
<data name="LblEctoolPath" xml:space="preserve"><value>ectool path</value></data>
|
||||
<data name="LblPollInterval" xml:space="preserve"><value>Poll interval (ms)</value></data>
|
||||
<data name="LblRpmEvery" xml:space="preserve"><value>Read RPM every N cycles</value></data>
|
||||
<data name="LblFanRpmArgs" xml:space="preserve"><value>RPM args (space-separated)</value></data>
|
||||
<data name="LblFanDutyArgs" xml:space="preserve"><value>Duty args (empty=skip)</value></data>
|
||||
<data name="LblAutoFanArgs" xml:space="preserve"><value>Restore auto fan args</value></data>
|
||||
<data name="LblTempSource" xml:space="preserve"><value>Temp source</value></data>
|
||||
<data name="LblFailCount" xml:space="preserve"><value>Consecutive errors before fail-safe</value></data>
|
||||
<data name="LblFailPct" xml:space="preserve"><value>Fail-safe fixed duty (%)</value></data>
|
||||
<data name="ChkFailAuto" xml:space="preserve"><value>Use autofanctrl in fail-safe (not fixed duty)</value></data>
|
||||
<data name="LblFailStrategy" xml:space="preserve"><value>Fail-safe strategy</value></data>
|
||||
<data name="LblChartMin" xml:space="preserve"><value>Chart history (minutes)</value></data>
|
||||
<data name="LblChartPoints" xml:space="preserve"><value>Chart max points</value></data>
|
||||
<data name="BtnSaveAdvanced" xml:space="preserve"><value>Save Advanced Settings</value></data>
|
||||
<data name="TrayOpen" xml:space="preserve"><value>Show window</value></data>
|
||||
<data name="TrayPause" xml:space="preserve"><value>Pause control</value></data>
|
||||
<data name="TrayResume" xml:space="preserve"><value>Resume control</value></data>
|
||||
<data name="TrayAbout" xml:space="preserve"><value>About</value></data>
|
||||
<data name="TrayExit" xml:space="preserve"><value>Exit</value></data>
|
||||
<data name="AboutMessage" xml:space="preserve"><value>Chromebox Fan Control
|
||||
|
||||
Uses LibreHardwareMonitor to read CPU temperature, controls fan via crosec ectool.
|
||||
|
||||
Run as Administrator.</value></data>
|
||||
<data name="ErrRpmArgsEmpty" xml:space="preserve"><value>RPM args cannot be empty.</value></data>
|
||||
<data name="ErrAutoFanArgsEmpty" xml:space="preserve"><value>Restore auto fan args cannot be empty.</value></data>
|
||||
<data name="MsgSaved" xml:space="preserve"><value>Saved.</value></data>
|
||||
<data name="LabelTemp" xml:space="preserve"><value>Temp:</value></data>
|
||||
<data name="LabelTargetDuty" xml:space="preserve"><value>Target duty:</value></data>
|
||||
<data name="LabelRpm" xml:space="preserve"><value>RPM:</value></data>
|
||||
<data name="ChartTemp" xml:space="preserve"><value>Temperature (°C)</value></data>
|
||||
<data name="ChartTime" xml:space="preserve"><value>Time (s)</value></data>
|
||||
<data name="ChartDuty" xml:space="preserve"><value>Duty (%)</value></data>
|
||||
<data name="ChartRpm" xml:space="preserve"><value>RPM</value></data>
|
||||
<data name="LegendCelsius" xml:space="preserve"><value>°C</value></data>
|
||||
<data name="LegendDuty" xml:space="preserve"><value>Duty %</value></data>
|
||||
<data name="CurveChartDuty" xml:space="preserve"><value>Duty (%)</value></data>
|
||||
<data name="CurveChartTemp" xml:space="preserve"><value>Temp (°C)</value></data>
|
||||
<data name="LblLanguage" xml:space="preserve"><value>Language</value></data>
|
||||
<data name="LangAuto" xml:space="preserve"><value>Auto (follow system)</value></data>
|
||||
<data name="LangEn" xml:space="preserve"><value>English</value></data>
|
||||
<data name="LangZhHans" xml:space="preserve"><value>Simplified Chinese</value></data>
|
||||
<data name="LangZhHant" xml:space="preserve"><value>Traditional Chinese</value></data>
|
||||
<data name="MsgRestartToApplyLanguage" xml:space="preserve"><value>Language changed. Restart the application to apply.</value></data>
|
||||
</root>
|
||||
117
ChromeboxFanControl/Properties/Resources.zh-Hans.resx
Normal file
117
ChromeboxFanControl/Properties/Resources.zh-Hans.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AppTitle" xml:space="preserve"><value>Chromebox 风扇温控</value></data>
|
||||
<data name="TabMonitor" xml:space="preserve"><value>监控</value></data>
|
||||
<data name="TabCurve" xml:space="preserve"><value>曲线</value></data>
|
||||
<data name="TabAdvanced" xml:space="preserve"><value>高级</value></data>
|
||||
<data name="StatusStarting" xml:space="preserve"><value>启动中…</value></data>
|
||||
<data name="StatusRunning" xml:space="preserve"><value>运行中</value></data>
|
||||
<data name="StatusPaused" xml:space="preserve"><value>已暂停</value></data>
|
||||
<data name="StatusFailSafe" xml:space="preserve"><value>安全模式</value></data>
|
||||
<data name="GridColTemp" xml:space="preserve"><value>温度 (°C)</value></data>
|
||||
<data name="GridColDuty" xml:space="preserve"><value>占空比 0–100</value></data>
|
||||
<data name="BtnSaveApply" xml:space="preserve"><value>保存并应用</value></data>
|
||||
<data name="BtnRestoreDefault" xml:space="preserve"><value>恢复默认曲线</value></data>
|
||||
<data name="LblEctoolPath" xml:space="preserve"><value>ectool 路径</value></data>
|
||||
<data name="LblPollInterval" xml:space="preserve"><value>轮询间隔 (ms)</value></data>
|
||||
<data name="LblRpmEvery" xml:space="preserve"><value>每 N 轮读 RPM</value></data>
|
||||
<data name="LblFanRpmArgs" xml:space="preserve"><value>读转速参数 (空格分隔)</value></data>
|
||||
<data name="LblFanDutyArgs" xml:space="preserve"><value>读占空比参数 (空=跳过)</value></data>
|
||||
<data name="LblAutoFanArgs" xml:space="preserve"><value>恢复自动风扇参数</value></data>
|
||||
<data name="LblTempSource" xml:space="preserve"><value>温度来源</value></data>
|
||||
<data name="LblFailCount" xml:space="preserve"><value>连续失败进入安全模式 (次)</value></data>
|
||||
<data name="LblFailPct" xml:space="preserve"><value>安全模式固定占空比 (%)</value></data>
|
||||
<data name="ChkFailAuto" xml:space="preserve"><value>安全模式使用 autofanctrl(不用固定占空比)</value></data>
|
||||
<data name="LblFailStrategy" xml:space="preserve"><value>安全模式策略</value></data>
|
||||
<data name="LblChartMin" xml:space="preserve"><value>图表保留时长 (分钟)</value></data>
|
||||
<data name="LblChartPoints" xml:space="preserve"><value>图表最大点数</value></data>
|
||||
<data name="BtnSaveAdvanced" xml:space="preserve"><value>保存高级设置</value></data>
|
||||
<data name="TrayOpen" xml:space="preserve"><value>打开主窗口</value></data>
|
||||
<data name="TrayPause" xml:space="preserve"><value>暂停控制</value></data>
|
||||
<data name="TrayResume" xml:space="preserve"><value>恢复控制</value></data>
|
||||
<data name="TrayAbout" xml:space="preserve"><value>关于</value></data>
|
||||
<data name="TrayExit" xml:space="preserve"><value>退出</value></data>
|
||||
<data name="AboutMessage" xml:space="preserve"><value>Chromebox 风扇温控
|
||||
|
||||
使用 LibreHardwareMonitor 读取 CPU 温度,通过 crosec ectool 控制风扇。
|
||||
|
||||
请以管理员身份运行。</value></data>
|
||||
<data name="ErrRpmArgsEmpty" xml:space="preserve"><value>读转速参数不能为空。</value></data>
|
||||
<data name="ErrAutoFanArgsEmpty" xml:space="preserve"><value>恢复自动风扇参数不能为空。</value></data>
|
||||
<data name="MsgSaved" xml:space="preserve"><value>已保存。</value></data>
|
||||
<data name="LabelTemp" xml:space="preserve"><value>温度:</value></data>
|
||||
<data name="LabelTargetDuty" xml:space="preserve"><value>目标占空比:</value></data>
|
||||
<data name="LabelRpm" xml:space="preserve"><value>转速:</value></data>
|
||||
<data name="ChartTemp" xml:space="preserve"><value>温度 (°C)</value></data>
|
||||
<data name="ChartTime" xml:space="preserve"><value>时间 (s)</value></data>
|
||||
<data name="ChartDuty" xml:space="preserve"><value>占空比 (%)</value></data>
|
||||
<data name="ChartRpm" xml:space="preserve"><value>转速</value></data>
|
||||
<data name="LegendCelsius" xml:space="preserve"><value>°C</value></data>
|
||||
<data name="LegendDuty" xml:space="preserve"><value>占空比 %</value></data>
|
||||
<data name="CurveChartDuty" xml:space="preserve"><value>占空比 (%)</value></data>
|
||||
<data name="CurveChartTemp" xml:space="preserve"><value>温度 (°C)</value></data>
|
||||
<data name="LblLanguage" xml:space="preserve"><value>语言</value></data>
|
||||
<data name="LangAuto" xml:space="preserve"><value>跟随系统</value></data>
|
||||
<data name="LangEn" xml:space="preserve"><value>English</value></data>
|
||||
<data name="LangZhHans" xml:space="preserve"><value>简体中文</value></data>
|
||||
<data name="LangZhHant" xml:space="preserve"><value>繁体中文</value></data>
|
||||
<data name="MsgRestartToApplyLanguage" xml:space="preserve"><value>语言已更改,请重启应用以生效。</value></data>
|
||||
</root>
|
||||
117
ChromeboxFanControl/Properties/Resources.zh-Hant.resx
Normal file
117
ChromeboxFanControl/Properties/Resources.zh-Hant.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="AppTitle" xml:space="preserve"><value>Chromebox 風扇溫控</value></data>
|
||||
<data name="TabMonitor" xml:space="preserve"><value>監控</value></data>
|
||||
<data name="TabCurve" xml:space="preserve"><value>曲線</value></data>
|
||||
<data name="TabAdvanced" xml:space="preserve"><value>進階</value></data>
|
||||
<data name="StatusStarting" xml:space="preserve"><value>啟動中…</value></data>
|
||||
<data name="StatusRunning" xml:space="preserve"><value>運行中</value></data>
|
||||
<data name="StatusPaused" xml:space="preserve"><value>已暫停</value></data>
|
||||
<data name="StatusFailSafe" xml:space="preserve"><value>安全模式</value></data>
|
||||
<data name="GridColTemp" xml:space="preserve"><value>溫度 (°C)</value></data>
|
||||
<data name="GridColDuty" xml:space="preserve"><value>占空比 0–100</value></data>
|
||||
<data name="BtnSaveApply" xml:space="preserve"><value>儲存並套用</value></data>
|
||||
<data name="BtnRestoreDefault" xml:space="preserve"><value>恢復預設曲線</value></data>
|
||||
<data name="LblEctoolPath" xml:space="preserve"><value>ectool 路徑</value></data>
|
||||
<data name="LblPollInterval" xml:space="preserve"><value>輪詢間隔 (ms)</value></data>
|
||||
<data name="LblRpmEvery" xml:space="preserve"><value>每 N 輪讀 RPM</value></data>
|
||||
<data name="LblFanRpmArgs" xml:space="preserve"><value>讀轉速參數 (空格分隔)</value></data>
|
||||
<data name="LblFanDutyArgs" xml:space="preserve"><value>讀占空比參數 (空=略過)</value></data>
|
||||
<data name="LblAutoFanArgs" xml:space="preserve"><value>恢復自動風扇參數</value></data>
|
||||
<data name="LblTempSource" xml:space="preserve"><value>溫度來源</value></data>
|
||||
<data name="LblFailCount" xml:space="preserve"><value>連續失敗進入安全模式 (次)</value></data>
|
||||
<data name="LblFailPct" xml:space="preserve"><value>安全模式固定占空比 (%)</value></data>
|
||||
<data name="ChkFailAuto" xml:space="preserve"><value>安全模式使用 autofanctrl(不用固定占空比)</value></data>
|
||||
<data name="LblFailStrategy" xml:space="preserve"><value>安全模式策略</value></data>
|
||||
<data name="LblChartMin" xml:space="preserve"><value>圖表保留時長 (分鐘)</value></data>
|
||||
<data name="LblChartPoints" xml:space="preserve"><value>圖表最大點數</value></data>
|
||||
<data name="BtnSaveAdvanced" xml:space="preserve"><value>儲存進階設定</value></data>
|
||||
<data name="TrayOpen" xml:space="preserve"><value>開啟主視窗</value></data>
|
||||
<data name="TrayPause" xml:space="preserve"><value>暫停控制</value></data>
|
||||
<data name="TrayResume" xml:space="preserve"><value>恢復控制</value></data>
|
||||
<data name="TrayAbout" xml:space="preserve"><value>關於</value></data>
|
||||
<data name="TrayExit" xml:space="preserve"><value>結束</value></data>
|
||||
<data name="AboutMessage" xml:space="preserve"><value>Chromebox 風扇溫控
|
||||
|
||||
使用 LibreHardwareMonitor 讀取 CPU 溫度,透過 crosec ectool 控制風扇。
|
||||
|
||||
請以系統管理員身分執行。</value></data>
|
||||
<data name="ErrRpmArgsEmpty" xml:space="preserve"><value>讀轉速參數不能為空。</value></data>
|
||||
<data name="ErrAutoFanArgsEmpty" xml:space="preserve"><value>恢復自動風扇參數不能為空。</value></data>
|
||||
<data name="MsgSaved" xml:space="preserve"><value>已儲存。</value></data>
|
||||
<data name="LabelTemp" xml:space="preserve"><value>溫度:</value></data>
|
||||
<data name="LabelTargetDuty" xml:space="preserve"><value>目標占空比:</value></data>
|
||||
<data name="LabelRpm" xml:space="preserve"><value>轉速:</value></data>
|
||||
<data name="ChartTemp" xml:space="preserve"><value>溫度 (°C)</value></data>
|
||||
<data name="ChartTime" xml:space="preserve"><value>時間 (s)</value></data>
|
||||
<data name="ChartDuty" xml:space="preserve"><value>占空比 (%)</value></data>
|
||||
<data name="ChartRpm" xml:space="preserve"><value>轉速</value></data>
|
||||
<data name="LegendCelsius" xml:space="preserve"><value>°C</value></data>
|
||||
<data name="LegendDuty" xml:space="preserve"><value>占空比 %</value></data>
|
||||
<data name="CurveChartDuty" xml:space="preserve"><value>占空比 (%)</value></data>
|
||||
<data name="CurveChartTemp" xml:space="preserve"><value>溫度 (°C)</value></data>
|
||||
<data name="LblLanguage" xml:space="preserve"><value>語言</value></data>
|
||||
<data name="LangAuto" xml:space="preserve"><value>跟隨系統</value></data>
|
||||
<data name="LangEn" xml:space="preserve"><value>English</value></data>
|
||||
<data name="LangZhHans" xml:space="preserve"><value>簡體中文</value></data>
|
||||
<data name="LangZhHant" xml:space="preserve"><value>繁體中文</value></data>
|
||||
<data name="MsgRestartToApplyLanguage" xml:space="preserve"><value>語言已變更,請重新啟動應用程式以生效。</value></data>
|
||||
</root>
|
||||
27
ChromeboxFanControlService/ChromeboxFanControlService.csproj
Normal file
27
ChromeboxFanControlService/ChromeboxFanControlService.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>ChromeboxFanControlService</RootNamespace>
|
||||
<AssemblyName>ChromeboxFanControlService</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ChromeboxFanControl\ChromeboxFanControl.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="appsettings.json" />
|
||||
</ItemGroup>
|
||||
<!-- appsettings comes from ChromeboxFanControl when merged in dist; service uses that or ProgramData config -->
|
||||
|
||||
</Project>
|
||||
43
ChromeboxFanControlService/FanControlWorker.cs
Normal file
43
ChromeboxFanControlService/FanControlWorker.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using ChromeboxFanControl;
|
||||
|
||||
namespace ChromeboxFanControlService;
|
||||
|
||||
public sealed class FanControlWorker : BackgroundService
|
||||
{
|
||||
private FanController? _fan;
|
||||
private AppConfig? _config;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_config = AppConfig.LoadMerged();
|
||||
_fan = new FanController(_config);
|
||||
_fan.Start();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
/* shutdown */
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_fan?.Stop();
|
||||
_fan?.Dispose();
|
||||
if (_config != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
EctoolRunner.RunSync(_config.EctoolPath, _config.AutoFanCtrlArgs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* best effort restore autofan on shutdown */
|
||||
}
|
||||
}
|
||||
await base.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
16
ChromeboxFanControlService/Program.cs
Normal file
16
ChromeboxFanControlService/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using ChromeboxFanControlService;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var host = Host.CreateDefaultBuilder(args)
|
||||
.UseWindowsService(options =>
|
||||
{
|
||||
options.ServiceName = "ChromeboxFanControl";
|
||||
})
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddHostedService<FanControlWorker>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync();
|
||||
15
ChromeboxFanControlService/appsettings.json
Normal file
15
ChromeboxFanControlService/appsettings.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"EctoolPath": "C:\\Program Files\\crosec\\ectool.exe",
|
||||
"PollIntervalMs": 1500,
|
||||
"FanRpmPollEveryNCycles": 1,
|
||||
"FanRpmArgs": [ "pwmgetfanrpm", "0" ],
|
||||
"FanDutyArgs": null,
|
||||
"AutoFanCtrlArgs": [ "autofanctrl" ],
|
||||
"TempSource": "AverageCore",
|
||||
"FailSafeAfterConsecutiveErrors": 5,
|
||||
"FailSafeFanPercent": 100,
|
||||
"FailSafeRestoreAutoFan": false,
|
||||
"ChartHistoryMinutes": 15,
|
||||
"ChartMaxPoints": 900,
|
||||
"CurvePoints": [ 0, 0, 10, 25, 40, 55, 70, 80, 90, 95, 97, 98, 99, 100 ]
|
||||
}
|
||||
33
README.md
33
README.md
@@ -8,7 +8,7 @@
|
||||
`C:\Program Files\crosec\ectool.exe`
|
||||
若路径不同,在程序「高级」选项卡中修改。
|
||||
2. **以管理员身份运行**本程序(清单已要求提升权限;LHM 与 EC 通信通常需要管理员)。
|
||||
3. 本机安装 [.NET 8 运行时](https://dotnet.microsoft.com/download/dotnet/8.0)(若使用 `dotnet publish --self-contained false` 发布,目标机需运行时)。
|
||||
3. **客户机**:`.\build.ps1 -publish`(及带 `-msi` 时)默认**自包含单文件**发布,**已内含 .NET 8 运行时**,`dist\` 仅含数个 exe,无需再装运行时。若需多 DLL 或依赖系统 .NET 8 以减小体积,可加 `-multiFile` 或 `-frameworkDependent`。
|
||||
|
||||
## 编译
|
||||
|
||||
@@ -16,28 +16,49 @@
|
||||
.\build.ps1
|
||||
```
|
||||
|
||||
发布到 `dist\`:
|
||||
### 发布 MSI 安装包(推荐)
|
||||
|
||||
```powershell
|
||||
.\build.ps1 -publish
|
||||
.\build.ps1 -publish -msi
|
||||
```
|
||||
|
||||
需已安装 [.NET 8 SDK](https://dotnet.microsoft.com/download)。
|
||||
`-publish` 只生成 `dist\`;加上 **`-msi`** 才用 WiX 生成安装包(需已安装 WiX)。输出两个 MSI:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `dist-installer\ChromeboxFanControl-Setup.msi` | **桌面版**:GUI + 可选 Windows 服务(功能树中可勾选) |
|
||||
| `dist-installer\ChromeboxFanControlService-Setup.msi` | **服务版**:仅后台服务,适合无头部署 |
|
||||
|
||||
**构建机**需 [.NET 8 SDK](https://dotnet.microsoft.com/download) 与 [WiX Toolset 3](https://wixtoolset.org/docs/wix3/)。**构建前请关闭正在运行的 ChromeboxFanControl.exe**。
|
||||
|
||||
以**管理员身份**运行 MSI。若尚未安装 ectool,可访问 [Chrultrabook 的 ectool 安装说明](https://docs.chrultrabook.com/docs/installing/ectool.html)。服务模式下配置从 `%ProgramData%\ChromeboxFanControl\config.json` 读取。
|
||||
|
||||
### 仅发布到 dist\(用于调试或手动打包)
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `.\build.ps1 -publish` | 同时发布桌面版与服务版到 `dist\`(默认单文件) |
|
||||
| `.\build.ps1 -publishGui` | 仅发布 GUI |
|
||||
| `.\build.ps1 -publishService` | 仅发布服务并合并进 `dist\` |
|
||||
| `.\build.ps1 -publish -multiFile` | 保留多 DLL 输出(默认单 exe) |
|
||||
| `.\build.ps1 -publish -frameworkDependent` | 依赖系统 .NET 8(无 bundled 运行时) |
|
||||
|
||||
## 配置
|
||||
|
||||
- 安装目录旁的 **`appsettings.json`**:默认选项。
|
||||
- **`%AppData%\ChromeboxFanControl\config.json`**:保存后会覆盖同名项(用户配置)。
|
||||
- **`%AppData%\ChromeboxFanControl\config.json`**:GUI 模式下保存后覆盖(用户配置)。
|
||||
- **`%ProgramData%\ChromeboxFanControl\config.json`**:Windows 服务模式下读取的配置;可与 GUI 共用同一份配置逻辑。
|
||||
|
||||
重要字段:
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| `Language` | 界面语言:`auto`(跟随系统)、`en`、`zh-Hans`、`zh-Hant`。修改后需重启生效。 |
|
||||
| `FanRpmArgs` | 读取转速时传给 ectool 的参数(默认 `pwmgetfanrpm` `0`),请用本机 `ectool help` 核对。 |
|
||||
| `FanDutyArgs` | 读取占空比时传给 ectool 的参数(如 `pwmget` `0`);空则跳过,显示目标占空比。 |
|
||||
| `AutoFanCtrlArgs` | 退出程序时恢复 EC 自动风扇(默认 `autofanctrl`),若命令名不同请修改。 |
|
||||
| `TempSource` | `AverageCore` 或 `MaxCore`。 |
|
||||
| `CurvePoints` | 13 个 0–100 的占空比控制点(与 Chrultrabook-Tools 曲线逻辑一致)。 |
|
||||
| `CurvePoints` | 14 个 0–100 的占空比控制点(对应温度断点 0,40,45,…,100°C,线性插值)。 |
|
||||
|
||||
## 使用说明
|
||||
|
||||
|
||||
135
build.ps1
135
build.ps1
@@ -22,6 +22,13 @@ if (-not $exe) {
|
||||
Write-Error "dotnet SDK not found. Install .NET 8 SDK: https://dotnet.microsoft.com/download"
|
||||
}
|
||||
|
||||
# -frameworkDependent: no bundled runtime (fewer files, needs .NET 8 on target)
|
||||
# -multiFile: disable single-file (default is single exe to reduce DLL count)
|
||||
$frameworkDependent = $args -contains "-frameworkDependent" -or $args -contains "--frameworkDependent"
|
||||
$multiFile = $args -contains "-multiFile" -or $args -contains "--multiFile"
|
||||
$selfContained = if ($frameworkDependent) { "false" } else { "true" }
|
||||
$singleFile = -not $multiFile
|
||||
|
||||
function Invoke-Dotnet {
|
||||
if ($exe -eq "dotnet") {
|
||||
dotnet @args
|
||||
@@ -30,28 +37,122 @@ function Invoke-Dotnet {
|
||||
}
|
||||
}
|
||||
|
||||
$doPublish = $args -contains "-publish" -or $args -contains "--publish"
|
||||
$doPublishGui = $args -contains "-publishGui" -or $args -contains "--publishGui"
|
||||
$doPublishSvc = $args -contains "-publishService" -or $args -contains "--publishService"
|
||||
# -publish without split = GUI + Service both
|
||||
if ($doPublish -and -not $doPublishGui -and -not $doPublishSvc) {
|
||||
$doPublishGui = $true
|
||||
$doPublishSvc = $true
|
||||
}
|
||||
$doPublishAny = $doPublishGui -or $doPublishSvc
|
||||
# -msi requires WiX; -publish only generates dist, MSI optional
|
||||
$doMsi = $args -contains "-msi" -or $args -contains "--msi"
|
||||
Write-Host ">> dotnet restore"
|
||||
Invoke-Dotnet restore "$root\ChromeboxFanControl.sln"
|
||||
if ($doPublishAny -or $doMsi) {
|
||||
Invoke-Dotnet restore "$root\ChromeboxFanControl.sln" -r win-x64
|
||||
} else {
|
||||
Invoke-Dotnet restore "$root\ChromeboxFanControl.sln"
|
||||
}
|
||||
|
||||
Write-Host ">> dotnet build Release"
|
||||
Invoke-Dotnet build "$root\ChromeboxFanControl.sln" -c Release --no-restore
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
$doPublish = $args -contains "-publish" -or $args -contains "--publish"
|
||||
if ($doPublish) {
|
||||
if ($doPublishAny) {
|
||||
$out = Join-Path $root "dist"
|
||||
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $out | Out-Null
|
||||
Write-Host ">> dotnet publish -> $out"
|
||||
Invoke-Dotnet publish "$root\ChromeboxFanControl\ChromeboxFanControl.csproj" `
|
||||
-c Release `
|
||||
-o $out `
|
||||
-r win-x64 `
|
||||
--self-contained false `
|
||||
-p:PublishSingleFile=false `
|
||||
--no-restore
|
||||
Write-Host "Done: $out\ChromeboxFanControl.exe"
|
||||
} else {
|
||||
Write-Host "Done: $root\ChromeboxFanControl\bin\Release\net8.0-windows\ChromeboxFanControl.exe"
|
||||
Write-Host "(Run .\build.ps1 -publish to output to dist\)"
|
||||
if ($doPublishGui -and $doPublishSvc) {
|
||||
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $out | Out-Null
|
||||
} elseif ($doPublishGui -and -not (Test-Path $out)) {
|
||||
New-Item -ItemType Directory -Path $out | Out-Null
|
||||
}
|
||||
if ($doPublishGui) {
|
||||
Write-Host ">> dotnet publish GUI -> $out"
|
||||
$guiArgs = @("-c", "Release", "-o", $out, "-r", "win-x64", "--self-contained", "false", "--no-restore")
|
||||
if ($singleFile) { $guiArgs += "-p:PublishSingleFile=true", "-p:IncludeNativeLibrariesForSelfExtract=true" }
|
||||
Invoke-Dotnet publish "$root\ChromeboxFanControl\ChromeboxFanControl.csproj" @guiArgs
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
if ($doPublishSvc) {
|
||||
Write-Host ">> dotnet publish Service -> dist-svc"
|
||||
$svcOut = Join-Path $root "dist-svc"
|
||||
$svcArgs = @("-c", "Release", "-o", $svcOut, "-r", "win-x64", "--self-contained", $selfContained, "--no-restore")
|
||||
if ($singleFile) { $svcArgs += "-p:PublishSingleFile=true", "-p:IncludeNativeLibrariesForSelfExtract=true" }
|
||||
Invoke-Dotnet publish "$root\ChromeboxFanControlService\ChromeboxFanControlService.csproj" @svcArgs
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
if (-not (Test-Path $out)) { New-Item -ItemType Directory -Path $out | Out-Null }
|
||||
Copy-Item "$svcOut\*" $out -Recurse -Force -Exclude "appsettings.json"
|
||||
if (-not $doMsi) { Remove-Item $svcOut -Recurse -Force }
|
||||
}
|
||||
if ($doPublishGui -and $doPublishSvc) {
|
||||
Write-Host "Done: $out\ChromeboxFanControl.exe (GUI), $out\ChromeboxFanControlService.exe (Service)"
|
||||
} elseif ($doPublishGui) {
|
||||
Write-Host "Done (GUI only): $out\ChromeboxFanControl.exe"
|
||||
} else {
|
||||
Write-Host "Done (Service only): $out\ChromeboxFanControlService.exe (merged into dist\)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($doMsi) {
|
||||
$dist = Join-Path $root "dist"
|
||||
$distSvc = Join-Path $root "dist-svc"
|
||||
if (-not (Test-Path $dist)) {
|
||||
Write-Error "dist\ not found. Run .\build.ps1 -publish first."
|
||||
}
|
||||
if (-not (Test-Path $distSvc)) {
|
||||
Write-Host ">> dist-svc missing, publishing Service for service MSI"
|
||||
$svcArgs = @("-c", "Release", "-o", $distSvc, "-r", "win-x64", "--self-contained", $selfContained, "--no-restore")
|
||||
if ($singleFile) { $svcArgs += "-p:PublishSingleFile=true", "-p:IncludeNativeLibrariesForSelfExtract=true" }
|
||||
Invoke-Dotnet publish "$root\ChromeboxFanControlService\ChromeboxFanControlService.csproj" @svcArgs
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
}
|
||||
$wixBin = $null
|
||||
foreach ($v in @("3.11", "3.14", "3.10")) {
|
||||
$p = "${env:ProgramFiles(x86)}\WiX Toolset v$v\bin"
|
||||
if (Test-Path "$p\candle.exe") { $wixBin = $p; break }
|
||||
}
|
||||
if (-not $wixBin) {
|
||||
Write-Warning "WiX Toolset not found. Skipping MSI. Install from https://wixtoolset.org/docs/wix3/ to build installers."
|
||||
} else {
|
||||
$wixDir = Join-Path $root "wix"
|
||||
$outMsi = Join-Path $root "dist-installer"
|
||||
if (-not (Test-Path $outMsi)) { New-Item -ItemType Directory -Path $outMsi | Out-Null }
|
||||
$objDir = Join-Path $root "wix\obj"
|
||||
if (-not (Test-Path $objDir)) { New-Item -ItemType Directory -Path $objDir | Out-Null }
|
||||
$ext = "-ext", "$wixBin\WixUtilExtension.dll", "-ext", "$wixBin\WixUIExtension.dll"
|
||||
|
||||
Write-Host ">> WiX: Desktop MSI"
|
||||
$harvested = Join-Path $root "wix\Harvested.wxs"
|
||||
& "$wixBin\heat.exe" dir $dist -cg AppFiles -gg -sf -srd -sreg -scom -dr INSTALLFOLDER -out $harvested -t "$wixDir\exclude-service.xsl" -sw5151
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
& "$wixBin\candle.exe" -arch x64 -out "$objDir\\" @ext "$wixDir\Product.wxs" $harvested
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
& "$wixBin\light.exe" -out "$outMsi\ChromeboxFanControl-Setup.msi" -b $dist @ext "$objDir\Product.wixobj" "$objDir\Harvested.wixobj"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Remove-Item $harvested -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host ">> WiX: Service MSI"
|
||||
$harvestedSvc = Join-Path $root "wix\Harvested-Service.wxs"
|
||||
& "$wixBin\heat.exe" dir $distSvc -cg ServiceFiles -gg -sf -srd -sreg -scom -dr INSTALLFOLDER -out $harvestedSvc -t "$wixDir\exclude-service.xsl" -sw5151
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
& "$wixBin\candle.exe" -arch x64 -out "$objDir\\" @ext "$wixDir\Product-Service.wxs" $harvestedSvc
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
& "$wixBin\light.exe" -out "$outMsi\ChromeboxFanControlService-Setup.msi" -b $distSvc @ext "$objDir\Product-Service.wixobj" "$objDir\Harvested-Service.wixobj"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
Remove-Item $harvestedSvc -Force -ErrorAction SilentlyContinue
|
||||
|
||||
if (Test-Path $distSvc) { Remove-Item $distSvc -Recurse -Force }
|
||||
Write-Host "Done: $outMsi\ChromeboxFanControl-Setup.msi (Desktop), $outMsi\ChromeboxFanControlService-Setup.msi (Service)"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $doPublishAny -and -not $doMsi) {
|
||||
Write-Host "Done: $root\ChromeboxFanControl\bin\Release\net8.0-windows\ChromeboxFanControl.exe"
|
||||
Write-Host " .\build.ps1 -publish -> dist\ GUI + Service (self-contained)"
|
||||
Write-Host " .\build.ps1 -publishGui -> GUI only"
|
||||
Write-Host " .\build.ps1 -publishService -> Service only (merge into dist\)"
|
||||
Write-Host " .\build.ps1 -publish -msi -> dist + two MSI (needs WiX)"
|
||||
Write-Host " .\build.ps1 -publish -frameworkDependent -> no bundled runtime (needs .NET 8)"
|
||||
Write-Host " .\build.ps1 -publish -multiFile -> keep multiple DLLs (default: single exe)"
|
||||
}
|
||||
|
||||
48
wix/Product-Service.wxs
Normal file
48
wix/Product-Service.wxs
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
<Product Id="*" Name="Chromebox Fan Control Service" Language="1033" Version="1.0.0" Manufacturer="ChromeboxFanControl"
|
||||
UpgradeCode="B2C3D4E5-F6A7-8901-BCDE-F12345678901">
|
||||
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Platform="x64" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Feature Id="ServiceFeature" Title="Chromebox Fan Control Service (background, auto-start)" Level="1" ConfigurableDirectory="INSTALLFOLDER">
|
||||
<ComponentGroupRef Id="ServiceFiles" />
|
||||
<ComponentRef Id="ConfigFolderComponent" />
|
||||
<ComponentRef Id="ServiceComponent" />
|
||||
</Feature>
|
||||
|
||||
<UIRef Id="WixUI_InstallDir" />
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Chromebox Fan Control" />
|
||||
</Directory>
|
||||
<Directory Id="CommonAppDataFolder">
|
||||
<Directory Id="ConfigFolder" Name="ChromeboxFanControl" />
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="ServiceComponent" Guid="C3D4E5F6-A7B8-9012-CDEF-F12345678902">
|
||||
<File Id="ServiceExe" Source="ChromeboxFanControlService.exe" KeyPath="yes" />
|
||||
<ServiceInstall Id="ChromeboxFanControlSvc" Name="ChromeboxFanControl"
|
||||
DisplayName="Chromebox Fan Control Service" Description="Fan control for Chromebox with Coreboot"
|
||||
Type="ownProcess" Start="auto" Account="LocalSystem" ErrorControl="normal" />
|
||||
<ServiceControl Id="StartService" Name="ChromeboxFanControl" Start="install" />
|
||||
<ServiceControl Id="StopService" Name="ChromeboxFanControl" Stop="both" Remove="uninstall" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ConfigFolder">
|
||||
<Component Id="ConfigFolderComponent" Guid="D4E5F6A7-B8C9-0123-DEF0-123456789003">
|
||||
<CreateFolder>
|
||||
<util:PermissionEx User="Users" GenericAll="yes" />
|
||||
</CreateFolder>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
</Product>
|
||||
</Wix>
|
||||
66
wix/Product.wxs
Normal file
66
wix/Product.wxs
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||
<Product Id="*" Name="Chromebox Fan Control" Language="1033" Version="1.0.0" Manufacturer="ChromeboxFanControl"
|
||||
UpgradeCode="A1B2C3D4-E5F6-7890-ABCD-EF1234567890">
|
||||
<Package InstallerVersion="500" Compressed="yes" InstallScope="perMachine" Platform="x64" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Feature Id="MainFeature" Title="Chromebox Fan Control" Level="1" ConfigurableDirectory="INSTALLFOLDER">
|
||||
<ComponentGroupRef Id="AppFiles" />
|
||||
<ComponentRef Id="ConfigFolderComponent" />
|
||||
<ComponentRef Id="ShortcutComponent" />
|
||||
</Feature>
|
||||
|
||||
<Feature Id="ServiceFeature" Title="Windows Service (runs in background, auto-start)" Level="2">
|
||||
<ComponentRef Id="ServiceComponent" />
|
||||
</Feature>
|
||||
|
||||
<UIRef Id="WixUI_FeatureTree" />
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFiles64Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="Chromebox Fan Control" />
|
||||
</Directory>
|
||||
<Directory Id="CommonAppDataFolder">
|
||||
<Directory Id="ConfigFolder" Name="ChromeboxFanControl" />
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="AppProgramMenuFolder" Name="Chromebox Fan Control" />
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="ServiceComponent" Guid="B2C3D4E5-F6A7-8901-BCDE-F12345678902">
|
||||
<File Id="ServiceExe" Source="ChromeboxFanControlService.exe" KeyPath="yes" />
|
||||
<ServiceInstall Id="ChromeboxFanControlSvc" Name="ChromeboxFanControl"
|
||||
DisplayName="Chromebox Fan Control Service" Description="Fan control for Chromebox with Coreboot"
|
||||
Type="ownProcess" Start="auto" Account="LocalSystem" ErrorControl="normal" />
|
||||
<ServiceControl Id="StartService" Name="ChromeboxFanControl" Start="install" />
|
||||
<ServiceControl Id="StopService" Name="ChromeboxFanControl" Stop="both" Remove="uninstall" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="ConfigFolder">
|
||||
<Component Id="ConfigFolderComponent" Guid="C3D4E5F6-A7B8-9012-CDEF-123456789003">
|
||||
<CreateFolder>
|
||||
<util:PermissionEx User="Users" GenericAll="yes" />
|
||||
</CreateFolder>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<DirectoryRef Id="AppProgramMenuFolder">
|
||||
<Component Id="ShortcutComponent" Guid="D4E5F6A7-B8C9-0123-DEF0-234567890104">
|
||||
<Shortcut Id="AppShortcut" Name="Chromebox Fan Control" Description="Fan control for Chromebox"
|
||||
Target="[INSTALLFOLDER]ChromeboxFanControl.exe" WorkingDirectory="INSTALLFOLDER" />
|
||||
<Shortcut Id="UninstallShortcut" Name="Uninstall Chromebox Fan Control"
|
||||
Target="[System64Folder]msiexec.exe" Arguments="/x [ProductCode]" />
|
||||
<RemoveFolder Id="RemoveAppProgramMenuFolder" Directory="AppProgramMenuFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\ChromeboxFanControl" Name="installed" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
</Product>
|
||||
</Wix>
|
||||
11
wix/exclude-service.xsl
Normal file
11
wix/exclude-service.xsl
Normal file
@@ -0,0 +1,11 @@
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:wi="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<xsl:output omit-xml-declaration="yes" />
|
||||
<xsl:key name="service-comp" match="wi:Component[wi:File[contains(@Source,'ChromeboxFanControlService.exe')]]" use="@Id" />
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()" />
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
<xsl:template match="wi:Component[wi:File[contains(@Source,'ChromeboxFanControlService.exe')]]" />
|
||||
<xsl:template match="wi:ComponentRef[key('service-comp', @Id)]" />
|
||||
</xsl:stylesheet>
|
||||
BIN
wix314.exe
Normal file
BIN
wix314.exe
Normal file
Binary file not shown.
Reference in New Issue
Block a user