Skip to content
Open
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
16 changes: 13 additions & 3 deletions app/GHelper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<UseWinRT>true</UseWinRT>
<Nullable>enable</Nullable>
<UseWindowsForms>True</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
Expand All @@ -16,6 +18,11 @@
<ProduceReferenceAssembly>False</ProduceReferenceAssembly>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.230</AssemblyVersion>
<EnableMsixTooling>true</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<IncludeAllContentForSelfExtract >true</IncludeAllContentForSelfExtract>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<SelfContained>true</SelfContained>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down Expand Up @@ -49,8 +56,6 @@
<None Remove="Resources\ultimate.ico" />
</ItemGroup>



<ItemGroup>
<Content Include="favicon.ico">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
Expand All @@ -60,6 +65,7 @@
<ItemGroup>
<PackageReference Include="FftSharp" Version="2.2.0" />
<PackageReference Include="HidSharpCore" Version="1.3.0" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.241114003" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="NvAPIWrapper.Net" Version="0.8.1.101" />
<PackageReference Include="System.Management" Version="9.0.9" />
Expand Down Expand Up @@ -128,6 +134,10 @@
</None>
</ItemGroup>

<ItemGroup>
<Folder Include="Tools\" />
</ItemGroup>

<Target Name="ZipSingleExe" AfterTargets="Publish" Condition="'$(GITHUB_ACTIONS)'!='true'">
<Exec Command="powershell -Command &quot;Compress-Archive -Path '$(PublishDir)GHelper.exe' -DestinationPath '$(PublishDir)GHelper.zip' -Force&quot;" />
</Target>
Expand Down
4 changes: 2 additions & 2 deletions app/GHelper.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33403.182
# Visual Studio Version 18
VisualStudioVersion = 18.0.11012.119 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GHelper", "GHelper.csproj", "{D6138BB1-8FDB-4835-87EF-2FE41A3DD604}"
EndProject
Expand Down
259 changes: 259 additions & 0 deletions app/Helpers/AmbientLightSensor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
using System;
using System.Threading.Tasks;
using Windows.Devices.Sensors;

