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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Windows CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: windows-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: windows-latest

steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: Verify formatting
run: dotnet format AutoPowerMode.sln --verify-no-changes

- name: Run logic tests
run: dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj --configuration Release

- name: Run Windows Settings UI smoke test
run: dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj --configuration Release
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ jobs:
shell: pwsh
run: dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj --configuration Release

- name: Run Windows Settings UI smoke test
shell: pwsh
run: dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj --configuration Release

- name: Publish framework-dependent Windows app
shell: pwsh
run: >-
Expand Down
7 changes: 7 additions & 0 deletions AutoPowerMode.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{885E9D21
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPowerMode.Tests", "tests\AutoPowerMode.Tests\AutoPowerMode.Tests.csproj", "{25EA1EF4-9A28-41A0-867E-78E71FCBA47B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPowerMode.WindowsUi.Tests", "tests\AutoPowerMode.WindowsUi.Tests\AutoPowerMode.WindowsUi.Tests.csproj", "{1AD8DC52-50DF-45C3-88D2-CE8047C396DD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -28,9 +30,14 @@ Global
{25EA1EF4-9A28-41A0-867E-78E71FCBA47B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25EA1EF4-9A28-41A0-867E-78E71FCBA47B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25EA1EF4-9A28-41A0-867E-78E71FCBA47B}.Release|Any CPU.Build.0 = Release|Any CPU
{1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AD8DC52-50DF-45C3-88D2-CE8047C396DD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2063FF39-BF7A-4775-B8B2-4176944BFABC} = {BBAC319F-8D99-4F35-A6E2-163EAF32C29C}
{25EA1EF4-9A28-41A0-867E-78E71FCBA47B} = {885E9D21-A0F5-4333-A557-CC623A1922A0}
{1AD8DC52-50DF-45C3-88D2-CE8047C396DD} = {885E9D21-A0F5-4333-A557-CC623A1922A0}
EndGlobalSection
EndGlobal
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# AutoPowerMode Changelog

## [v1.3.3] - 2026-07-12

- 修复 v1.3.2 在 Windows 打开设置窗口时可能抛出 `TableLayoutPanel` 已满异常的问题:响应式文字宽度和控件尺寸现在会在暂停子布局期间一次性更新,并允许布局表在 WinForms 需要时安全扩展行。
- 新增 Windows 专用设置窗口冒烟测试,实际打开中英文设置页并在宽、窄窗口间反复缩放,同时确认不会出现横向滚动;PR 和正式发布流程都会在 Windows runner 上执行该测试。

- Fixed a v1.3.2 startup error that could occur when opening Settings on Windows because a full fixed-size `TableLayoutPanel` relaid itself while responsive text constraints were still being updated. Child layout updates are now applied as one suspended batch, and the table can safely add a row if WinForms requires one.
- Added a Windows-only Settings UI smoke test that opens both localized forms, repeatedly resizes between wide and narrow layouts, and verifies that horizontal scrolling stays hidden. The test now gates both pull requests and releases on a Windows runner.

## [v1.3.2] - 2026-07-12

- 修复设置窗口缩小后仍出现横向滚动和左侧内容被裁切的问题:窄窗口自动切换为上下排列,长状态和选项文字可换行,并为纵向滚动条预留宽度;底部操作栏继续固定显示。
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# AutoPowerMode

Current version: v1.3.2
Current version: v1.3.3

AutoPowerMode is a Windows-only system tray application that switches Windows power plans automatically according to whether the current user is actively using the computer.

Expand Down Expand Up @@ -50,6 +50,7 @@ src/AutoPowerMode/ Application source
Services/ Windows and local-system services
UI/ Tray, settings, and diagnostics UI
tests/AutoPowerMode.Tests/ Logic tests
tests/AutoPowerMode.WindowsUi.Tests/ Windows-only Settings UI smoke test
docs/reviews/ Historical release reviews
archive/ Git-ignored local build history
```
Expand All @@ -59,6 +60,8 @@ archive/ Git-ignored local build history
```bash
dotnet build AutoPowerMode.sln --configuration Release
dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj
# Windows only:
dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj
```

The tests cover configuration migration, independently configurable dual-rate monitoring, idle protections, language selection and persistence, power-plan parsing, switch policies, notifications, DPI layout, log rotation and path sanitization, startup registration, and external override protection.
The logic tests cover configuration migration, independently configurable dual-rate monitoring, idle protections, language selection and persistence, power-plan parsing, switch policies, notifications, DPI layout, log rotation and path sanitization, startup registration, and external override protection. The Windows-only smoke test opens the localized Settings form and exercises both wide and compact layouts.
7 changes: 5 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# AutoPowerMode

当前版本:v1.3.2
当前版本:v1.3.3

AutoPowerMode 是一个 Windows-only 桌面托盘程序,用于根据当前用户是否正在使用电脑,自动切换 Windows 电源计划。

Expand Down Expand Up @@ -50,6 +50,7 @@ src/AutoPowerMode/ 应用源码
Services/ Windows 和本地系统服务
UI/ 托盘、设置和诊断界面
tests/AutoPowerMode.Tests/ 纯逻辑自动测试
tests/AutoPowerMode.WindowsUi.Tests/ Windows 专用设置窗口冒烟测试
docs/reviews/ 历史版本审查记录
archive/ 被 Git 忽略的本地构建历史
```
Expand All @@ -59,6 +60,8 @@ archive/ 被 Git 忽略的本地构建历史
```bash
dotnet build AutoPowerMode.sln --configuration Release
dotnet run --project tests/AutoPowerMode.Tests/AutoPowerMode.Tests.csproj
# 仅限 Windows:
dotnet run --project tests/AutoPowerMode.WindowsUi.Tests/AutoPowerMode.WindowsUi.Tests.csproj
```

测试覆盖配置迁移、可独立配置的双频检测策略、空闲误触保护、语言选择与持久化、电源计划解析、切换策略、通知、DPI 布局、日志轮转与路径脱敏、开机自启和外部覆盖保护。
逻辑测试覆盖配置迁移、可独立配置的双频检测策略、空闲误触保护、语言选择与持久化、电源计划解析、切换策略、通知、DPI 布局、日志轮转与路径脱敏、开机自启和外部覆盖保护。Windows 专用冒烟测试会实际打开中英文设置窗口并验证宽窄布局切换
2 changes: 1 addition & 1 deletion src/AutoPowerMode/AppInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace AutoPowerMode;
public static class AppInfo
{
public const string Name = "AutoPowerMode";
public const string Version = "v1.3.2";
public const string Version = "v1.3.3";
public const string DisplayName = $"{Name} {Version}";
public const string RepositoryUrl = "https://github.com/VailElla/AutoPowerMode";
}
6 changes: 3 additions & 3 deletions src/AutoPowerMode/AutoPowerMode.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<Nullable>enable</Nullable>
<AssemblyName>AutoPowerMode</AssemblyName>
<RootNamespace>AutoPowerMode</RootNamespace>
<Version>1.3.2</Version>
<AssemblyVersion>1.3.2.0</AssemblyVersion>
<FileVersion>1.3.2.0</FileVersion>
<Version>1.3.3</Version>
<AssemblyVersion>1.3.3.0</AssemblyVersion>
<FileVersion>1.3.3.0</FileVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
Expand Down
24 changes: 18 additions & 6 deletions src/AutoPowerMode/UI/SettingsForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ private void BuildLayout(IReadOnlyList<PowerPlan> powerPlans)
root.AutoSizeMode = AutoSizeMode.GrowAndShrink;
root.Padding = new Padding(12, 12, 12, 6);
root.Margin = Padding.Empty;
root.GrowStyle = TableLayoutPanelGrowStyle.FixedSize;
root.GrowStyle = TableLayoutPanelGrowStyle.AddRows;

ConfigureTextLabel(_automationStatusLabel, LocalizationService.Text("AutomationStatus"));
ConfigureTextLabel(_idleThresholdLabel, LocalizationService.Text("IdleSwitchDelay"));
Expand Down Expand Up @@ -403,12 +403,24 @@ private Size LayoutSettingsAtWidth(int requestedWidth)
{
var contentWidth = Math.Max(1, requestedWidth);
var compact = DpiLayoutPolicy.ShouldUseCompactSettingsLayout(DeviceDpi, contentWidth);
ArrangeSettingsControls(compact);
ConfigureWrappingConstraints(contentWidth, compact);

_root.MinimumSize = new Size(contentWidth, 0);
_root.MaximumSize = new Size(contentWidth, 0);
_root.Width = contentWidth;
_root.SuspendLayout();
_idleProtectionOptions.SuspendLayout();
try
{
ArrangeSettingsControls(compact);
ConfigureWrappingConstraints(contentWidth, compact);

_root.MinimumSize = new Size(contentWidth, 0);
_root.MaximumSize = new Size(contentWidth, 0);
_root.Width = contentWidth;
}
finally
{
_idleProtectionOptions.ResumeLayout(performLayout: false);
_root.ResumeLayout(performLayout: false);
}

_root.PerformLayout();

var preferredSize = _root.GetPreferredSize(new Size(contentWidth, 0));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>AutoPowerMode.WindowsUi.Tests</AssemblyName>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../../src/AutoPowerMode/AutoPowerMode.csproj" />
</ItemGroup>
</Project>
117 changes: 117 additions & 0 deletions tests/AutoPowerMode.WindowsUi.Tests/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using AutoPowerMode;
using System.Drawing;
using System.Windows.Forms;

namespace AutoPowerMode.WindowsUi.Tests;

internal static class Program
{
[STAThread]
private static int Main()
{
try
{
ApplicationConfiguration.Initialize();
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);

VerifySettingsLayout(AppLanguagePreference.SimplifiedChinese);
VerifySettingsLayout(AppLanguagePreference.English);

Console.WriteLine("PASS SettingsForm opens and resizes without horizontal scrolling.");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine("FAIL SettingsForm Windows UI smoke test.");
Console.Error.WriteLine(ex);
return 1;
}
}

private static void VerifySettingsLayout(string languagePreference)
{
LocalizationService.UsePreference(languagePreference);

var plans = new[]
{
new PowerPlan
{
Guid = "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c",
Name = "High Performance",
IsActive = true
},
new PowerPlan
{
Guid = "a1841308-3541-4fab-bc81-f71556f20b4a",
Name = "Power Saver"
}
};
var config = new AppConfig
{
IdleThresholdSeconds = 1000,
ActiveCheckIntervalSeconds = 30,
IdleCheckIntervalSeconds = 1,
ActivePowerPlanGuid = plans[0].Guid,
IdlePowerPlanGuid = plans[1].Guid,
NotificationsEnabled = true,
Language = languagePreference,
PowerPlansConfiguredByUser = true
};

using var form = new SettingsForm(
config,
plans,
LocalizationService.Text("StatusWaiting"))
{
ShowInTaskbar = false
};

form.Show();
ProcessPendingLayout(form);

foreach (var clientSize in new[]
{
new Size(700, 520),
new Size(400, 240),
new Size(460, 270),
new Size(900, 600)
})
{
form.ClientSize = clientSize;
ProcessPendingLayout(form);

var scrollPanel = Descendants(form)
.OfType<Panel>()
.Single(control => control.AutoScroll);
if (scrollPanel.HorizontalScroll.Visible)
{
throw new InvalidOperationException(
$"Horizontal scrolling appeared at {form.ClientSize.Width}x{form.ClientSize.Height}.");
}
}

form.Close();
Application.DoEvents();
}

private static void ProcessPendingLayout(Control control)
{
control.PerformLayout();
Application.DoEvents();
control.PerformLayout();
Application.DoEvents();
}

private static IEnumerable<Control> Descendants(Control parent)
{
foreach (Control child in parent.Controls)
{
yield return child;

foreach (var descendant in Descendants(child))
{
yield return descendant;
}
}
}
}