namespace GHelper.Helpers
{
public static class AmbientLightSensor
{
private static LightSensor? _lightSensor;
private static uint _currentLuxValue = 0;
private static bool _isInitialized = false;
private static bool _sensorAvailable = false;
private static bool _isMonitoring = false;
private static DateTime _lastReadingTime = DateTime.MinValue;

// Exponential smoothing parameters
private static double _smoothedLux = -1;
private static double _smoothingFactor; // Alpha
private static int _maxChangePerSecond;

public static event EventHandler<LightLevelChangedEventArgs>? LightLevelChanged;

public static int GetCurrentLux()
{
return (int)Math.Round(_smoothedLux >= 0 ? _smoothedLux : _currentLuxValue);
}

public static bool IsAvailable()
{
if (_isInitialized) return _sensorAvailable;

try
{
_lightSensor = LightSensor.GetDefault();
_sensorAvailable = _lightSensor != null;

if (_sensorAvailable)
{
Logger.WriteLine("Windows Runtime LightSensor found and available");

// Set minimum report interval to reduce battery usage
var minInterval = _lightSensor.MinimumReportInterval;
var desiredInterval = Math.Max(minInterval, 1000); // At least 1 second
_lightSensor.ReportInterval = desiredInterval;

Logger.WriteLine($"Sensor intervals - Min: {minInterval}ms, Set: {desiredInterval}ms");
}
else
{
Logger.WriteLine("No Windows Runtime LightSensor available");
}
}
catch (Exception ex)
{
Logger.WriteLine($"Failed to detect ambient light sensor: {ex.Message}");
_sensorAvailable = false;
}

_isInitialized = true;
Logger.WriteLine($"Ambient light sensor availability: {_sensorAvailable}");
return _sensorAvailable;
}

public static void Initialize()
{
if (_isInitialized) return;

// Load smoothing settings
if (!AppConfig.Exists("als_smoothing_factor")) AppConfig.Set("als_smoothing_factor", 40); // 0.4
if (!AppConfig.Exists("als_max_change_per_sec")) AppConfig.Set("als_max_change_per_sec", 50);

_smoothingFactor = AppConfig.Get("als_smoothing_factor", 40) / 100.0;
_maxChangePerSecond = AppConfig.Get("als_max_change_per_sec", 50);

_sensorAvailable = IsAvailable();

if (!_sensorAvailable)
{
Logger.WriteLine("Ambient light sensor not available");
}

_isInitialized = true;
Logger.WriteLine("Ambient light sensor initialized");
}

public static void Start()
{
if (!_isInitialized) return;

if (_sensorAvailable && _lightSensor != null && !_isMonitoring)
{
try
{
// Reset smoothed value on start
_smoothedLux = -1;

// Subscribe to sensor reading changes
_lightSensor.ReadingChanged += OnSensorReadingChanged;
_isMonitoring = true;

Logger.WriteLine("Ambient light sensor monitoring started (event-driven)");

// Get initial reading
Task.Run(async () =>
{
await Task.Delay(500); // Small delay to let sensor initialize
TriggerInitialReading();
});
}
catch (Exception ex)
{
Logger.WriteLine($"Failed to start sensor monitoring: {ex.Message}");
}
}
else
{
Logger.WriteLine("Ambient light sensor not available - no automatic backlight control");
}
}

public static void Stop()
{
if (_lightSensor != null && _isMonitoring)
{
try
{
_lightSensor.ReadingChanged -= OnSensorReadingChanged;
_isMonitoring = false;
Logger.WriteLine("Ambient light sensor monitoring stopped");
}
catch (Exception ex)
{
Logger.WriteLine($"Error stopping sensor: {ex.Message}");
}
}
}

private static void OnSensorReadingChanged(LightSensor sender, LightSensorReadingChangedEventArgs args)
{
try
{
var reading = args.Reading;
var rawLuxValue = reading?.IlluminanceInLux;
if (rawLuxValue.HasValue)
{
var newRawLux = (uint)Math.Max(0, Math.Round(rawLuxValue.Value));
var previousSmoothedLux = _smoothedLux;
var now = DateTime.Now;

// Initialize smoothed value on first reading
if (_smoothedLux < 0)
{
_smoothedLux = newRawLux;
}

// Apply exponential smoothing
var newSmoothedLux = _smoothingFactor * newRawLux + (1 - _smoothingFactor) * _smoothedLux;

// Apply rate-of-change limiting
if (previousSmoothedLux >= 0)
{
var timeDelta = (now - _lastReadingTime).TotalSeconds;
if (timeDelta > 0)
{
var maxChange = _maxChangePerSecond * timeDelta;
var change = newSmoothedLux - previousSmoothedLux;

if (Math.Abs(change) > maxChange)
{
newSmoothedLux = previousSmoothedLux + Math.Sign(change) * maxChange;
Logger.WriteLine($"Rate limit applied: Raw={newRawLux}, Change={change:F1}, Limited to={maxChange:F1}");
}
}
}

// Only trigger events if the smoothed value has changed meaningfully
if (Math.Abs(newSmoothedLux - _smoothedLux) >= 1)
{
var previousValue = (int)Math.Round(_smoothedLux);
_smoothedLux = newSmoothedLux;
_currentLuxValue = newRawLux; // Keep track of raw value for logging
_lastReadingTime = now;

var finalLux = (int)Math.Round(_smoothedLux);

Logger.WriteLine($"Light level changed: Raw={newRawLux}, Smoothed={finalLux} lux");

LightLevelChanged?.Invoke(null, new LightLevelChangedEventArgs
{
CurrentLux = finalLux,
PreviousLux = previousValue,
Timestamp = _lastReadingTime
});
}
}
else
{
Logger.WriteLine("Sensor reading returned null lux value");
}
}
catch (Exception ex)
{
Logger.WriteLine($"Error processing sensor reading: {ex.Message}");
}
}

private static void TriggerInitialReading()
{
try
{
if (_lightSensor != null)
{
var reading = _lightSensor.GetCurrentReading();
var luxValue = reading?.IlluminanceInLux;
if (luxValue.HasValue)
{
_currentLuxValue = (uint)Math.Max(0, Math.Round(luxValue.Value));
_smoothedLux = _currentLuxValue; // Initialize smoothed value
_lastReadingTime = DateTime.Now;

Logger.WriteLine($"Initial sensor reading: {_currentLuxValue} lux");

LightLevelChanged?.Invoke(null, new LightLevelChangedEventArgs
{
CurrentLux = (int)_currentLuxValue,
PreviousLux = -1,
Timestamp = _lastReadingTime
});
}
else
{
Logger.WriteLine("Initial sensor reading returned null");
}
}
}
catch (Exception ex)
{
Logger.WriteLine($"Error getting initial reading: {ex.Message}");
}
}

public static DateTime GetLastReadingTime()
{
return _lastReadingTime;
}

public static bool IsSensorActive()
{
return _sensorAvailable && _isMonitoring;
}
}

public class LightLevelChangedEventArgs : EventArgs
{
public int CurrentLux { get; set; }
public int PreviousLux { get; set; }
public DateTime Timestamp { get; set; }
}
}
Loading