diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3839048..8439b28 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle -name: Java CI with Gradle +name: Edge builds on: workflow_dispatch: @@ -23,44 +23,18 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.200' - - name: Set up JDK 21 + + - name: Set up JDK uses: actions/setup-java@v4 with: java-version: '22' distribution: 'corretto' - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. - # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - - name: Setup Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - - name: Build with Gradle Wrapper - run: ./gradlew :target:desktop:createDistributable + run: cd app; ./gradlew :target:desktop:createDistributable - name: Archive production artifacts uses: actions/upload-artifact@v4 with: name: cleanmeter - path: target\desktop\build\compose\binaries\main\app - - dependency-submission: - - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 22 - uses: actions/setup-java@v4 - with: - java-version: '22' - distribution: 'corretto' - - # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. - # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + path: app\target\desktop\build\compose\binaries\main\app diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..ee7c2fc --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,110 @@ +name: Nightly Release + +on: + schedule: + # Run every night at 2 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: + +jobs: + check-changes: + name: Check for changes since last release + runs-on: ubuntu-latest + outputs: + has-changes: ${{ steps.check.outputs.has-changes }} + new-tag: ${{ steps.tag.outputs.new-tag }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get latest release tag + id: latest-release + run: | + latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + echo "latest-tag=$latest_tag" >> $GITHUB_OUTPUT + echo "Latest release tag: $latest_tag" + + - name: Check for changes since last release + id: check + run: | + latest_tag="${{ steps.latest-release.outputs.latest-tag }}" + if [ -z "$latest_tag" ]; then + echo "No previous releases found, will create first release" + echo "has-changes=true" >> $GITHUB_OUTPUT + else + # Check if there are commits since the last release + commits_since=$(git rev-list --count $latest_tag..HEAD 2>/dev/null || echo "1") + echo "Commits since last release: $commits_since" + if [ "$commits_since" -gt "0" ]; then + echo "has-changes=true" >> $GITHUB_OUTPUT + else + echo "has-changes=false" >> $GITHUB_OUTPUT + fi + fi + + - name: Generate new tag + id: tag + if: steps.check.outputs.has-changes == 'true' + run: | + # Generate nightly tag with date + new_tag="nightly-$(date +%Y%m%d)" + echo "new-tag=$new_tag" >> $GITHUB_OUTPUT + echo "New tag will be: $new_tag" + + build-and-release: + name: Build and create nightly release + runs-on: windows-latest + needs: check-changes + if: needs.check-changes.outputs.has-changes == 'true' + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: '22' + distribution: 'corretto' + + - name: Build with Gradle Wrapper + run: cd app; ./gradlew :target:desktop:createDistributable + + - name: Zip distributable + run: | + cd app + Compress-Archive -Path "target/desktop/build/compose/binaries/main/app" -DestinationPath "../cleanmeter-nightly.zip" + + - name: Create Release + id: create-release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ needs.check-changes.outputs.new-tag }} + release_name: Nightly Release ${{ needs.check-changes.outputs.new-tag }} + body: | + 🌙 **Nightly Release** - Automatically generated from latest changes on main branch + + This is an automated nightly build containing the latest changes from the main branch. + + **⚠️ Note:** This is a development build and may contain unstable features. + + **Installation:** Download and extract the cleanmeter-nightly.zip file. + + Built from commit: ${{ github.sha }} + draft: false + prerelease: true + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: ./cleanmeter-nightly.zip + asset_name: cleanmeter-nightly.zip + asset_content_type: application/zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a0bd399 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,37 @@ +name: Publish + +on: + workflow_dispatch: + push: + tags: + - '*' + +jobs: + build: + name: Publish binaries + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: '22' + distribution: 'corretto' + + - name: Build with Gradle Wrapper + run: cd app; ./gradlew :target:desktop:createDistributable + + - name: Zip distributable + run: zip -r cleanmeter.zip target/desktop/build/compose/binaries/main/app + + - name: Upload binaries to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: cleanmeter.zip + asset_name: cleanmeter + tag: ${{ github.ref }} + overwrite: false + draft: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index a684c59..7f15036 100644 --- a/.gitignore +++ b/.gitignore @@ -155,4 +155,7 @@ bin/ /httpRequests/ # Datasource local storage ignored files /dataSources/ -/dataSources.local.xml \ No newline at end of file +/dataSources.local.xml + +!app/bin/*.bat +preferences.json diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 9d08854..312bf2e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,13 +1,6 @@ - - - - - - - - + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 9fc29e4..0819acb 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,7 +1,10 @@ + + - diff --git a/HardwareMonitor/HardwareMonitor/Monitor/MonitorPacketCommand.cs b/HardwareMonitor/HardwareMonitor/Monitor/MonitorPacketCommand.cs index 9385409..3f9d56b 100644 --- a/HardwareMonitor/HardwareMonitor/Monitor/MonitorPacketCommand.cs +++ b/HardwareMonitor/HardwareMonitor/Monitor/MonitorPacketCommand.cs @@ -6,5 +6,6 @@ public enum MonitorPacketCommand : short RefreshPresentMonApps = 1, SelectPresentMonApp = 2, PresentMonApps = 3, - SelectPollingRate = 4 + SelectPollingRate = 4, + SetForegroundApplication = 5 } \ No newline at end of file diff --git a/HardwareMonitor/HardwareMonitor/Monitor/MonitorPoller.cs b/HardwareMonitor/HardwareMonitor/Monitor/MonitorPoller.cs index 5a49adb..1219f4a 100644 --- a/HardwareMonitor/HardwareMonitor/Monitor/MonitorPoller.cs +++ b/HardwareMonitor/HardwareMonitor/Monitor/MonitorPoller.cs @@ -15,7 +15,7 @@ namespace HardwareMonitor.Monitor; public class MonitorPoller( IHostApplicationLifetime hostApplicationLifetime, ILogger logger -) : BackgroundService +) : BackgroundService, IDisposable { private readonly Computer _computer = new() { @@ -38,68 +38,99 @@ ILogger logger protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - logger.LogInformation("Starting monitor"); + logger.LogInformation("Starting hardware monitor service"); - _computer.Open(); - _computer.Accept(new UpdateVisitor()); - _presentMonPoller.Start(stoppingToken); - _presentMonPoller.OnUpdateApps += SendPresentMonAppsToClients; - _socketHost.StartServer(); - _socketHost.OnClientData += OnClientData; - _socketHost.OnClientConnected += OnClientConnected; + try + { + _computer.Open(); + _computer.Accept(new UpdateVisitor()); + _presentMonPoller.Start(stoppingToken); + _presentMonPoller.OnUpdateApps += SendPresentMonAppsToClients; + _socketHost.StartServer(); + _socketHost.OnClientData += OnClientData; + _socketHost.OnClientConnected += OnClientConnected; - var sharedMemoryData = QueryHardwareData(); + var sharedMemoryData = QueryHardwareData(); - using var memoryStream = new MemoryStream(); - using var writer = new BinaryWriter(memoryStream); - var accumulator = 0; + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + var accumulator = 0; - WriteDataToStream(writer, sharedMemoryData); + WriteDataToStream(writer, sharedMemoryData); - while (!stoppingToken.IsCancellationRequested) - { - if (!_socketHost.HasConnections()) - { - //logger.LogInformation("No clients connected, waiting for connections..."); - await Task.Delay(1000, stoppingToken); - continue; - } + logger.LogInformation("Hardware monitor service started successfully"); - foreach (var hardware in sharedMemoryData.Hardwares) + while (!stoppingToken.IsCancellationRequested) { - try + if (!_socketHost.HasConnections()) { - hardware.Update(); + //logger.LogInformation("No clients connected, waiting for connections..."); + await Task.Delay(1000, stoppingToken); + continue; } - catch + + foreach (var hardware in sharedMemoryData.Hardwares) { - hardware.StopUpdates(); - logger.LogError("Stopping updates of {HardwareName} - {HardwareIdentifier}", hardware.Name, hardware.Identifier); + try + { + hardware.Update(); + } + catch + { + hardware.StopUpdates(); + logger.LogError("Stopping updates of {HardwareName} - {HardwareIdentifier}", hardware.Name, + hardware.Identifier); + } } - } - WriteDataToStream(writer, sharedMemoryData); + WriteDataToStream(writer, sharedMemoryData); - if (_socketHost.HasConnections()) - { - _socketHost.SendToAll(memoryStream.ToArray()); - } else - { - //logger.LogInformation("No clients connected, not sending data"); - } + if (_socketHost.HasConnections()) + { + _socketHost.SendToAll(memoryStream.ToArray()); + } + else + { + //logger.LogInformation("No clients connected, not sending data"); + } - if (accumulator >= 1000) - { - GC.Collect(); - accumulator = 0; - } + if (accumulator >= 1000) + { + GC.Collect(); + accumulator = 0; + } - accumulator += 500; - await Task.Delay(_pollingRate, stoppingToken); + accumulator += 500; + await Task.Delay(_pollingRate, stoppingToken); + } + } + catch (OperationCanceledException) + { + logger.LogInformation("Hardware monitor service shutdown requested"); } + catch (Exception ex) + { + logger.LogError(ex, "Unexpected error in hardware monitor service"); + throw; + } + finally + { + logger.LogInformation("Shutting down hardware monitor service"); + } + } - Stop(); - hostApplicationLifetime.StopApplication(); + public override async Task StopAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Stop requested for hardware monitor service"); + + try + { + await base.StopAsync(cancellationToken); + } + finally + { + Stop(); + } } private static void WriteDataToStream(BinaryWriter writer, SharedMemoryData sharedMemoryData) @@ -155,6 +186,9 @@ private void OnClientData(byte[] data) case MonitorPacketCommand.SelectPollingRate: SelectPollingRate(data); break; + case MonitorPacketCommand.SetForegroundApplication: + SetForegroundApplication(data); + break; // server -> client cases case MonitorPacketCommand.Data: @@ -181,6 +215,14 @@ private void SelectPresentMonApp(byte[] data) _presentMonPoller.SetSelectedApp(appName); } + private void SetForegroundApplication(byte[] data) + { + // start at 2 because the first 2 were the command + var size = BitConverter.ToInt16(data, 2); + var appName = Encoding.UTF8.GetString(data, 4, size); + _presentMonPoller.SetForegroundApplication(appName); + } + private void SendPresentMonAppsToClients() { using var memoryStream = new MemoryStream(); @@ -244,10 +286,37 @@ private SharedMemoryData QueryHardwareData() private void Stop() { - _computer.Close(); - _presentMonPoller.Stop(); - _socketHost.Close(); - _socketHost.OnClientData -= OnClientData; + logger.LogInformation("Stopping monitor services"); + + try + { + _presentMonPoller.Stop(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error stopping PresentMon poller"); + } + + try + { + _socketHost.Close(); + _socketHost.OnClientData -= OnClientData; + } + catch (Exception ex) + { + logger.LogError(ex, "Error closing socket host"); + } + + try + { + _computer.Close(); + } + catch (Exception ex) + { + logger.LogError(ex, "Error closing hardware computer"); + } + + logger.LogInformation("Monitor services stopped"); } private static SharedMemoryHardware MapHardware(IHardware hardware) => new() @@ -283,4 +352,11 @@ public static unsafe bool IsNaN(float f) int binary = *(int*)(&f); return ((binary & 0x7F800000) == 0x7F800000) && ((binary & 0x007FFFFF) != 0); } + + public void Dispose() + { + Stop(); + _computer?.Close(); + GC.SuppressFinalize(this); + } } \ No newline at end of file diff --git a/HardwareMonitor/HardwareMonitor/PresentMon/PresentMonPoller.cs b/HardwareMonitor/HardwareMonitor/PresentMon/PresentMonPoller.cs index bc93a98..8d76da9 100644 --- a/HardwareMonitor/HardwareMonitor/PresentMon/PresentMonPoller.cs +++ b/HardwareMonitor/HardwareMonitor/PresentMon/PresentMonPoller.cs @@ -24,6 +24,7 @@ public class PresentMonPoller(ILogger logger) private CultureInfo _cultureInfo = (CultureInfo)CultureInfo.CurrentCulture.Clone(); private string _currentSelectedApp = NO_SELECTED_APP; + private string _currentForegroundApp; public async void Start(CancellationToken stoppingToken) { @@ -68,7 +69,31 @@ public async void Start(CancellationToken stoppingToken) public void Stop() { - _process.Kill(true); + try + { + if (_process != null && !_process.HasExited) + { + logger.LogInformation("Stopping PresentMon process"); + + // Try graceful shutdown first + _process.CancelOutputRead(); + _process.CancelErrorRead(); + + // Give it a moment to exit gracefully + if (!_process.WaitForExit(1000)) + { + // Force kill if it doesn't exit gracefully + _process.Kill(true); + } + + _process.Dispose(); + logger.LogInformation("PresentMon process stopped"); + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error stopping PresentMon process"); + } } private void ParseData(string? argsData) @@ -84,6 +109,11 @@ private void ParseData(string? argsData) return; } + if (_currentSelectedApp == NO_SELECTED_APP && _currentForegroundApp != parts[0]) + { + return; + } + if (float.TryParse(parts[9], NumberStyles.Any, _cultureInfo, out var frametime)) { Frametime.Value = frametime; @@ -112,6 +142,11 @@ public void SetSelectedApp(string appName) _currentSelectedApp = appName; } + public void SetForegroundApplication(string appName) + { + _currentForegroundApp = appName; + } + private async Task TerminateCurrentPresentMon() { var processStartInfo = new ProcessStartInfo diff --git a/HardwareMonitor/HardwareMonitor/Program.cs b/HardwareMonitor/HardwareMonitor/Program.cs index 3906db8..7ccd7dc 100644 --- a/HardwareMonitor/HardwareMonitor/Program.cs +++ b/HardwareMonitor/HardwareMonitor/Program.cs @@ -3,22 +3,86 @@ using HardwareMonitor.Monitor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Serilog; var builder = Host.CreateDefaultBuilder(args) - .ConfigureServices(services => services.AddHostedService()) - .UseWindowsService() - .UseSerilog((context, services, loggerConfiguration) => loggerConfiguration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext() - .WriteTo.File( - Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LogFiles", - $"{DateTime.Now.Year}-{DateTime.Now.Month}-{DateTime.Now.Day}", "Log.txt"), - rollingInterval: RollingInterval.Infinite, - outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level}] {Message}{NewLine}{Exception}") - .WriteTo.Console() - ); + .ConfigureServices(services => + { + services.AddHostedService(); + services.Configure(options => + { + options.ShutdownTimeout = TimeSpan.FromSeconds(30); + }); + }) + .UseWindowsService(options => + { + options.ServiceName = "CleanMeter Hardware Monitor"; + }) + .ConfigureLogging((context, logging) => + { + logging.ClearProviders(); + if (Environment.UserInteractive) + { + logging.AddConsole(); + } + logging.AddEventLog(); + }) + .UseSerilog((context, services, loggerConfiguration) => + { + var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LogFiles"); + Directory.CreateDirectory(logPath); + + loggerConfiguration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.File( + Path.Combine(logPath, "cleanmeter-hardware-monitor-.log"), + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 30, + outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level}] {Message}{NewLine}{Exception}"); + + // Only log to console when running interactively (not as service) + if (Environment.UserInteractive) + { + loggerConfiguration.WriteTo.Console(); + } + }); var host = builder.Build(); -host.Run(); \ No newline at end of file + +// Handle Windows shutdown signals +var lifetime = host.Services.GetRequiredService(); +var logger = host.Services.GetRequiredService>(); + +lifetime.ApplicationStopping.Register(() => +{ + logger.LogInformation("Application shutdown signal received"); +}); + +// Handle console cancel events (Ctrl+C) only when running interactively +if (Environment.UserInteractive) +{ + Console.CancelKeyPress += (sender, e) => + { + logger.LogInformation("Console cancel event received"); + e.Cancel = true; + lifetime.StopApplication(); + }; +} + +try +{ + logger.LogInformation("Starting CleanMeter Hardware Monitor Service"); + await host.RunAsync(); +} +catch (Exception ex) +{ + logger.LogCritical(ex, "Application terminated unexpectedly"); + throw; +} +finally +{ + logger.LogInformation("CleanMeter Hardware Monitor Service stopped"); +} \ No newline at end of file diff --git a/HardwareMonitor/HardwareMonitor/Sockets/PipeHost.cs b/HardwareMonitor/HardwareMonitor/Sockets/PipeHost.cs index 4e4aae5..6d8a523 100644 --- a/HardwareMonitor/HardwareMonitor/Sockets/PipeHost.cs +++ b/HardwareMonitor/HardwareMonitor/Sockets/PipeHost.cs @@ -1,10 +1,14 @@ -using System.IO.Pipes; +using System.Diagnostics.CodeAnalysis; +using System.IO.Pipes; +using System.Security.AccessControl; +using System.Security.Principal; using Microsoft.Extensions.Logging; // ReSharper disable FieldCanBeMadeReadOnly.Local #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. namespace HardwareMonitor.Sockets; +[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] public class PipeHost(ILogger logger) { private readonly string _pipeName = "HardwareMonitor_31337"; @@ -27,14 +31,18 @@ private async Task AcceptClientsAsync() { try { - var pipeServer = new NamedPipeServerStream( + var pipeSecurity = new PipeSecurity(); + var everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + pipeSecurity.AddAccessRule(new PipeAccessRule(everyoneSid, PipeAccessRights.FullControl, AccessControlType.Allow)); + var pipeServer = NamedPipeServerStreamAcl.Create( _pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 4096, // inBufferSize - 4096 // outBufferSize + 4096, // outBufferSize + pipeSecurity ); logger.LogInformation("Waiting for client connection on pipe: {PipeName}", _pipeName); @@ -130,7 +138,8 @@ public void Close() try { - _serverTask?.Wait(5000); + // Reduce timeout for faster shutdown during Windows shutdown + _serverTask?.Wait(1000); } catch { } diff --git a/HardwareMonitor/HardwareMonitorTester/HardwareMonitorTester.csproj b/HardwareMonitor/HardwareMonitorTester/HardwareMonitorTester.csproj deleted file mode 100644 index c2a4ef4..0000000 --- a/HardwareMonitor/HardwareMonitorTester/HardwareMonitorTester.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - diff --git a/HardwareMonitor/HardwareMonitorTester/Program.cs b/HardwareMonitor/HardwareMonitorTester/Program.cs deleted file mode 100644 index 72571bd..0000000 --- a/HardwareMonitor/HardwareMonitorTester/Program.cs +++ /dev/null @@ -1,30 +0,0 @@ -// See https://aka.ms/new-console-template for more information - -using System.Net; -using System.Net.Sockets; -using HardwareMonitor.Monitor; - -await (new TestClass()).Main(); - -public class TestClass -{ - byte[] buffer = new byte[500_000]; - - public async Task Main() - { - var ipAddress = IPAddress.Loopback; - var localEndPoint = new IPEndPoint(ipAddress, 31337); - - var socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - await socket.ConnectAsync(localEndPoint); - var buffer = new byte[500_000]; - - while (true) - { - // Receive ack. - var received = await socket.ReceiveAsync(buffer, SocketFlags.None); - var command = (MonitorPacketCommand)BitConverter.ToInt16(buffer, 0); - Console.WriteLine($"Received {command} {received} bytes"); - } - } -} \ No newline at end of file diff --git a/HardwareMonitor/Updater/Program.cs b/HardwareMonitor/Updater/Program.cs deleted file mode 100644 index 6737561..0000000 --- a/HardwareMonitor/Updater/Program.cs +++ /dev/null @@ -1,92 +0,0 @@ -// See https://aka.ms/new-console-template for more information - -using System.Diagnostics; -using System.IO.Compression; - -var arguments = Environment.GetCommandLineArgs(); - -new Updater().Update(arguments); - -class Updater -{ - public void Update(string[] arguments) - { - var dict = ParseArguments(arguments); - if (!dict.ContainsKey("--package") || !dict.ContainsKey("--path") || !dict.ContainsKey("--autostart")) return; - var isAutostart = dict["--autostart"] == "true"; - if (!isAutostart) - { - ChangeService("stop"); - // DeleteService(); - } - - RenameCurrentExecutable(); - UnzipPackage(dict["--package"], dict["--path"]); - LaunchApp(isAutostart, dict["--path"]); - Environment.Exit(0); - } - - private void ChangeService(string action) - { - var processStartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - RedirectStandardOutput = true, - UseShellExecute = false, - FileName = "cmd.exe", - Arguments = $"/c sc {action} svcleanmeter" - }; - var process = new Process(); - process.StartInfo = processStartInfo; - process.Start(); - } - - private void RenameCurrentExecutable() - { - var currentPath = Environment.ProcessPath!; - var newPath = Path.ChangeExtension(currentPath, ".bak"); - File.Move(currentPath, newPath, true); - } - - private void UnzipPackage(string package, string destination) - { - ZipFile.ExtractToDirectory(package, $@"{destination}\..\", true); - } - - private void LaunchApp(bool isAutostart, string path) - { - if (isAutostart) - { - ChangeService("start"); - } - - var processStartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - RedirectStandardOutput = true, - UseShellExecute = false, - FileName = "cmd.exe", - Arguments = $"/c {path}\\cleanmeter.exe" - }; - var process = new Process(); - process.StartInfo = processStartInfo; - process.Start(); - } - - private Dictionary ParseArguments(string[] args) - { - var arguments = new Dictionary(); - - foreach (var argument in args) - { - var splitted = argument.Split('='); - - if (splitted.Length == 2) - { - arguments[splitted[0]] = splitted[1]; - } - } - - return arguments; - } -} \ No newline at end of file diff --git a/HardwareMonitor/Updater/Updater.csproj b/HardwareMonitor/Updater/Updater.csproj deleted file mode 100644 index 2f4fc77..0000000 --- a/HardwareMonitor/Updater/Updater.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - diff --git a/app/.idea/.gitignore b/app/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/app/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/app/.idea/.name b/app/.idea/.name new file mode 100644 index 0000000..ec7ebd6 --- /dev/null +++ b/app/.idea/.name @@ -0,0 +1 @@ +CleanMeter \ No newline at end of file diff --git a/app/.idea/artifacts/native_jvm.xml b/app/.idea/artifacts/native_jvm.xml new file mode 100644 index 0000000..add699c --- /dev/null +++ b/app/.idea/artifacts/native_jvm.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/core/native/build/libs + + + + + \ No newline at end of file diff --git a/app/.idea/codeStyles/Project.xml b/app/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..1bec35e --- /dev/null +++ b/app/.idea/codeStyles/Project.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.idea/codeStyles/codeStyleConfig.xml b/app/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/app/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/.idea/compiler.xml b/app/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/app/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.idea/gradle.xml b/app/.idea/gradle.xml new file mode 100644 index 0000000..543c034 --- /dev/null +++ b/app/.idea/gradle.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/app/.idea/kotlinc.xml b/app/.idea/kotlinc.xml new file mode 100644 index 0000000..d4b7acc --- /dev/null +++ b/app/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/.idea/misc.xml b/app/.idea/misc.xml new file mode 100644 index 0000000..b1f50a1 --- /dev/null +++ b/app/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/.idea/vcs.xml b/app/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/app/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/app/LICENSE similarity index 100% rename from LICENSE rename to app/LICENSE diff --git a/README.md b/app/README.md similarity index 100% rename from README.md rename to app/README.md diff --git a/app/bin/win-x64/registry-delete.bat b/app/bin/win-x64/registry-delete.bat new file mode 100644 index 0000000..e125ed2 --- /dev/null +++ b/app/bin/win-x64/registry-delete.bat @@ -0,0 +1,59 @@ +@echo off +:: ------------------------------- +:: Auto-elevate to Administrator +:: ------------------------------- +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo Requesting administrative privileges... + powershell -Command "Start-Process '%~f0' -ArgumentList '%*' -Verb RunAs" + exit /b +) + +:: ------------------------------- +:: Registry Delete +:: ------------------------------- +set REGISTRY_KEY=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run +set ENTRY_NAME=%~1 + +echo ======================================== +echo CleanMeter Registry Deleter +echo ======================================== +echo. + +echo DEBUG: Entry Name = "%ENTRY_NAME%" +echo. + +:: Check if parameter is provided +if "%ENTRY_NAME%"=="" ( + echo ERROR: Missing entry name parameter. + echo Usage: %~nx0 "entry_name" + echo Example: %~nx0 "MyApp" + exit /b 1 +) + +echo Deleting registry entry... +echo Key: %REGISTRY_KEY% +echo Name: %ENTRY_NAME% +echo. + +:: Check if entry exists +reg query "%REGISTRY_KEY%" /v "%ENTRY_NAME%" >nul 2>&1 +if %errorLevel% neq 0 ( + echo WARNING: Registry entry "%ENTRY_NAME%" does not exist. + echo Operation completed. + exit /b 0 +) + +echo DEBUG: Executing command: reg delete "%REGISTRY_KEY%" /v "%ENTRY_NAME%" /f +reg delete "%REGISTRY_KEY%" /v "%ENTRY_NAME%" /f +set DELETE_RESULT=%errorLevel% +echo DEBUG: Command result: %DELETE_RESULT% + +if %DELETE_RESULT% neq 0 ( + echo ERROR: Failed to delete registry entry (Error Code: %DELETE_RESULT%) + exit /b %DELETE_RESULT% +) else ( + echo SUCCESS: Registry entry deleted successfully! +) + +echo Operation completed. diff --git a/app/bin/win-x64/registry-write.bat b/app/bin/win-x64/registry-write.bat new file mode 100644 index 0000000..3819d87 --- /dev/null +++ b/app/bin/win-x64/registry-write.bat @@ -0,0 +1,61 @@ +@echo off +:: ------------------------------- +:: Auto-elevate to Administrator +:: ------------------------------- +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo Requesting administrative privileges... + powershell -Command "Start-Process '%~f0' -ArgumentList '%*' -Verb RunAs" + exit /b +) + +:: ------------------------------- +:: Registry Write +:: ------------------------------- +set REGISTRY_KEY=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run +set ENTRY_NAME=%~1 +set ENTRY_VALUE=%~2 + +echo ======================================== +echo CleanMeter Registry Writer +echo ======================================== +echo. + +echo DEBUG: Entry Name = "%ENTRY_NAME%" +echo DEBUG: Entry Value = "%ENTRY_VALUE%" +echo. + +:: Check if parameters are provided +if "%ENTRY_NAME%"=="" ( + echo ERROR: Missing entry name parameter. + echo Usage: %~nx0 "entry_name" "entry_value" + echo Example: %~nx0 "MyApp" "C:\Path\To\MyApp.exe" + exit /b 1 +) + +if "%ENTRY_VALUE%"=="" ( + echo ERROR: Missing entry value parameter. + echo Usage: %~nx0 "entry_name" "entry_value" + echo Example: %~nx0 "MyApp" "C:\Path\To\MyApp.exe" + exit /b 1 +) + +echo Writing registry entry... +echo Key: %REGISTRY_KEY% +echo Name: %ENTRY_NAME% +echo Value: %ENTRY_VALUE% +echo. + +echo DEBUG: Executing command: reg add "%REGISTRY_KEY%" /v "%ENTRY_NAME%" /t REG_SZ /d "%ENTRY_VALUE%" /f +reg add "%REGISTRY_KEY%" /v "%ENTRY_NAME%" /t REG_SZ /d "%ENTRY_VALUE%" /f +set WRITE_RESULT=%errorLevel% +echo DEBUG: Command result: %WRITE_RESULT% + +if %WRITE_RESULT% neq 0 ( + echo ERROR: Failed to write registry entry (Error Code: %WRITE_RESULT%) + exit /b %WRITE_RESULT% +) else ( + echo SUCCESS: Registry entry written successfully! +) + +echo Operation completed. diff --git a/app/bin/win-x64/service-create.bat b/app/bin/win-x64/service-create.bat new file mode 100644 index 0000000..76f0ecc --- /dev/null +++ b/app/bin/win-x64/service-create.bat @@ -0,0 +1,83 @@ +@echo off +:: ------------------------------- +:: Auto-elevate to Administrator +:: ------------------------------- +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo Requesting administrative privileges... + powershell -Command "Start-Process '%~f0' -Verb RunAs" + exit /b +) + +:: ------------------------------- +:: Service config +:: ------------------------------- +set SERVICE_NAME=CleanMeterHardwareMonitor +set SERVICE_DESCRIPTION=Hardware monitoring service for CleanMeter application +set DISPLAY_NAME=CleanMeter Hardware Monitor +set SCRIPT_DIR=%~dp0 +set EXE_PATH=%SCRIPT_DIR%HardwareMonitor.exe + +echo ======================================== +echo CleanMeter Hardware Monitor - CREATE +echo ======================================== +echo. + +REM Check if executable exists +echo Checking for executable at: %EXE_PATH% +if not exist "%EXE_PATH%" ( + echo ERROR: HardwareMonitor.exe not found at: %EXE_PATH% + echo Please build and publish the project first using: + echo dotnet publish -c Release -r win-x64 --self-contained false + pause + exit /b 1 +) +echo Found executable: %EXE_PATH% + +REM Stop the service if it's running +echo Stopping service if running... +sc stop "%SERVICE_NAME%" >nul +echo Stop command result: %errorLevel% >nul + +REM Delete existing service if it exists +echo Removing existing service if it exists... +sc delete "%SERVICE_NAME%" >nul +echo Delete command result: %errorLevel% >nul + +REM Create the service +echo. +echo Creating service with command: +echo sc create "%SERVICE_NAME%" binPath= "\"%EXE_PATH%\"" DisplayName= "%DISPLAY_NAME%" start= auto +sc create "%SERVICE_NAME%" binPath= "\"%EXE_PATH%\"" DisplayName= "%DISPLAY_NAME%" start= auto + +REM Wait a moment for service registration to complete +echo. +echo Waiting for service registration to complete... +timeout /t 5 /nobreak >nul + +echo Service created successfully! + +REM Set service description +echo Setting service description... +sc description "%SERVICE_NAME%" "%SERVICE_DESCRIPTION%" +echo Description command result: %errorLevel% + +REM Configure service recovery options +echo Configuring service recovery options... +sc failure "%SERVICE_NAME%" reset= 86400 actions= restart/30000/restart/60000/restart/120000 +echo Recovery options result: %errorLevel% + +REM Start the service +echo Starting service... +sc start "%SERVICE_NAME%" +set START_RESULT=%errorLevel% +echo Start command result: %START_RESULT% + +if %START_RESULT% neq 0 ( + echo WARNING: Service created but failed to start (Error Code: %START_RESULT%) + echo Check the Event Log for details or start manually using: sc start "%SERVICE_NAME%" +) else ( + echo SUCCESS: CleanMeter Hardware Monitor Service installed and started successfully! +) + +echo Operation completed. diff --git a/app/bin/win-x64/service-delete.bat b/app/bin/win-x64/service-delete.bat new file mode 100644 index 0000000..f3a6ca7 --- /dev/null +++ b/app/bin/win-x64/service-delete.bat @@ -0,0 +1,60 @@ +@echo off +:: ------------------------------- +:: Auto-elevate to Administrator +:: ------------------------------- +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo Requesting administrative privileges... + powershell -Command "Start-Process '%~f0' -Verb RunAs" + exit /b +) + +:: ------------------------------- +:: Service config +:: ------------------------------- +set SERVICE_NAME=CleanMeterHardwareMonitor +set SERVICE_DESCRIPTION=Hardware monitoring service for CleanMeter application +set DISPLAY_NAME=CleanMeter Hardware Monitor + +echo ======================================== +echo CleanMeter Hardware Monitor - DELETE +echo ======================================== +echo. + +REM Check if service exists +echo Checking if service exists... +sc query "%SERVICE_NAME%" >nul 2>&1 +if %errorLevel% neq 0 ( + echo Service "%SERVICE_NAME%" does not exist or is already uninstalled. + exit /b 1 +) +echo Service found, proceeding with removal... + +REM Stop the service +echo Stopping service... +sc stop "%SERVICE_NAME%" +echo Stop command result: %errorLevel% + +REM Wait a moment for the service to stop +echo Waiting for service to stop... +timeout /t 5 /nobreak >nul + +REM Delete the service +echo Removing service... +sc delete "%SERVICE_NAME%" +set DELETE_RESULT=%errorLevel% +echo Delete command result: %DELETE_RESULT% + +if %DELETE_RESULT% neq 0 ( + echo ERROR: Failed to remove service (Error Code: %DELETE_RESULT%) +) else ( + echo SUCCESS: Service removed successfully! + echo. + echo Verifying removal: + sc query "%SERVICE_NAME%" >nul 2>&1 + if %errorLevel% neq 0 ( + echo Service successfully removed from system. + ) else ( + echo WARNING: Service may still be present in system. + ) +) diff --git a/app/bin/win-x64/service-stop.bat b/app/bin/win-x64/service-stop.bat new file mode 100644 index 0000000..54ea58a --- /dev/null +++ b/app/bin/win-x64/service-stop.bat @@ -0,0 +1,65 @@ +@echo off +:: ------------------------------- +:: Auto-elevate to Administrator +:: ------------------------------- +net session >nul 2>&1 +if %errorLevel% neq 0 ( + echo Requesting administrative privileges... + powershell -Command "Start-Process '%~f0' -Verb RunAs" + exit /b +) + +:: ------------------------------- +:: Service config +:: ------------------------------- +set SERVICE_NAME=CleanMeterHardwareMonitor +set SERVICE_DESCRIPTION=Hardware monitoring service for CleanMeter application +set DISPLAY_NAME=CleanMeter Hardware Monitor + +echo ======================================== +echo CleanMeter Hardware Monitor - STOP +echo ======================================== +echo. + +REM Check if service exists +echo Checking if service exists... +sc query "%SERVICE_NAME%" >nul 2>&1 +if %errorLevel% neq 0 ( + echo Service "%SERVICE_NAME%" does not exist. + echo Operation completed. + exit /b 1 +) + +REM Check if service is running +echo Checking service status... +sc query "%SERVICE_NAME%" | find "RUNNING" >nul +if %errorLevel% neq 0 ( + echo Service "%SERVICE_NAME%" is not running. + echo Operation completed. + exit /b 0 +) + +REM Stop the service +echo Stopping service... +sc stop "%SERVICE_NAME%" +set STOP_RESULT=%errorLevel% +echo Stop command result: %STOP_RESULT% + +if %STOP_RESULT% neq 0 ( + echo WARNING: Failed to stop service (Error Code: %STOP_RESULT%) + echo Check the Event Log for details or try stopping manually using: sc stop "%SERVICE_NAME%" +) else ( + echo Waiting for service to stop... + timeout /t 3 /nobreak >nul + + REM Verify service has stopped + sc query "%SERVICE_NAME%" | find "STOPPED" >nul + if %errorLevel% equ 0 ( + echo SUCCESS: CleanMeter Hardware Monitor Service stopped successfully! + ) else ( + echo WARNING: Service may still be stopping... + ) +) + +echo Operation completed. + diff --git a/build.gradle.kts b/app/build.gradle.kts similarity index 100% rename from build.gradle.kts rename to app/build.gradle.kts diff --git a/core/common/build.gradle.kts b/app/core/common/build.gradle.kts similarity index 79% rename from core/common/build.gradle.kts rename to app/core/common/build.gradle.kts index 96ecdf0..2cd7c42 100644 --- a/core/common/build.gradle.kts +++ b/app/core/common/build.gradle.kts @@ -3,6 +3,10 @@ plugins { kotlin("plugin.serialization") } +kotlin { + jvmToolchain(17) +} + repositories { mavenCentral() } @@ -10,11 +14,4 @@ repositories { dependencies { implementation(libs.kotlinx.serialization) implementation(libs.kotlinx.coroutines.core) -} - -tasks.test { - useJUnitPlatform() -} -kotlin { - jvmToolchain(20) } \ No newline at end of file diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/HardwareMonitorData.kt b/app/core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/HardwareMonitorData.kt similarity index 100% rename from core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/HardwareMonitorData.kt rename to app/core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/HardwareMonitorData.kt diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/PresentMonReading.kt b/app/core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/PresentMonReading.kt similarity index 100% rename from core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/PresentMonReading.kt rename to app/core/common/src/main/kotlin/app/cleanmeter/core/common/hardwaremonitor/PresentMonReading.kt diff --git a/app/core/common/src/main/kotlin/app/cleanmeter/core/common/process/SingleInstance.kt b/app/core/common/src/main/kotlin/app/cleanmeter/core/common/process/SingleInstance.kt new file mode 100644 index 0000000..7cfab51 --- /dev/null +++ b/app/core/common/src/main/kotlin/app/cleanmeter/core/common/process/SingleInstance.kt @@ -0,0 +1,42 @@ +package app.cleanmeter.core.common.process + +import app.cleanmeter.core.common.reporting.ApplicationParams +import app.cleanmeter.core.common.reporting.setDefaultUncaughtExceptionHandler +import kotlin.system.exitProcess + +fun singleInstance(args: Array, block: () -> Unit) { + if(isAppAlreadyRunning()) { + exitProcess(0) + } + + ApplicationParams.parse(args) + + setDefaultUncaughtExceptionHandler() + + block() +} + +private fun isAppAlreadyRunning(): Boolean { + val os = System.getProperty("os.name").lowercase() + val processName = "Clean Meter" // or jar name + + return try { + val process = when { + os.contains("win") -> { + ProcessBuilder("tasklist", "/FI", "IMAGENAME eq $processName.exe").start() + } + os.contains("mac") || os.contains("nix") || os.contains("nux") -> { + ProcessBuilder("pgrep", "-f", processName).start() + } + else -> return false + } + + val output = process.inputStream.bufferedReader().readText() + process.waitFor() + + // Parse output to check if process exists (beyond current instance) + output.lines().filterNot { it.isEmpty() }.size > 1 + } catch (e: Exception) { + false + } +} diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/ApplicationParams.kt b/app/core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/ApplicationParams.kt similarity index 100% rename from core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/ApplicationParams.kt rename to app/core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/ApplicationParams.kt diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/LoggerUtils.kt b/app/core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/LoggerUtils.kt similarity index 100% rename from core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/LoggerUtils.kt rename to app/core/common/src/main/kotlin/app/cleanmeter/core/common/reporting/LoggerUtils.kt diff --git a/core/design-system/build.gradle.kts b/app/core/design-system/build.gradle.kts similarity index 84% rename from core/design-system/build.gradle.kts rename to app/core/design-system/build.gradle.kts index 38c4671..9b28875 100644 --- a/core/design-system/build.gradle.kts +++ b/app/core/design-system/build.gradle.kts @@ -4,6 +4,10 @@ plugins { alias(libs.plugins.compose.compiler) } +kotlin { + jvmToolchain(17) +} + dependencies { implementation(compose.desktop.currentOs) } \ No newline at end of file diff --git a/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/ColorScheme.kt b/app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/ColorScheme.kt similarity index 100% rename from core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/ColorScheme.kt rename to app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/ColorScheme.kt diff --git a/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Primitives.kt b/app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Primitives.kt similarity index 100% rename from core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Primitives.kt rename to app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Primitives.kt diff --git a/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Theme.kt b/app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Theme.kt similarity index 100% rename from core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Theme.kt rename to app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Theme.kt diff --git a/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Typography.kt b/app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Typography.kt similarity index 100% rename from core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Typography.kt rename to app/core/design-system/src/main/kotlin/app/cleanmeter/core/designsystem/Typography.kt diff --git a/core/design-system/src/main/resources/font/inter_black.ttf b/app/core/design-system/src/main/resources/font/inter_black.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_black.ttf rename to app/core/design-system/src/main/resources/font/inter_black.ttf diff --git a/core/design-system/src/main/resources/font/inter_bold.ttf b/app/core/design-system/src/main/resources/font/inter_bold.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_bold.ttf rename to app/core/design-system/src/main/resources/font/inter_bold.ttf diff --git a/core/design-system/src/main/resources/font/inter_extrabold.ttf b/app/core/design-system/src/main/resources/font/inter_extrabold.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_extrabold.ttf rename to app/core/design-system/src/main/resources/font/inter_extrabold.ttf diff --git a/core/design-system/src/main/resources/font/inter_extralight.ttf b/app/core/design-system/src/main/resources/font/inter_extralight.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_extralight.ttf rename to app/core/design-system/src/main/resources/font/inter_extralight.ttf diff --git a/core/design-system/src/main/resources/font/inter_light.ttf b/app/core/design-system/src/main/resources/font/inter_light.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_light.ttf rename to app/core/design-system/src/main/resources/font/inter_light.ttf diff --git a/core/design-system/src/main/resources/font/inter_medium.ttf b/app/core/design-system/src/main/resources/font/inter_medium.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_medium.ttf rename to app/core/design-system/src/main/resources/font/inter_medium.ttf diff --git a/core/design-system/src/main/resources/font/inter_regular.ttf b/app/core/design-system/src/main/resources/font/inter_regular.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_regular.ttf rename to app/core/design-system/src/main/resources/font/inter_regular.ttf diff --git a/core/design-system/src/main/resources/font/inter_semibold.ttf b/app/core/design-system/src/main/resources/font/inter_semibold.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_semibold.ttf rename to app/core/design-system/src/main/resources/font/inter_semibold.ttf diff --git a/core/design-system/src/main/resources/font/inter_thin.ttf b/app/core/design-system/src/main/resources/font/inter_thin.ttf similarity index 100% rename from core/design-system/src/main/resources/font/inter_thin.ttf rename to app/core/design-system/src/main/resources/font/inter_thin.ttf diff --git a/app/core/native/build.gradle.kts b/app/core/native/build.gradle.kts new file mode 100644 index 0000000..6911869 --- /dev/null +++ b/app/core/native/build.gradle.kts @@ -0,0 +1,35 @@ +@file:OptIn(ExperimentalKotlinGradlePluginApi::class) + +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi + +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +kotlin { + jvmToolchain(17) + + jvm() + + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.serialization) + implementation("io.github.z4kn4fein:semver:2.0.0") + implementation(projects.core.common) + } + } + + jvmMain { + dependencies { + api(libs.jna) + } + } + } +} \ No newline at end of file diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/PlatformService.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/PlatformService.kt new file mode 100644 index 0000000..eb4c45b --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/PlatformService.kt @@ -0,0 +1,11 @@ +package app.cleanmeter.core.os + +import java.awt.Component + +/** + * Platform-specific service interface for OS-level operations + */ +expect object PlatformService { + fun changeWindowTransparency(w: Component, isTransparent: Boolean) + fun getForegroundProcessName(): String? +} diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt new file mode 100644 index 0000000..1050fd7 --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt @@ -0,0 +1,14 @@ +package app.cleanmeter.core.os + +const val OVERLAY_SETTINGS_PREFERENCE_KEY = "OVERLAY_SETTINGS_PREFERENCE_KEY" +const val PREFERENCE_START_MINIMIZED = "PREFERENCE_START_MINIMIZED" +const val PREFERENCE_PERMISSION_CONSENT = "PREFERENCE_PERMISSION_CONSENT" + +expect object PreferencesRepository { + fun getPreferenceString(key: String): String? + fun getPreferenceBoolean(key: String, defaultValue: Boolean = false): Boolean + fun getPreferenceBooleanNullable(key: String): Boolean? + fun setPreference(key: String, value: String) + fun setPreferenceBoolean(key: String, value: Boolean) + fun clear() +} diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/StartupManager.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/StartupManager.kt new file mode 100644 index 0000000..6ae866b --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/StartupManager.kt @@ -0,0 +1,7 @@ +package app.cleanmeter.core.os + +expect object StartupManager { + fun isAppRegisteredToStartWithSystem(): Boolean + fun registerAppToStartWithSystem() + fun removeAppFromStartWithSystem() +} diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt new file mode 100644 index 0000000..2a69b61 --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt @@ -0,0 +1,10 @@ +package app.cleanmeter.core.os.hardwaremonitor + +expect object HardwareMonitorProcessManager { + fun start() + fun stop() + fun isServiceCreated(): Boolean + fun createService() + fun stopService() + fun deleteService() +} diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt new file mode 100644 index 0000000..685204b --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt @@ -0,0 +1,5 @@ +package app.cleanmeter.core.os.resource + +expect object NativeResourceLoader { + fun load(path: String): String +} diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt new file mode 100644 index 0000000..db18057 --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt @@ -0,0 +1,14 @@ +package app.cleanmeter.core.os.util + +import java.io.InputStream +import java.nio.ByteBuffer +import java.nio.charset.Charset + +expect fun getByteBuffer(input: InputStream, length: Int): ByteBuffer +expect fun getByteBuffer(input: ByteArray, length: Int, offset: Int): ByteBuffer +expect fun getByteBuffer(pointer: Any, size: Int, offset: Int = 0): ByteBuffer + +expect val systemCharset: Charset + +expect fun ByteBuffer.readString(maxLength: Int, charset: Charset = systemCharset): String +expect fun trim(bytes: ByteArray): ByteArray diff --git a/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/util/env.kt b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/util/env.kt new file mode 100644 index 0000000..f82e79d --- /dev/null +++ b/app/core/native/src/commonMain/kotlin/app/cleanmeter/core/os/util/env.kt @@ -0,0 +1,3 @@ +package app.cleanmeter.core.os.util + +expect fun isDev(): Boolean diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/PlatformService.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/PlatformService.kt new file mode 100644 index 0000000..4ad4685 --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/PlatformService.kt @@ -0,0 +1,36 @@ +package app.cleanmeter.core.os + +import app.cleanmeter.core.os.win32.WindowsService +import java.awt.Component + +actual object PlatformService { + actual fun changeWindowTransparency(w: Component, isTransparent: Boolean) { + when (getCurrentPlatform()) { + Platform.WINDOWS -> WindowsService.changeWindowTransparency(w, isTransparent) + Platform.MACOS -> { + // TODO: Implement macOS window transparency + println("macOS window transparency not yet implemented") + } + Platform.LINUX -> { + // TODO: Implement Linux window transparency + println("Linux window transparency not yet implemented") + } + } + } + + actual fun getForegroundProcessName(): String? { + return when(getCurrentPlatform()) { + Platform.WINDOWS -> WindowsService.getForegroundProcessName() + Platform.MACOS -> { + // TODO: Implement macOS window transparency + println("macOS window transparency not yet implemented") + null + } + Platform.LINUX -> { + // TODO: Implement Linux window transparency + println("Linux window transparency not yet implemented") + null + } + } + } +} diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt new file mode 100644 index 0000000..f0b23ce --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt @@ -0,0 +1,77 @@ +package app.cleanmeter.core.os + +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.contentOrNull +import java.io.File +import java.nio.file.Path + +actual object PreferencesRepository { + + private val json = Json { + prettyPrint = true + ignoreUnknownKeys = true + } + + private val preferencesFile: File by lazy { + val currentDir = Path.of("").toAbsolutePath().toString() + File(currentDir, "preferences.json") + } + + private fun loadPreferences(): Map { + return if (preferencesFile.exists()) { + try { + val jsonContent = preferencesFile.readText() + json.decodeFromString>(jsonContent) + } catch (e: Exception) { + emptyMap() + } + } else { + emptyMap() + } + } + + private fun savePreferences(data: Map) { + try { + val jsonContent = json.encodeToString(data) + preferencesFile.writeText(jsonContent) + } catch (e: Exception) { + // Silently fail - matches original behavior + } + } + + actual fun getPreferenceString(key: String): String? { + val data = loadPreferences() + return (data[key] as? JsonPrimitive)?.contentOrNull + } + + actual fun getPreferenceBoolean(key: String, defaultValue: Boolean): Boolean { + val data = loadPreferences() + return (data[key] as? JsonPrimitive)?.booleanOrNull ?: defaultValue + } + + actual fun getPreferenceBooleanNullable(key: String): Boolean? { + val data = loadPreferences() + return (data[key] as? JsonPrimitive)?.booleanOrNull + } + + actual fun setPreference(key: String, value: String) { + val data = loadPreferences().toMutableMap() + data[key] = JsonPrimitive(value) + savePreferences(data) + } + + actual fun setPreferenceBoolean(key: String, value: Boolean) { + val data = loadPreferences().toMutableMap() + data[key] = JsonPrimitive(value) + savePreferences(data) + } + + actual fun clear() { + savePreferences(emptyMap()) + } +} \ No newline at end of file diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/StartupManager.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/StartupManager.kt new file mode 100644 index 0000000..5558613 --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/StartupManager.kt @@ -0,0 +1,50 @@ +package app.cleanmeter.core.os + +import app.cleanmeter.core.os.win32.WinRegistry + +actual object StartupManager { + + actual fun isAppRegisteredToStartWithSystem(): Boolean { + return when (getCurrentPlatform()) { + Platform.WINDOWS -> WinRegistry.isAppRegisteredToStartWithWindows() + Platform.MACOS -> { + // TODO: Implement macOS startup check (using launchctl) + println("macOS startup management not yet implemented") + false + } + Platform.LINUX -> { + // TODO: Implement Linux startup check (using systemd or autostart files) + println("Linux startup management not yet implemented") + false + } + } + } + + actual fun registerAppToStartWithSystem() { + when (getCurrentPlatform()) { + Platform.WINDOWS -> WinRegistry.registerAppToStartWithWindows() + Platform.MACOS -> { + // TODO: Implement macOS startup registration + println("macOS startup registration not yet implemented") + } + Platform.LINUX -> { + // TODO: Implement Linux startup registration + println("Linux startup registration not yet implemented") + } + } + } + + actual fun removeAppFromStartWithSystem() { + when (getCurrentPlatform()) { + Platform.WINDOWS -> WinRegistry.removeAppFromStartWithWindows() + Platform.MACOS -> { + // TODO: Implement macOS startup removal + println("macOS startup removal not yet implemented") + } + Platform.LINUX -> { + // TODO: Implement Linux startup removal + println("Linux startup removal not yet implemented") + } + } + } +} diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/getCurrentPlatform.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/getCurrentPlatform.kt new file mode 100644 index 0000000..b0ca6c7 --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/getCurrentPlatform.kt @@ -0,0 +1,16 @@ +package app.cleanmeter.core.os + +import java.util.Locale + +enum class Platform { + WINDOWS, MACOS, LINUX +} + +fun getCurrentPlatform(): Platform { + val osName = System.getProperty("os.name").lowercase(Locale.getDefault()) + return when { + "win" in osName -> Platform.WINDOWS + "mac" in osName -> Platform.MACOS + else -> Platform.LINUX + } +} \ No newline at end of file diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt new file mode 100644 index 0000000..07f0570 --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt @@ -0,0 +1,190 @@ +package app.cleanmeter.core.os.hardwaremonitor + +import app.cleanmeter.core.common.reporting.logException +import app.cleanmeter.core.os.Platform +import app.cleanmeter.core.os.getCurrentPlatform +import app.cleanmeter.core.os.util.isDev +import app.cleanmeter.core.os.win32.WinRegistry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.BufferedReader +import java.io.InputStreamReader +import java.nio.file.Path +import java.util.* + +actual object HardwareMonitorProcessManager { + private var process: Process? = null + + val appDir: String + get() { + val currentDir = Path.of("").toAbsolutePath().toString() + return if (isDev()) { + "$currentDir\\bin" + } else { + "$currentDir\\app\\resources" + } + } + + actual fun start() { + if (!isDev() || isServiceCreated()) return + + when (getCurrentPlatform()) { + Platform.WINDOWS -> { + val process = ProcessBuilder().apply { + command("cmd.exe", "/c", "$appDir\\win-x64\\HardwareMonitor.exe") + }.start() + + val scannerIn = Scanner(process.inputStream) + val scannerErr = Scanner(process.errorStream) + + CoroutineScope(Dispatchers.IO).launch { + while (scannerIn.hasNextLine()) { + System.out.println(scannerIn.nextLine()) + } + } + CoroutineScope(Dispatchers.IO).launch { + while (scannerErr.hasNextLine()) { + System.err.println(scannerErr.nextLine()) + } + } + + this.process = process + } + + Platform.MACOS -> { + // TODO: Implement macOS hardware monitor + println("macOS hardware monitor not yet implemented") + } + + Platform.LINUX -> { + // TODO: Implement Linux hardware monitor + println("Linux hardware monitor not yet implemented") + } + } + } + + actual fun stop() { + if (!isDev() || isServiceCreated()) return + + when (getCurrentPlatform()) { + Platform.WINDOWS -> { + process?.apply { + descendants().forEach(ProcessHandle::destroy) + destroy() + } + process = null + } + + Platform.MACOS -> { + // TODO: Implement macOS process stopping + println("macOS process stopping not yet implemented") + } + + Platform.LINUX -> { + // TODO: Implement Linux process stopping + println("Linux process stopping not yet implemented") + } + } + } + + actual fun createService() { + if (isDev()) return + + when (getCurrentPlatform()) { + Platform.WINDOWS -> { + val scCommand = "$appDir\\win-x64\\service-create.bat" + + val process = ProcessBuilder( "cmd", "/c", scCommand) + .redirectErrorStream(true) + .inheritIO() + .start() + + process.waitFor() + } + + Platform.MACOS -> { + // TODO: Implement macOS service creation + println("macOS service creation not yet implemented") + } + + Platform.LINUX -> { + // TODO: Implement Linux service creation + println("Linux service creation not yet implemented") + } + } + } + + actual fun stopService() { + when (getCurrentPlatform()) { + Platform.WINDOWS -> { + val scCommand = "$appDir\\win-x64\\service-stop.bat" + + val process = ProcessBuilder( "cmd", "/c", scCommand) + .redirectErrorStream(true) + .inheritIO() + .start() + + process.waitFor() + } + + Platform.MACOS -> { + // TODO: Implement macOS service stopping + println("macOS service stopping not yet implemented") + } + + Platform.LINUX -> { + // TODO: Implement Linux service stopping + println("Linux service stopping not yet implemented") + } + } + } + + actual fun deleteService() { + when (getCurrentPlatform()) { + Platform.WINDOWS -> { + val scCommand = "$appDir\\win-x64\\service-delete.bat" + + val process = ProcessBuilder( "cmd", "/c", scCommand) + .redirectErrorStream(true) + .inheritIO() + .start() + + process.waitFor() + } + + Platform.MACOS -> { + // TODO: Implement macOS service deletion + println("macOS service deletion not yet implemented") + } + + Platform.LINUX -> { + // TODO: Implement Linux service deletion + println("Linux service deletion not yet implemented") + } + } + } + + actual fun isServiceCreated(): Boolean { + return when (getCurrentPlatform()) { + Platform.WINDOWS -> { + val proc = ProcessBuilder("sc", "query", "\"CleanMeterHardwareMonitor\"") + .redirectErrorStream(true) + .start() + val input = BufferedReader(InputStreamReader(proc.inputStream)).lineSequence().toList() + + return proc.exitValue() == 0 + } + + Platform.MACOS -> { + println("macOS service deletion not yet implemented") + false + } + + Platform.LINUX -> { + println("Linux service deletion not yet implemented") + false + } + } + } +} diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt similarity index 97% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt index 0ee1022..2d80b00 100644 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorReader.kt @@ -20,6 +20,7 @@ enum class Command(val value: Short) { SelectPresentMonApp(2), PresentMonApps(3), SelectPollingRate(4), + SetForegroundApplication(5), ; companion object { @@ -59,6 +60,7 @@ object HardwareMonitorReader { is Packet.SelectPresentMonApp -> null is Packet.SelectPollingRate -> null + is Packet.SetForegroundApplication -> null } } diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/SocketClient.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/SocketClient.kt similarity index 56% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/SocketClient.kt rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/SocketClient.kt index 57e2321..ef7343e 100644 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/SocketClient.kt +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/hardwaremonitor/SocketClient.kt @@ -1,17 +1,21 @@ package app.cleanmeter.core.os.hardwaremonitor import app.cleanmeter.core.os.PREFERENCE_PERMISSION_CONSENT +import app.cleanmeter.core.os.Platform +import app.cleanmeter.core.os.PlatformService.getForegroundProcessName import app.cleanmeter.core.os.PreferencesRepository +import app.cleanmeter.core.os.getCurrentPlatform +import app.cleanmeter.core.os.hardwaremonitor.Packet.* import app.cleanmeter.core.os.util.getByteBuffer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import java.io.FileInputStream -import java.io.FileOutputStream import java.io.IOException import java.io.InputStream import java.io.RandomAccessFile @@ -21,6 +25,7 @@ import java.net.Socket import java.net.SocketException import java.nio.ByteBuffer import java.nio.ByteOrder +import java.nio.file.FileSystems private const val COMMAND_SIZE = 2 private const val LENGTH_SIZE = 4 @@ -41,6 +46,17 @@ sealed class Packet { return buffer } } + data class SetForegroundApplication(val name: String) : Packet() { + override fun toByteArray(): ByteArray { + val nameBytes = name.toByteArray() + val buffer = ByteBuffer.allocate(2 + 2 + nameBytes.count()).order(ByteOrder.LITTLE_ENDIAN).apply { + putShort(Command.SetForegroundApplication.value) + putShort(nameBytes.size.toShort()) + put(nameBytes) + }.array() + return buffer + } + } data class SelectPollingRate(val interval: Short) : Packet() { override fun toByteArray(): ByteArray { @@ -91,11 +107,12 @@ object SocketClient { val command = getCommand(inputStream) val size = getSize(inputStream) when (command) { - Command.Data -> packetChannel.trySend(Packet.Data(inputStream.readNBytes(size))) - Command.PresentMonApps -> packetChannel.trySend(Packet.PresentMonApps(inputStream.readNBytes(size))) + Command.Data -> packetChannel.trySend(Data(inputStream.readNBytes(size))) + Command.PresentMonApps -> packetChannel.trySend(PresentMonApps(inputStream.readNBytes(size))) Command.RefreshPresentMonApps -> Unit Command.SelectPresentMonApp -> Unit Command.SelectPollingRate -> Unit + Command.SetForegroundApplication -> Unit } } catch (e: SocketException) { println("Error while listening for packets") @@ -135,76 +152,107 @@ object SocketClient { object PipeClient { private val pipeName = "\\\\.\\pipe\\HardwareMonitor_31337" - private var pipeInputStream: FileInputStream? = null - private var pipeOutputStream: FileOutputStream? = null + private var pipeFile: RandomAccessFile? = null private var pollingRate = 500L private val packetChannel = Channel(Channel.CONFLATED) val packetFlow: Flow = packetChannel.receiveAsFlow() + val currentForegroundApplication: Flow = flow { + var currentForegroundApplication: String? = null + while(true) { + when (getCurrentPlatform()) { + Platform.WINDOWS -> { + val foregroundProcessName = getForegroundProcessName()?.split( + FileSystems.getDefault().separator + )?.last() ?: continue + + if (foregroundProcessName != currentForegroundApplication) { + currentForegroundApplication = foregroundProcessName + println("Foreground process: $foregroundProcessName") + emit(foregroundProcessName) + sendPacket(SetForegroundApplication(foregroundProcessName)) + } + } + + Platform.MACOS -> println("TODO macos") + Platform.LINUX -> println("TODO linux") + } + delay(2000L) + } + }.flowOn(Dispatchers.IO) + init { if (PreferencesRepository.getPreferenceBoolean(PREFERENCE_PERMISSION_CONSENT, false)) { connect() } } - private fun connect() = CoroutineScope(Dispatchers.IO).launch { - while (true) { - if (!isConnected()) { - try { - println("Trying to connect to pipe: $pipeName") - close() + private fun connect() { + connectToNamedPipe() + } - // Open pipe as stream - this should work correctly - pipeInputStream = FileInputStream(pipeName) - // Don't open output stream until we need to send - println("Connected to named pipe for reading") - } catch (ex: Exception) { - println("Couldn't connect to pipe: ${ex.message}") - ex.printStackTrace() - close() - delay(pollingRate) - continue + private fun connectToNamedPipe() { + CoroutineScope(Dispatchers.IO).launch { + while (true) { + if (!isConnected()) { + try { + println("Trying to connect to pipe: $pipeName") + close() + + // Open pipe as stream - this should work correctly + pipeFile = RandomAccessFile(pipeName, "rw") + // Don't open output stream until we need to send + println("Connected to named pipe for reading") + } catch (ex: Exception) { + println("Couldn't connect to pipe: ${ex.message}") + ex.printStackTrace() + close() + delay(pollingRate) + continue + } } - } - - pipeInputStream?.let { inputStream -> - try { - while (isConnected()) { - - // Read command (2 bytes) - val commandBytes = readExactly(inputStream, COMMAND_SIZE) - val command = Command.fromValue(ByteBuffer.wrap(commandBytes).order(java.nio.ByteOrder.LITTLE_ENDIAN).short) - // Read size (4 bytes) - val sizeBytes = readExactly(inputStream, LENGTH_SIZE) - val size = ByteBuffer.wrap(sizeBytes).order(java.nio.ByteOrder.LITTLE_ENDIAN).int - - if (size < 0) { // Sanity check - break - } - - // Read payload - val payload = readExactly(inputStream, size) - when (command) { - Command.Data -> packetChannel.trySend(Packet.Data(payload)) - Command.PresentMonApps -> packetChannel.trySend(Packet.PresentMonApps(payload)) - Command.RefreshPresentMonApps -> Unit - Command.SelectPresentMonApp -> Unit - Command.SelectPollingRate -> Unit + pipeFile?.let { raf -> + try { + while (isConnected()) { + + val commandBytes = ByteArray(COMMAND_SIZE) + raf.readFully(commandBytes) + val command = Command.fromValue( + ByteBuffer.wrap(commandBytes) + .order(ByteOrder.LITTLE_ENDIAN) + .short + ) + + val sizeBytes = ByteArray(LENGTH_SIZE) + raf.readFully(sizeBytes) + val size = ByteBuffer.wrap(sizeBytes).order(ByteOrder.LITTLE_ENDIAN).int + + val payload = ByteArray(size) + raf.readFully(payload) + + when (command) { + Command.Data -> packetChannel.trySend(Data(payload)) + Command.PresentMonApps -> packetChannel.trySend(PresentMonApps(payload)) + Command.RefreshPresentMonApps -> Unit + Command.SelectPresentMonApp -> Unit + Command.SelectPollingRate -> Unit + Command.SetForegroundApplication -> Unit + } } + } catch (e: Exception) { + println("Error while listening for packets: ${e.message}") + e.printStackTrace() + close() } - } catch (e: Exception) { - println("Error while listening for packets: ${e.message}") - e.printStackTrace() - close() } } } } private fun isConnected(): Boolean { - return pipeInputStream != null + return pipeFile != null } // Helper function to read exactly n bytes from InputStream @@ -230,37 +278,25 @@ object PipeClient { fun sendPacket(packet: Packet) { // Open output stream only when needed - if (pipeOutputStream == null && pipeInputStream != null) { - try { - pipeOutputStream = FileOutputStream(pipeName, true) // append mode - } catch (e: Exception) { - println("Error opening output stream: ${e.message}") - return - } - } - - pipeOutputStream?.let { stream -> + pipeFile?.let { raf -> try { val data = packet.toByteArray() - stream.write(data) - stream.flush() + raf.write(data) + raf.fd.sync() } catch (e: Exception) { println("Error sending packet: ${e.message}") - pipeOutputStream?.close() - pipeOutputStream = null + close() } } } fun close() { try { - pipeInputStream?.close() - pipeOutputStream?.close() + pipeFile?.close() } catch (e: Exception) { println("Error closing pipe: ${e.message}") } finally { - pipeInputStream = null - pipeOutputStream = null + pipeFile = null } } } \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt similarity index 71% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt index 8d88542..980987c 100644 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/resource/NativeResourceLoader.kt @@ -1,7 +1,7 @@ package app.cleanmeter.core.os.resource -object NativeResourceLoader { - fun load(path: String): String { +actual object NativeResourceLoader { + actual fun load(path: String): String { return NativeResourceLoader::class.java.getResourceAsStream(path) ?.bufferedReader() .use { it?.readText().orEmpty() } diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt similarity index 72% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt index b2e507b..b7240b6 100644 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/util/MemoryUtils.kt @@ -7,17 +7,18 @@ import java.nio.ByteOrder import java.nio.charset.Charset import java.util.* -internal fun getByteBuffer(input: InputStream, length: Int): ByteBuffer { +actual fun getByteBuffer(input: InputStream, length: Int): ByteBuffer { if (length <= 0) return ByteBuffer.allocate(0).order(ByteOrder.LITTLE_ENDIAN) return ByteBuffer.wrap(input.readNBytes(length)).order(ByteOrder.LITTLE_ENDIAN) } -internal fun getByteBuffer(input: ByteArray, length: Int, offset: Int): ByteBuffer { +actual fun getByteBuffer(input: ByteArray, length: Int, offset: Int): ByteBuffer { if (length <= 0) return ByteBuffer.allocate(0) return ByteBuffer.wrap(input).slice(offset, length).order(ByteOrder.LITTLE_ENDIAN) } -internal fun getByteBuffer(pointer: Pointer, size: Int, offset: Int = 0): ByteBuffer { +actual fun getByteBuffer(pointer: Any, size: Int, offset: Int): ByteBuffer { + require(pointer is com.sun.jna.Pointer) { "Expected JNA Pointer but got ${pointer::class}" } val buffer = ByteBuffer.allocateDirect(size) buffer.put(pointer.getByteArray(0, size)) buffer.order(ByteOrder.LITTLE_ENDIAN) @@ -27,7 +28,7 @@ internal fun getByteBuffer(pointer: Pointer, size: Int, offset: Int = 0): ByteBu return buffer } -internal val systemCharset: Charset by lazy { +actual val systemCharset: Charset by lazy { val osName = System.getProperty("os.name").lowercase(Locale.getDefault()) when { "win" in osName -> Charset.forName(System.getProperty("sun.jnu.encoding")) @@ -36,14 +37,14 @@ internal val systemCharset: Charset by lazy { } } -internal fun ByteBuffer.readString(maxLength: Int, charset: Charset = systemCharset): String { +actual fun ByteBuffer.readString(maxLength: Int, charset: Charset): String { val array = ByteArray(maxLength) get(array, 0, maxLength) return String(trim(array), charset) } -internal fun trim(bytes: ByteArray): ByteArray { +actual fun trim(bytes: ByteArray): ByteArray { var i = bytes.size - 1 while (i >= 0 && bytes[i].toInt() == 0) { --i diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/util/env.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/util/env.kt new file mode 100644 index 0000000..a048248 --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/util/env.kt @@ -0,0 +1,3 @@ +package app.cleanmeter.core.os.util + +actual fun isDev(): Boolean = System.getenv("env") == "dev" \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/Kernel32Impl.java b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/Kernel32Impl.java similarity index 100% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/win32/Kernel32Impl.java rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/Kernel32Impl.java diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/Shell32Impl.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/Shell32Impl.kt similarity index 100% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/win32/Shell32Impl.kt rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/Shell32Impl.kt diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/WinRegistry.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/WinRegistry.kt similarity index 60% rename from core/native/src/main/kotlin/app/cleanmeter/core/os/win32/WinRegistry.kt rename to app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/WinRegistry.kt index f60653c..5fcfb89 100644 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/WinRegistry.kt +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/WinRegistry.kt @@ -1,6 +1,6 @@ package app.cleanmeter.core.os.win32 -import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorProcessManager +import app.cleanmeter.core.os.util.isDev import java.io.BufferedReader import java.io.InputStreamReader import java.nio.file.Path @@ -9,6 +9,16 @@ object WinRegistry { const val STARTUP_ITEMS_LOCATION = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" const val REGISTRY_APP_NAME = "cleanmeter" + val appDir: String + get() { + val currentDir = Path.of("").toAbsolutePath().toString() + return if (isDev()) { + "$currentDir\\bin" + } else { + "$currentDir\\app\\resources" + } + } + fun read(location: String, key: String): List { val proc = ProcessBuilder("reg", "query", location, "/v", key) .redirectErrorStream(true) @@ -18,8 +28,8 @@ object WinRegistry { return input.lineSequence().toList() } - fun write(location: String, key: String, value: String, type: String = "REG_SZ") { - val proc = ProcessBuilder("reg", "add", location, "/v", key, "/t", type, "/d", value) + fun write(key: String, value: String) { + val proc = ProcessBuilder("cmd", "/c", "$appDir\\win-x64\\registry-write.bat", key, value) .redirectErrorStream(true) .start() @@ -28,8 +38,8 @@ object WinRegistry { println(input.lineSequence().toList()) } - fun delete(location: String, key: String) { - val proc = ProcessBuilder("reg", "delete", location, "/v", key, "/f") + fun delete(key: String) { + val proc = ProcessBuilder("cmd", "/c", "$appDir\\win-x64\\registry-delete.bat", key) .redirectErrorStream(true) .start() @@ -45,17 +55,10 @@ object WinRegistry { } fun registerAppToStartWithWindows() { - if (WindowsService.isProcessElevated()) { - write(STARTUP_ITEMS_LOCATION, REGISTRY_APP_NAME, "\\\"${Path.of("").toAbsolutePath()}\\$REGISTRY_APP_NAME.exe\\\" --autostart") - HardwareMonitorProcessManager.createService() - } + write(REGISTRY_APP_NAME, "${Path.of("").toAbsolutePath()}\\$REGISTRY_APP_NAME.exe") } fun removeAppFromStartWithWindows() { - if (WindowsService.isProcessElevated()) { - delete(STARTUP_ITEMS_LOCATION, REGISTRY_APP_NAME) - HardwareMonitorProcessManager.stopService() - HardwareMonitorProcessManager.deleteService() - } + delete(REGISTRY_APP_NAME) } } diff --git a/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/WindowsService.kt b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/WindowsService.kt new file mode 100644 index 0000000..764b446 --- /dev/null +++ b/app/core/native/src/jvmMain/kotlin/app/cleanmeter/core/os/win32/WindowsService.kt @@ -0,0 +1,65 @@ +package app.cleanmeter.core.os.win32 + +import com.sun.jna.Native +import com.sun.jna.platform.win32.Kernel32 +import com.sun.jna.platform.win32.User32 +import com.sun.jna.platform.win32.WinDef.HWND +import com.sun.jna.platform.win32.WinNT +import com.sun.jna.platform.win32.WinUser +import com.sun.jna.ptr.IntByReference +import java.awt.Component + +internal object WindowsService { + + fun changeWindowTransparency(w: Component, isTransparent: Boolean) { + val hwnd = HWND().apply { pointer = Native.getComponentPointer(w) } + val wl = if (isTransparent) { + User32.INSTANCE.GetWindowLong( + hwnd, + WinUser.GWL_EXSTYLE + ) or WinUser.WS_EX_LAYERED or WinUser.WS_EX_TRANSPARENT + } else { + User32.INSTANCE.GetWindowLong( + hwnd, + WinUser.GWL_EXSTYLE + ) or WinUser.WS_EX_LAYERED and WinUser.WS_EX_TRANSPARENT.inv() + } + User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, wl) + } + + fun getForegroundProcessName(): String? { + return try { + val hwnd = User32.INSTANCE.GetForegroundWindow() ?: return null + + val pid = IntByReference() + val threadId = User32.INSTANCE.GetWindowThreadProcessId(hwnd, pid) + if (threadId == 0) { + return null + } + + val hProcess: WinNT.HANDLE = Kernel32.INSTANCE.OpenProcess( + Kernel32.PROCESS_QUERY_INFORMATION or Kernel32.PROCESS_VM_READ, + false, + pid.value + ) ?: return null + + val buffer = CharArray(4096) + val bufferSize = IntByReference(buffer.size) + val success = Kernel32.INSTANCE.QueryFullProcessImageName(hProcess, 0, buffer, bufferSize) + + // Clean up: close the opened process + Kernel32.INSTANCE.CloseHandle(hProcess) + + if (success) { + val processName = String(buffer, 0, bufferSize.value) + processName + } else { + null + } + } catch (e: Exception) { + println("Exception in JNA getForegroundProcessName: ${e.message}") + e.printStackTrace() + null + } + } +} diff --git a/core/updater/build.gradle.kts b/app/core/updater/build.gradle.kts similarity index 90% rename from core/updater/build.gradle.kts rename to app/core/updater/build.gradle.kts index dd83107..354dd60 100644 --- a/core/updater/build.gradle.kts +++ b/app/core/updater/build.gradle.kts @@ -3,6 +3,10 @@ plugins { kotlin("plugin.serialization") } +kotlin { + jvmToolchain(17) +} + dependencies { implementation(libs.ktor.client.core) implementation(libs.ktor.client.okhttp) diff --git a/core/updater/src/main/kotlin/app/cleanmeter/updater/AutoUpdater.kt b/app/core/updater/src/main/kotlin/app/cleanmeter/updater/AutoUpdater.kt similarity index 100% rename from core/updater/src/main/kotlin/app/cleanmeter/updater/AutoUpdater.kt rename to app/core/updater/src/main/kotlin/app/cleanmeter/updater/AutoUpdater.kt diff --git a/gradle.properties b/app/gradle.properties similarity index 100% rename from gradle.properties rename to app/gradle.properties diff --git a/gradle/libs.versions.toml b/app/gradle/libs.versions.toml similarity index 99% rename from gradle/libs.versions.toml rename to app/gradle/libs.versions.toml index 356e13e..ff7097c 100644 --- a/gradle/libs.versions.toml +++ b/app/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.0.20" ktor = "2.3.12" jackson = "2.11.2" -compose = "1.7.1" +compose = "1.8.2" [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/app/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to app/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/app/gradle/wrapper/gradle-wrapper.properties similarity index 92% rename from gradle/wrapper/gradle-wrapper.properties rename to app/gradle/wrapper/gradle-wrapper.properties index 0d18421..5c82cb0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/app/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/app/gradlew old mode 100755 new mode 100644 similarity index 100% rename from gradlew rename to app/gradlew diff --git a/gradlew.bat b/app/gradlew.bat similarity index 100% rename from gradlew.bat rename to app/gradlew.bat diff --git a/settings.gradle.kts b/app/settings.gradle.kts similarity index 100% rename from settings.gradle.kts rename to app/settings.gradle.kts diff --git a/target/desktop/build.gradle.kts b/app/target/desktop/build.gradle.kts similarity index 82% rename from target/desktop/build.gradle.kts rename to app/target/desktop/build.gradle.kts index 249ca85..6661658 100644 --- a/target/desktop/build.gradle.kts +++ b/app/target/desktop/build.gradle.kts @@ -1,22 +1,5 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat -val copyPresentMon = tasks.register("copyPresentMon") { - from("../../HardwareMonitor/HardwareMonitor/bin/Release/net8.0/win-x64/presentmon") - into(layout.buildDirectory.dir("compose/binaries/main/app/cleanmeter/app/resources")) -} - -val copyMonitorFiles = tasks.register("copyMonitorFiles") { -// finalizedBy(copyPresentMon) - from("../../HardwareMonitor/HardwareMonitor/bin/Release/net8.0/win-x64/native") - into(layout.buildDirectory.dir("compose/binaries/main/app/cleanmeter/app/resources")) -} - -val compileMonitor = tasks.register("compileMonitor") { - finalizedBy(copyMonitorFiles) - workingDir("../../HardwareMonitor/") - commandLine("dotnet", "publish", "-c", "Release", "-r", "win-x64", "-p:PublishAot=true") -} - plugins { kotlin("jvm") kotlin("plugin.serialization") @@ -24,6 +7,10 @@ plugins { alias(libs.plugins.compose.compiler) } +kotlin { + jvmToolchain(17) +} + dependencies { implementation(libs.jnativehook) implementation(libs.kotlinx.serialization) @@ -85,3 +72,14 @@ compose.desktop { } } } + +val copyMonitorFiles = tasks.register("copyMonitorFiles") { + from("../../bin/") + into(layout.buildDirectory.dir("compose/binaries/main/app/cleanmeter/app/resources")) +} + +val compileMonitor = tasks.register("compileMonitor") { + finalizedBy(copyMonitorFiles) + workingDir("../../../HardwareMonitor/") + commandLine("dotnet", "publish", "-c", "Release", "-r", "win-x64", "-p:PublishAot=false") +} diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ApplicationViewModelStoreOwner.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ApplicationViewModelStoreOwner.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ApplicationViewModelStoreOwner.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ApplicationViewModelStoreOwner.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ComposeApp.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ComposeApp.kt similarity index 91% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ComposeApp.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ComposeApp.kt index 2a1fcaa..ae825c3 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ComposeApp.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ComposeApp.kt @@ -9,7 +9,7 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.window.application import androidx.lifecycle.viewmodel.compose.viewModel import app.cleanmeter.core.common.reporting.ApplicationParams -import app.cleanmeter.core.os.ProcessManager +import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorProcessManager import app.cleanmeter.target.desktop.ui.overlay.OverlayWindow import app.cleanmeter.target.desktop.ui.settings.SettingsWindow @@ -40,7 +40,7 @@ fun composeApp() = application { getOverlayPosition = { overlayPosition }, onApplicationExit = { if (!ApplicationParams.isAutostart) { - ProcessManager.stop() + HardwareMonitorProcessManager.stop() } } ) diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/DesktopMain.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/DesktopMain.kt similarity index 62% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/DesktopMain.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/DesktopMain.kt index a2808d2..839a20b 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/DesktopMain.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/DesktopMain.kt @@ -1,27 +1,24 @@ package app.cleanmeter.target.desktop import app.cleanmeter.core.common.process.singleInstance -import app.cleanmeter.core.common.reporting.ApplicationParams -import app.cleanmeter.core.os.ProcessManager -import app.cleanmeter.core.os.util.isDev -import app.cleanmeter.core.os.win32.WindowsService import app.cleanmeter.core.os.PREFERENCE_PERMISSION_CONSENT import app.cleanmeter.core.os.PreferencesRepository +import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorProcessManager +import app.cleanmeter.core.os.util.isDev fun main(vararg args: String) = singleInstance(args) { - if (PreferencesRepository.getPreferenceBoolean(PREFERENCE_PERMISSION_CONSENT, false)) { - WindowsService.tryElevateProcess(ApplicationParams.isAutostart) + if (PreferencesRepository.getPreferenceBoolean(PREFERENCE_PERMISSION_CONSENT, false)) { if (isDev()) { + HardwareMonitorProcessManager.start() Runtime.getRuntime().addShutdownHook(Thread { - ProcessManager.stop() + HardwareMonitorProcessManager.stop() }) } else { KeyboardManager.registerKeyboardHook() - } - - if (!ApplicationParams.isAutostart) { - ProcessManager.start() + if (!HardwareMonitorProcessManager.isServiceCreated()) { + HardwareMonitorProcessManager.createService() + } } } diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt similarity index 68% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt index 876e10f..b7dce9e 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/KeyboardHook.kt @@ -4,9 +4,13 @@ import com.github.kwhat.jnativehook.GlobalScreen import com.github.kwhat.jnativehook.keyboard.NativeKeyEvent import com.github.kwhat.jnativehook.keyboard.NativeKeyListener import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.Channel.Factory.CONFLATED import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted sealed class KeyboardEvent { data object ToggleOverlay : KeyboardEvent() @@ -15,10 +19,15 @@ sealed class KeyboardEvent { internal object KeyboardManager { - private val _channel = Channel(CONFLATED) + private val _channel = Channel(Channel.UNLIMITED) val events = _channel.receiveAsFlow() + .shareIn( + scope = CoroutineScope(Dispatchers.Default), + started = SharingStarted.Eagerly, + replay = 0 + ) - fun filter(event: KeyboardEvent) = events.filterIsInstance(event::class) + inline fun filter() = events.filterIsInstance() internal fun registerKeyboardHook() { try { @@ -28,14 +37,16 @@ internal object KeyboardManager { val isCtrl = nativeEvent.modifiers.and(NativeKeyEvent.CTRL_MASK) > 0 val isAlt = nativeEvent.modifiers.and(NativeKeyEvent.VC_ALT) > 0 - if (!isCtrl && !isAlt) return + if (!(isCtrl && isAlt)) return val event = when (nativeEvent.keyCode) { NativeKeyEvent.VC_F10 -> KeyboardEvent.ToggleOverlay NativeKeyEvent.VC_F11 -> KeyboardEvent.ToggleRecording else -> null } - event?.let { _channel.trySend(it) } + event?.let { + _channel.trySend(it) + } } }) } catch (e: Throwable) { diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/MainViewModel.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/MainViewModel.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/MainViewModel.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/MainViewModel.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/OverlaySettingsRepository.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/OverlaySettingsRepository.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/OverlaySettingsRepository.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/OverlaySettingsRepository.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/PreferenceRepositoryExtensions.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/PreferenceRepositoryExtensions.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/PreferenceRepositoryExtensions.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/data/PreferenceRepositoryExtensions.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/model/OverlaySettings.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/model/OverlaySettings.kt similarity index 98% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/model/OverlaySettings.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/model/OverlaySettings.kt index e25e49f..635c154 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/model/OverlaySettings.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/model/OverlaySettings.kt @@ -16,9 +16,11 @@ data class OverlaySettings( val positionY: Int = 0, val isPositionLocked: Boolean = true, val opacity: Float = 1f, + val scale: Float = 1f, val pollingRate: Long = 500, val isLoggingEnabled: Boolean = false, val sensors: Sensors = Sensors(), + val currentPresentMonApp: String = "", ) { @Serializable @Immutable diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/AppTheme.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/AppTheme.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/AppTheme.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/AppTheme.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/ColorTokens.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/ColorTokens.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/ColorTokens.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/ColorTokens.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Button.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Button.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Button.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Button.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CheckboxWithLabel.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CheckboxWithLabel.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CheckboxWithLabel.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CheckboxWithLabel.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CustomReadingProgress.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CustomReadingProgress.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CustomReadingProgress.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/CustomReadingProgress.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Disclaimer.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Disclaimer.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Disclaimer.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Disclaimer.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/HotKeySymbol.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/HotKeySymbol.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/HotKeySymbol.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/HotKeySymbol.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/KeyboardShortcutInfoLabel.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/KeyboardShortcutInfoLabel.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/KeyboardShortcutInfoLabel.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/KeyboardShortcutInfoLabel.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Pill.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Pill.kt similarity index 94% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Pill.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Pill.kt index 6b9c4b2..d45601b 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Pill.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Pill.kt @@ -1,5 +1,6 @@ package app.cleanmeter.target.desktop.ui.components +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -25,13 +26,14 @@ import app.cleanmeter.target.desktop.ui.overlay.conditional fun Pill( title: String, isHorizontal: Boolean, - minWidth: Dp = 80.dp, + minWidth: Dp = 20.dp, content: @Composable RowScope.() -> Unit ) { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier + .animateContentSize() .conditional( predicate = isHorizontal, ifTrue = { diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Progress.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Progress.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Progress.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Progress.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/ProgressUnit.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/ProgressUnit.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/ProgressUnit.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/ProgressUnit.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/RuntimeToast.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/RuntimeToast.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/RuntimeToast.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/RuntimeToast.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SensorBoundaryInput.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SensorBoundaryInput.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SensorBoundaryInput.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SensorBoundaryInput.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SettingsTab.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SettingsTab.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SettingsTab.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SettingsTab.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SliderElements.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SliderElements.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SliderElements.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/SliderElements.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/StyleCard.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/StyleCard.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/StyleCard.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/StyleCard.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Text.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Text.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Text.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Text.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Toggle.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Toggle.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Toggle.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/Toggle.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/TopBar.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/TopBar.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/TopBar.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/TopBar.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/UpdateToast.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/UpdateToast.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/UpdateToast.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/UpdateToast.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/DropdownMenu.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/DropdownMenu.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/DropdownMenu.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/DropdownMenu.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/SensorReadingDropdownMenu.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/SensorReadingDropdownMenu.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/SensorReadingDropdownMenu.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/dropdown/SensorReadingDropdownMenu.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CheckboxSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CheckboxSection.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CheckboxSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CheckboxSection.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CollapsibleSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CollapsibleSection.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CollapsibleSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/CollapsibleSection.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/DropdownSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/DropdownSection.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/DropdownSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/DropdownSection.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/Section.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/Section.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/Section.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/Section.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/SectionBody.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/SectionBody.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/SectionBody.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/SectionBody.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/ToggleSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/ToggleSection.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/ToggleSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/components/section/ToggleSection.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/Overlay.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/Overlay.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/Overlay.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/Overlay.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayUi.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayUi.kt similarity index 90% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayUi.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayUi.kt index 885c716..3662b57 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayUi.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayUi.kt @@ -1,5 +1,6 @@ package app.cleanmeter.target.desktop.ui.overlay +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -11,6 +12,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.IntrinsicMeasurable import androidx.compose.ui.layout.IntrinsicMeasureScope @@ -29,6 +31,10 @@ import app.cleanmeter.target.desktop.ui.overlay.sections.GpuSection import app.cleanmeter.target.desktop.ui.overlay.sections.NetSection import app.cleanmeter.target.desktop.ui.overlay.sections.RamSection +private fun Float.map(fromMin: Float, fromMax: Float, toMin: Float, toMax: Float): Float { + return (this - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin +} + inline fun Modifier.conditional( predicate: Boolean, ifTrue: Modifier.() -> Modifier, @@ -47,6 +53,7 @@ fun OverlayUi( if (overlaySettings.isHorizontal) { Row( modifier = Modifier + .scale(overlaySettings.scale.map(0f,1f,.5f, 1f)) .padding(16.dp) .fillMaxHeight() .background(Color.Black.copy(alpha = 0.36f), CircleShape) @@ -62,6 +69,7 @@ fun OverlayUi( } else { Column( modifier = Modifier + .scale(overlaySettings.scale.map(0f,1f,.5f, 1f)) .padding(16.dp) .background(Color.Black.copy(alpha = 0.36f), RoundedCornerShape(12.dp)) .padding(4.dp), @@ -80,25 +88,25 @@ fun OverlayUi( fun Content(data: HardwareMonitorData, overlaySettings: OverlaySettings) { if (overlaySettings.isHorizontal) { Row( - modifier = Modifier.fillMaxHeight(), + modifier = Modifier.animateContentSize().fillMaxHeight(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { FpsSection(overlaySettings, data) - GpuSection(overlaySettings, data) CpuSection(overlaySettings, data) + GpuSection(overlaySettings, data) RamSection(overlaySettings, data) NetSection(overlaySettings, data) } } else { Column( - modifier = Modifier, + modifier = Modifier.animateContentSize(), verticalArrangement = Arrangement.spacedBy(4.dp) ) { Layout( content = { FpsSection(overlaySettings, data) - GpuSection(overlaySettings, data) CpuSection(overlaySettings, data) + GpuSection(overlaySettings, data) RamSection(overlaySettings, data) NetSection(overlaySettings, data) }, diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayViewModel.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayWindow.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayWindow.kt similarity index 95% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayWindow.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayWindow.kt index 62d5a7a..84919d6 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayWindow.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/OverlayWindow.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.rememberWindowState import androidx.lifecycle.viewmodel.compose.viewModel -import app.cleanmeter.core.os.win32.WindowsService +import app.cleanmeter.core.os.PlatformService import app.cleanmeter.target.desktop.ApplicationViewModelStoreOwner import app.cleanmeter.target.desktop.KeyboardEvent import app.cleanmeter.target.desktop.KeyboardManager @@ -49,7 +49,8 @@ fun ApplicationScope.OverlayWindow( var isVisible by remember { mutableStateOf(true) } LaunchedEffect(Unit) { - KeyboardManager.filter(KeyboardEvent.ToggleOverlay).collectLatest { + KeyboardManager.filter().collectLatest { + println("toggle overlay") isVisible = !isVisible } } @@ -128,7 +129,7 @@ fun ApplicationScope.OverlayWindow( window.toFront() } - WindowsService.changeWindowTransparency(window, overlayState.overlaySettings!!.isPositionLocked) + PlatformService.changeWindowTransparency(window, overlayState.overlaySettings!!.isPositionLocked) WindowDraggableArea { Overlay( diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/CpuSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/CpuSection.kt similarity index 81% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/CpuSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/CpuSection.kt index 247de51..d363305 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/CpuSection.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/CpuSection.kt @@ -1,5 +1,10 @@ package app.cleanmeter.target.desktop.ui.overlay.sections +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -25,7 +30,11 @@ import java.util.* @Composable internal fun CpuSection(overlaySettings: OverlaySettings, data: HardwareMonitorData) { - if (overlaySettings.sensors.cpuTemp.isValid() || overlaySettings.sensors.cpuUsage.isValid()) { + AnimatedVisibility( + visible = overlaySettings.sensors.cpuTemp.isValid() || overlaySettings.sensors.cpuUsage.isValid(), + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut(), + ) { Pill( title = "CPU", isHorizontal = overlaySettings.isHorizontal, @@ -55,7 +64,10 @@ internal fun CpuSection(overlaySettings: OverlaySettings, data: HardwareMonitorD if (overlaySettings.sensors.cpuConsumption.isValid()) { val reading = data.getReading(overlaySettings.sensors.cpuConsumption.customReadingId) val value = (reading?.Value ?: 1f).coerceAtLeast(1f).toInt() - Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.widthIn(min = 35.dp).padding(bottom = 2.dp)) { + Row( + verticalAlignment = Alignment.Bottom, + modifier = Modifier.widthIn(min = 35.dp).padding(bottom = 2.dp) + ) { ProgressLabel("$value") ProgressUnit("W") } diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/FpsSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/FpsSection.kt similarity index 92% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/FpsSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/FpsSection.kt index fc539b6..692ea62 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/FpsSection.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/FpsSection.kt @@ -1,5 +1,10 @@ package app.cleanmeter.target.desktop.ui.overlay.sections +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -39,7 +44,11 @@ import java.util.* @Composable internal fun FpsSection(overlaySettings: OverlaySettings, data: HardwareMonitorData) { - if (overlaySettings.sensors.framerate.isEnabled || overlaySettings.sensors.frametime.isEnabled) { + AnimatedVisibility( + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut(), + visible = overlaySettings.sensors.framerate.isEnabled || overlaySettings.sensors.frametime.isEnabled, + ) { if (overlaySettings.isHorizontal) { Pill( title = "FPS", @@ -52,7 +61,6 @@ internal fun FpsSection(overlaySettings: OverlaySettings, data: HardwareMonitorD fontSize = 16.sp, lineHeight = 0.sp, fontWeight = FontWeight.Normal, - modifier = Modifier.width(50.dp) ) } @@ -64,7 +72,7 @@ internal fun FpsSection(overlaySettings: OverlaySettings, data: HardwareMonitorD fontSize = 12.sp, lineHeight = 0.sp, fontWeight = FontWeight.Normal, - modifier = Modifier.width(50.dp).padding(bottom = 2.dp) + modifier = Modifier.padding(bottom = 2.dp) ) } } diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/GpuSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/GpuSection.kt similarity index 76% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/GpuSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/GpuSection.kt index c45375a..16cef79 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/GpuSection.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/GpuSection.kt @@ -1,5 +1,10 @@ package app.cleanmeter.target.desktop.ui.overlay.sections +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn @@ -23,7 +28,11 @@ private fun OverlaySettings.Sensors.isAllValid(): Boolean { @Composable internal fun GpuSection(overlaySettings: OverlaySettings, data: HardwareMonitorData) { - if (overlaySettings.sensors.isAllValid()) { + AnimatedVisibility( + visible = overlaySettings.sensors.isAllValid(), + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut(), + ) { Pill( title = "GPU", isHorizontal = overlaySettings.isHorizontal, @@ -51,8 +60,13 @@ internal fun GpuSection(overlaySettings: OverlaySettings, data: HardwareMonitorD } if (overlaySettings.sensors.vramUsage.isValid() && overlaySettings.sensors.totalVramUsed.isValid()) { - val vramUsage = data.getReading(overlaySettings.sensors.vramUsage.customReadingId, "memory")?.Value?.coerceAtLeast(1f) ?: 1f - val totalVramUsed = data.getReading(overlaySettings.sensors.totalVramUsed.customReadingId)?.Value?.coerceAtLeast(1f) ?: 1f + val vramUsage = + data.getReading(overlaySettings.sensors.vramUsage.customReadingId, "memory")?.Value?.coerceAtLeast( + 1f + ) ?: 1f + val totalVramUsed = + data.getReading(overlaySettings.sensors.totalVramUsed.customReadingId)?.Value?.coerceAtLeast(1f) + ?: 1f Progress( value = vramUsage / 100f, @@ -66,7 +80,10 @@ internal fun GpuSection(overlaySettings: OverlaySettings, data: HardwareMonitorD if (overlaySettings.sensors.gpuConsumption.isValid()) { val reading = data.getReading(overlaySettings.sensors.gpuConsumption.customReadingId) val value = (reading?.Value ?: 1f).coerceAtLeast(1f).toInt() - Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.widthIn(min = 35.dp).padding(bottom = 2.dp)) { + Row( + verticalAlignment = Alignment.Bottom, + modifier = Modifier.widthIn(min = 35.dp).padding(bottom = 2.dp) + ) { ProgressLabel("$value") ProgressUnit("W") } diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/NetSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/NetSection.kt similarity index 91% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/NetSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/NetSection.kt index e2f998e..fc1e985 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/NetSection.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/NetSection.kt @@ -1,5 +1,10 @@ package app.cleanmeter.target.desktop.ui.overlay.sections +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -43,7 +48,11 @@ import app.cleanmeter.target.desktop.ui.overlay.conditional @Composable internal fun NetSection(overlaySettings: OverlaySettings, data: HardwareMonitorData) { - if (overlaySettings.sensors.upRate.isValid() || overlaySettings.sensors.downRate.isValid()) { + AnimatedVisibility( + visible = overlaySettings.sensors.upRate.isValid() || overlaySettings.sensors.downRate.isValid(), + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut(), + ) { if (overlaySettings.isHorizontal) { Pill( title = "NET", @@ -68,7 +77,8 @@ internal fun NetSection(overlaySettings: OverlaySettings, data: HardwareMonitorD painterResource("icons/arrow_down.svg"), "", tint = Purple, - modifier = Modifier.padding(end = 4.dp, bottom = 3.dp).rotate(180f).alpha(upRate.coerceAtMost(1f)) + modifier = Modifier.padding(end = 4.dp, bottom = 3.dp).rotate(180f) + .alpha(upRate.coerceAtMost(1f)) ) } } @@ -108,7 +118,8 @@ internal fun NetSection(overlaySettings: OverlaySettings, data: HardwareMonitorD painterResource("icons/arrow_down.svg"), "", tint = Cyan, - modifier = Modifier.padding(end = 4.dp, bottom = 3.dp).alpha(dlRate.coerceAtMost(1f)) + modifier = Modifier.padding(end = 4.dp, bottom = 3.dp) + .alpha(dlRate.coerceAtMost(1f)) ) } } @@ -120,7 +131,8 @@ internal fun NetSection(overlaySettings: OverlaySettings, data: HardwareMonitorD painterResource("icons/arrow_down.svg"), "", tint = Purple, - modifier = Modifier.padding(end = 4.dp, bottom = 3.dp).rotate(180f).alpha(upRate.coerceAtMost(1f)) + modifier = Modifier.padding(end = 4.dp, bottom = 3.dp).rotate(180f) + .alpha(upRate.coerceAtMost(1f)) ) } } @@ -174,7 +186,8 @@ private fun NetGraph(data: HardwareMonitorData, isHorizontal: Boolean, overlaySe largestDown.floatValue = downRatePoints.max() + .2f } - Box(modifier = Modifier + Box( + modifier = Modifier .conditional( predicate = isHorizontal, ifTrue = { width(100.dp) }, diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/RamSection.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/RamSection.kt similarity index 72% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/RamSection.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/RamSection.kt index a93e2b6..8581ed0 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/RamSection.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/RamSection.kt @@ -1,5 +1,10 @@ package app.cleanmeter.target.desktop.ui.overlay.sections +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.runtime.Composable import app.cleanmeter.core.common.hardwaremonitor.HardwareMonitorData import app.cleanmeter.core.common.hardwaremonitor.RamUsage @@ -11,7 +16,11 @@ import java.util.* @Composable internal fun RamSection(overlaySettings: OverlaySettings, data: HardwareMonitorData) { - if (overlaySettings.sensors.ramUsage.isEnabled) { + AnimatedVisibility( + visible = overlaySettings.sensors.ramUsage.isEnabled, + enter = scaleIn() + fadeIn(), + exit = scaleOut() + fadeOut(), + ) { Pill( title = "RAM", isHorizontal = overlaySettings.isHorizontal, diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/drawLine.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/drawLine.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/drawLine.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/overlay/sections/drawLine.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/CheckboxSectionOption.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/CheckboxSectionOption.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/CheckboxSectionOption.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/CheckboxSectionOption.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Footer.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Footer.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Footer.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Footer.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Settings.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Settings.kt similarity index 97% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Settings.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Settings.kt index 895e39e..6229e90 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Settings.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/Settings.kt @@ -71,7 +71,9 @@ fun WindowScope.Settings( Column( modifier = Modifier .fillMaxSize() - .background(LocalColorScheme.current.background.surface, RoundedCornerShape(12.dp)) + .background(LocalColorScheme.current.border.brandSubtle, RoundedCornerShape(12.dp)) + .padding(4.dp) + .background(LocalColorScheme.current.background.surface, RoundedCornerShape(8.dp)) ) { WindowDraggableArea { TopBar(onCloseRequest = onCloseRequest, onMinimizeRequest = onMinimizeRequest) @@ -102,12 +104,9 @@ fun WindowScope.Settings( ) } - if (updaterState !is UpdateState.NotAvailable && settingsState.isRuntimeAvailable) { + if (updaterState !is UpdateState.NotAvailable) { UpdateToast() } - if (!settingsState.isRuntimeAvailable) { - RuntimeToast() - } } } } @@ -244,6 +243,7 @@ private fun TabContent( }, onLayoutChange = { viewModel.onEvent(SettingsEvent.OverlayOrientationSelect(it)) }, onOpacityChange = { viewModel.onEvent(SettingsEvent.OverlayOpacityChange(it)) }, + onScaleChange = { viewModel.onEvent(SettingsEvent.OverlayScaleChange(it)) }, onGraphTypeChange = { viewModel.onEvent(SettingsEvent.OverlayGraphChange(it)) }, onOverlayCustomPositionEnable = { viewModel.onEvent(SettingsEvent.OverlayCustomPositionEnable(it)) }, onDisplaySelect = { diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt similarity index 95% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt index f82a996..ccd69bb 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsViewModel.kt @@ -9,7 +9,7 @@ import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorProcessManager import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorReader import app.cleanmeter.core.os.hardwaremonitor.Packet import app.cleanmeter.core.os.hardwaremonitor.PipeClient -import app.cleanmeter.core.os.win32.WindowsService +import app.cleanmeter.core.os.PlatformService import app.cleanmeter.target.desktop.KeyboardEvent import app.cleanmeter.target.desktop.KeyboardManager import app.cleanmeter.target.desktop.data.OverlaySettingsRepository @@ -42,7 +42,6 @@ data class SettingsState( val hardwareData: HardwareMonitorData? = null, val isRecording: Boolean = false, val adminConsent: Boolean = false, - val isRuntimeAvailable: Boolean = false, val logSink: String = "" ) @@ -56,6 +55,7 @@ sealed class SettingsEvent { data class OverlayCustomPositionEnable(val isEnabled: Boolean) : SettingsEvent() data class OverlayOrientationSelect(val isHorizontal: Boolean) : SettingsEvent() data class OverlayOpacityChange(val opacity: Float) : SettingsEvent() + data class OverlayScaleChange(val scale: Float) : SettingsEvent() data class OverlayGraphChange(val progressType: OverlaySettings.ProgressType) : SettingsEvent() data class DarkThemeToggle(val isEnabled: Boolean) : SettingsEvent() data class FpsApplicationSelect(val applicationName: String) : SettingsEvent() @@ -83,7 +83,6 @@ class SettingsViewModel : ViewModel() { observeRecordingHotkey() observeRecordingState() sendInitialPollingRate() - checkForNetCoreRuntime() checkIfLoggingIsEnabled() _state.update { @@ -121,13 +120,6 @@ class SettingsViewModel : ViewModel() { } } - private fun checkForNetCoreRuntime() { - CoroutineScope(Dispatchers.IO).launch { - val isRuntimeAvailable = HardwareMonitorProcessManager.checkRuntime() - _state.update { it.copy(isRuntimeAvailable = isRuntimeAvailable) } - } - } - private fun observeOverlaySettings() { CoroutineScope(Dispatchers.IO).launch { OverlaySettingsRepository @@ -151,7 +143,7 @@ class SettingsViewModel : ViewModel() { private fun observeRecordingHotkey() { CoroutineScope(Dispatchers.Default).launch { KeyboardManager - .filter(KeyboardEvent.ToggleRecording) + .filter() .collectLatest { println("Toggle recording ${_state.value.isRecording}") _state.update { it.copy(isRecording = !it.isRecording) } @@ -213,6 +205,7 @@ class SettingsViewModel : ViewModel() { is SettingsEvent.ConsentGiven -> onConsentGiven() is SettingsEvent.PollingRateSelect -> onPollingRateSelect(event.pollingRate, this) is SettingsEvent.ToggleLoggingEnabled -> onToggleLoggingEnabled(this) + is SettingsEvent.OverlayScaleChange -> onOverlayScaleChange(event.scale, this) } } @@ -247,7 +240,7 @@ class SettingsViewModel : ViewModel() { private fun onConsentGiven() { PreferencesRepository.setPreferenceBoolean(PREFERENCE_PERMISSION_CONSENT, true) _state.update { it.copy(adminConsent = true) } - WindowsService.elevateProcess() + HardwareMonitorProcessManager.createService() } private fun onBoundarySet( @@ -326,7 +319,11 @@ class SettingsViewModel : ViewModel() { } private fun onFpsApplicationSelect(applicationName: String, settingsState: SettingsState) { - PipeClient.sendPacket(Packet.SelectPresentMonApp(applicationName)) + with(settingsState) { + val newSettings = overlaySettings?.copy(currentPresentMonApp = applicationName) + PipeClient.sendPacket(Packet.SelectPresentMonApp(applicationName)) + OverlaySettingsRepository.setOverlaySettings(newSettings) + } } private fun onDarkModeToggle(enabled: Boolean, settingsState: SettingsState) { @@ -356,6 +353,16 @@ class SettingsViewModel : ViewModel() { } } + private fun onOverlayScaleChange(scale: Float, settingsState: SettingsState) { + with(settingsState) { + val newSettings = overlaySettings?.copy( + scale = scale, + ) + + OverlaySettingsRepository.setOverlaySettings(newSettings) + } + } + private fun onOverlayOrientationSelect(isHorizontal: Boolean, settingsState: SettingsState) { with(settingsState) { val newSettings = overlaySettings?.copy( @@ -530,6 +537,9 @@ class SettingsViewModel : ViewModel() { ), vramUsage = overlaySettings.sensors.vramUsage.copy( isEnabled = isEnabled + ), + gpuConsumption = overlaySettings.sensors.gpuConsumption.copy( + isEnabled = isEnabled ) ) ) @@ -541,6 +551,9 @@ class SettingsViewModel : ViewModel() { ), cpuUsage = overlaySettings.sensors.cpuUsage.copy( isEnabled = isEnabled + ), + cpuConsumption = overlaySettings.sensors.cpuConsumption.copy( + isEnabled = isEnabled ) ) ) diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsWindow.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsWindow.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsWindow.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/SettingsWindow.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/AppSettingsUi.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/AppSettingsUi.kt similarity index 75% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/AppSettingsUi.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/AppSettingsUi.kt index 92b91b8..023ff7f 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/AppSettingsUi.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/AppSettingsUi.kt @@ -3,31 +3,18 @@ package app.cleanmeter.target.desktop.ui.settings.tabs import ClearButton import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.TooltipArea -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import app.cleanmeter.core.designsystem.LocalColorScheme -import app.cleanmeter.core.designsystem.LocalTypography -import app.cleanmeter.core.os.win32.WinRegistry import app.cleanmeter.core.os.PREFERENCE_START_MINIMIZED import app.cleanmeter.core.os.PreferencesRepository +import app.cleanmeter.core.os.StartupManager import app.cleanmeter.target.desktop.model.OverlaySettings import app.cleanmeter.target.desktop.ui.components.CheckboxWithLabel import app.cleanmeter.target.desktop.ui.components.StyleCard @@ -121,37 +108,20 @@ fun AppSettingsUi( @OptIn(ExperimentalFoundationApi::class) @Composable private fun startWithWindowsCheckbox() { - var state by remember { mutableStateOf(WinRegistry.isAppRegisteredToStartWithWindows()) } + var state by remember { mutableStateOf(StartupManager.isAppRegisteredToStartWithSystem()) } - LaunchedEffect(Unit) { - if (state) { - WinRegistry.removeAppFromStartWithWindows() - } - } - - TooltipArea( - delayMillis = 0, - tooltip = { - Text( - text = "Temporarily disabled.", - style = LocalTypography.current.labelM, - color = LocalColorScheme.current.text.heading, - ) - }) { - CheckboxWithLabel( - label = "Start with Windows", - checked = state, - enabled = false, - onCheckedChange = { value -> - state = value - if (value) { - WinRegistry.registerAppToStartWithWindows() - } else { - WinRegistry.removeAppFromStartWithWindows() - } + CheckboxWithLabel( + label = "Start with Windows", + checked = state, + onCheckedChange = { value -> + state = value + if (value) { + StartupManager.registerAppToStartWithSystem() + } else { + StartupManager.removeAppFromStartWithSystem() } - ) - } + } + ) } @Composable diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/HelpSettingsUi.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/CpuStats.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/CpuStats.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/CpuStats.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/CpuStats.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/FpsStats.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/FpsStats.kt similarity index 83% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/FpsStats.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/FpsStats.kt index 0e973ff..1d2ccee 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/FpsStats.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/FpsStats.kt @@ -4,8 +4,11 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import app.cleanmeter.core.os.hardwaremonitor.PipeClient import app.cleanmeter.target.desktop.ui.components.CheckboxWithLabel import app.cleanmeter.target.desktop.ui.components.dropdown.DropdownMenu import app.cleanmeter.target.desktop.ui.components.section.CustomBodyCheckboxSection @@ -20,7 +23,9 @@ internal fun FpsStats( onOptionsToggle: (CheckboxSectionOption) -> Unit, onFpsApplicationSelect: (String) -> Unit, getPresentMonApps: () -> List, + currentPresentMonApp: String, ) { + val currentForegroundApplication by PipeClient.currentForegroundApplication.collectAsState(null) CustomBodyCheckboxSection( title = "FPS", options = availableOptions.filterOptions(SensorType.Framerate, SensorType.Frametime), @@ -39,9 +44,9 @@ internal fun FpsStats( if (presentMonApps.isNotEmpty()) { DropdownMenu( label = "Monitored app:", - disclaimer = "Apps are auto updated every 10 seconds.", + disclaimer = "Apps are auto updated every 10 seconds. $currentPresentMonApp -> $currentForegroundApplication", options = presentMonApps, - selectedIndex = 0, + selectedIndex = presentMonApps.indexOf(currentPresentMonApp).coerceAtLeast(0), onValueChanged = { onFpsApplicationSelect(presentMonApps[it]) }, modifier = Modifier.padding(top = 8.dp) ) diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/GpuStats.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/GpuStats.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/GpuStats.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/GpuStats.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/NetworkStats.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/NetworkStats.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/NetworkStats.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/NetworkStats.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/RamStats.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/RamStats.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/RamStats.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/RamStats.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/StatsUi.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/StatsUi.kt similarity index 98% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/StatsUi.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/StatsUi.kt index da3ebe1..49f190f 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/StatsUi.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/stats/StatsUi.kt @@ -17,11 +17,9 @@ import app.cleanmeter.core.designsystem.LocalColorScheme import app.cleanmeter.core.designsystem.LocalTypography import app.cleanmeter.target.desktop.model.OverlaySettings import app.cleanmeter.target.desktop.ui.components.KeyboardShortcutInfoLabel -import app.cleanmeter.target.desktop.ui.components.section.DropdownSection import app.cleanmeter.target.desktop.ui.settings.CheckboxSectionOption import app.cleanmeter.target.desktop.ui.settings.SectionType import app.cleanmeter.target.desktop.ui.settings.SensorType -import java.awt.GraphicsEnvironment internal fun List.filterOptions(vararg optionType: SensorType) = this.filter { source -> optionType.any { it == source.type } } @@ -54,6 +52,7 @@ fun StatsUi( onOptionsToggle = onOptionsToggle, onFpsApplicationSelect = onFpsApplicationSelect, getPresentMonApps = getPresentMonApps, + currentPresentMonApp = overlaySettings.currentPresentMonApp ) GpuStats( diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/GraphType.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/GraphType.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/GraphType.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/GraphType.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Opacity.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Opacity.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Opacity.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Opacity.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Orientation.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Orientation.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Orientation.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Orientation.kt diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Position.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Position.kt similarity index 100% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Position.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Position.kt diff --git a/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Scale.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Scale.kt new file mode 100644 index 0000000..ea781bd --- /dev/null +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/Scale.kt @@ -0,0 +1,69 @@ +package app.cleanmeter.target.desktop.ui.settings.tabs.style + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material.Icon +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Slider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import app.cleanmeter.core.designsystem.LocalColorScheme +import app.cleanmeter.target.desktop.model.OverlaySettings +import app.cleanmeter.target.desktop.ui.components.SliderThumb +import app.cleanmeter.target.desktop.ui.components.coercedValueAsFraction +import app.cleanmeter.target.desktop.ui.components.drawTrack +import app.cleanmeter.target.desktop.ui.components.section.CollapsibleSection + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun Scale( + overlaySettings: OverlaySettings, + onScaleChange: (Float) -> Unit +) { + CollapsibleSection(title = "SCALE") { + Column { + val inactiveTrackColor = LocalColorScheme.current.background.surfaceSunkenSubtle + val activeTrackColor = LocalColorScheme.current.background.brand + val inactiveTickColor = LocalColorScheme.current.background.surfaceSunken + val activeTickColor = LocalColorScheme.current.background.brandHover + Slider( + value = overlaySettings.scale, + onValueChange = { + onScaleChange(it.coerceIn(0f, 1f)) + }, + steps = 5, + track = { sliderState -> + Canvas( + Modifier + .fillMaxWidth() + .height(24.dp) + ) { + drawTrack( + tickFractions = FloatArray(sliderState.steps + 2) { it.toFloat() / (sliderState.steps + 1) }, + activeRangeStart = 0f, + activeRangeEnd = sliderState.coercedValueAsFraction, + inactiveTrackColor = inactiveTrackColor, + activeTrackColor = activeTrackColor, + inactiveTickColor = inactiveTickColor, + activeTickColor = activeTickColor, + ) + } + }, + thumb = { + SliderThumb() + } + ) + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + Icon(painterResource("icons/no_brightness.svg"), "", tint = LocalColorScheme.current.icon.bolderActive) + Icon(painterResource("icons/mid_brightness.svg"), "", tint = LocalColorScheme.current.icon.bolderActive) + Icon(painterResource("icons/full_brightness.svg"), "", tint = LocalColorScheme.current.icon.bolderActive) + } + } + } +} \ No newline at end of file diff --git a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/StyleUi.kt b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/StyleUi.kt similarity index 94% rename from target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/StyleUi.kt rename to app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/StyleUi.kt index c5faa9c..d4874c8 100644 --- a/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/StyleUi.kt +++ b/app/target/desktop/src/main/kotlin/app/cleanmeter/target/desktop/ui/settings/tabs/style/StyleUi.kt @@ -21,6 +21,7 @@ fun StyleUi( onOverlayCustomPosition: (IntOffset, Boolean) -> Unit, onLayoutChange: (Boolean) -> Unit, onOpacityChange: (Float) -> Unit, + onScaleChange: (Float) -> Unit, onGraphTypeChange: (OverlaySettings.ProgressType) -> Unit, onOverlayCustomPositionEnable: (Boolean) -> Unit, onDisplaySelect: (Int) -> Unit, @@ -49,6 +50,11 @@ fun StyleUi( onOpacityChange = onOpacityChange ) + Scale( + overlaySettings = overlaySettings, + onScaleChange = onScaleChange + ) + GraphType( overlaySettings = overlaySettings, onGraphTypeChange = onGraphTypeChange diff --git a/target/desktop/src/main/resources/icons/arrow_down.svg b/app/target/desktop/src/main/resources/icons/arrow_down.svg similarity index 100% rename from target/desktop/src/main/resources/icons/arrow_down.svg rename to app/target/desktop/src/main/resources/icons/arrow_down.svg diff --git a/target/desktop/src/main/resources/icons/bars.png b/app/target/desktop/src/main/resources/icons/bars.png similarity index 100% rename from target/desktop/src/main/resources/icons/bars.png rename to app/target/desktop/src/main/resources/icons/bars.png diff --git a/target/desktop/src/main/resources/icons/cloud_download.svg b/app/target/desktop/src/main/resources/icons/cloud_download.svg similarity index 100% rename from target/desktop/src/main/resources/icons/cloud_download.svg rename to app/target/desktop/src/main/resources/icons/cloud_download.svg diff --git a/target/desktop/src/main/resources/icons/dark_mode.png b/app/target/desktop/src/main/resources/icons/dark_mode.png similarity index 100% rename from target/desktop/src/main/resources/icons/dark_mode.png rename to app/target/desktop/src/main/resources/icons/dark_mode.png diff --git a/target/desktop/src/main/resources/icons/data_usage.svg b/app/target/desktop/src/main/resources/icons/data_usage.svg similarity index 100% rename from target/desktop/src/main/resources/icons/data_usage.svg rename to app/target/desktop/src/main/resources/icons/data_usage.svg diff --git a/target/desktop/src/main/resources/icons/discord.png b/app/target/desktop/src/main/resources/icons/discord.png similarity index 100% rename from target/desktop/src/main/resources/icons/discord.png rename to app/target/desktop/src/main/resources/icons/discord.png diff --git a/target/desktop/src/main/resources/icons/download.svg b/app/target/desktop/src/main/resources/icons/download.svg similarity index 100% rename from target/desktop/src/main/resources/icons/download.svg rename to app/target/desktop/src/main/resources/icons/download.svg diff --git a/target/desktop/src/main/resources/icons/download_done.svg b/app/target/desktop/src/main/resources/icons/download_done.svg similarity index 100% rename from target/desktop/src/main/resources/icons/download_done.svg rename to app/target/desktop/src/main/resources/icons/download_done.svg diff --git a/target/desktop/src/main/resources/icons/drag_pan.svg b/app/target/desktop/src/main/resources/icons/drag_pan.svg similarity index 100% rename from target/desktop/src/main/resources/icons/drag_pan.svg rename to app/target/desktop/src/main/resources/icons/drag_pan.svg diff --git a/target/desktop/src/main/resources/icons/dynamic_mode.png b/app/target/desktop/src/main/resources/icons/dynamic_mode.png similarity index 100% rename from target/desktop/src/main/resources/icons/dynamic_mode.png rename to app/target/desktop/src/main/resources/icons/dynamic_mode.png diff --git a/target/desktop/src/main/resources/icons/full_brightness.svg b/app/target/desktop/src/main/resources/icons/full_brightness.svg similarity index 100% rename from target/desktop/src/main/resources/icons/full_brightness.svg rename to app/target/desktop/src/main/resources/icons/full_brightness.svg diff --git a/target/desktop/src/main/resources/icons/github.png b/app/target/desktop/src/main/resources/icons/github.png similarity index 100% rename from target/desktop/src/main/resources/icons/github.png rename to app/target/desktop/src/main/resources/icons/github.png diff --git a/target/desktop/src/main/resources/icons/help.svg b/app/target/desktop/src/main/resources/icons/help.svg similarity index 100% rename from target/desktop/src/main/resources/icons/help.svg rename to app/target/desktop/src/main/resources/icons/help.svg diff --git a/target/desktop/src/main/resources/icons/horizontal.png b/app/target/desktop/src/main/resources/icons/horizontal.png similarity index 100% rename from target/desktop/src/main/resources/icons/horizontal.png rename to app/target/desktop/src/main/resources/icons/horizontal.png diff --git a/target/desktop/src/main/resources/icons/info.svg b/app/target/desktop/src/main/resources/icons/info.svg similarity index 100% rename from target/desktop/src/main/resources/icons/info.svg rename to app/target/desktop/src/main/resources/icons/info.svg diff --git a/target/desktop/src/main/resources/icons/ko-fi.png b/app/target/desktop/src/main/resources/icons/ko-fi.png similarity index 100% rename from target/desktop/src/main/resources/icons/ko-fi.png rename to app/target/desktop/src/main/resources/icons/ko-fi.png diff --git a/target/desktop/src/main/resources/icons/layers.svg b/app/target/desktop/src/main/resources/icons/layers.svg similarity index 100% rename from target/desktop/src/main/resources/icons/layers.svg rename to app/target/desktop/src/main/resources/icons/layers.svg diff --git a/target/desktop/src/main/resources/icons/light_mode.png b/app/target/desktop/src/main/resources/icons/light_mode.png similarity index 100% rename from target/desktop/src/main/resources/icons/light_mode.png rename to app/target/desktop/src/main/resources/icons/light_mode.png diff --git a/target/desktop/src/main/resources/icons/lock_closed.svg b/app/target/desktop/src/main/resources/icons/lock_closed.svg similarity index 100% rename from target/desktop/src/main/resources/icons/lock_closed.svg rename to app/target/desktop/src/main/resources/icons/lock_closed.svg diff --git a/target/desktop/src/main/resources/icons/lock_open.svg b/app/target/desktop/src/main/resources/icons/lock_open.svg similarity index 100% rename from target/desktop/src/main/resources/icons/lock_open.svg rename to app/target/desktop/src/main/resources/icons/lock_open.svg diff --git a/target/desktop/src/main/resources/icons/mid_brightness.svg b/app/target/desktop/src/main/resources/icons/mid_brightness.svg similarity index 100% rename from target/desktop/src/main/resources/icons/mid_brightness.svg rename to app/target/desktop/src/main/resources/icons/mid_brightness.svg diff --git a/target/desktop/src/main/resources/icons/no_brightness.svg b/app/target/desktop/src/main/resources/icons/no_brightness.svg similarity index 100% rename from target/desktop/src/main/resources/icons/no_brightness.svg rename to app/target/desktop/src/main/resources/icons/no_brightness.svg diff --git a/target/desktop/src/main/resources/icons/onboarding_dark.png b/app/target/desktop/src/main/resources/icons/onboarding_dark.png similarity index 100% rename from target/desktop/src/main/resources/icons/onboarding_dark.png rename to app/target/desktop/src/main/resources/icons/onboarding_dark.png diff --git a/target/desktop/src/main/resources/icons/onboarding_light.png b/app/target/desktop/src/main/resources/icons/onboarding_light.png similarity index 100% rename from target/desktop/src/main/resources/icons/onboarding_light.png rename to app/target/desktop/src/main/resources/icons/onboarding_light.png diff --git a/target/desktop/src/main/resources/icons/rings.png b/app/target/desktop/src/main/resources/icons/rings.png similarity index 100% rename from target/desktop/src/main/resources/icons/rings.png rename to app/target/desktop/src/main/resources/icons/rings.png diff --git a/target/desktop/src/main/resources/icons/sensors.svg b/app/target/desktop/src/main/resources/icons/sensors.svg similarity index 100% rename from target/desktop/src/main/resources/icons/sensors.svg rename to app/target/desktop/src/main/resources/icons/sensors.svg diff --git a/target/desktop/src/main/resources/icons/settings.svg b/app/target/desktop/src/main/resources/icons/settings.svg similarity index 100% rename from target/desktop/src/main/resources/icons/settings.svg rename to app/target/desktop/src/main/resources/icons/settings.svg diff --git a/target/desktop/src/main/resources/icons/update.svg b/app/target/desktop/src/main/resources/icons/update.svg similarity index 100% rename from target/desktop/src/main/resources/icons/update.svg rename to app/target/desktop/src/main/resources/icons/update.svg diff --git a/target/desktop/src/main/resources/icons/vertical.png b/app/target/desktop/src/main/resources/icons/vertical.png similarity index 100% rename from target/desktop/src/main/resources/icons/vertical.png rename to app/target/desktop/src/main/resources/icons/vertical.png diff --git a/target/desktop/src/main/resources/imgs/favicon.ico b/app/target/desktop/src/main/resources/imgs/favicon.ico similarity index 100% rename from target/desktop/src/main/resources/imgs/favicon.ico rename to app/target/desktop/src/main/resources/imgs/favicon.ico diff --git a/target/desktop/src/main/resources/imgs/github.svg b/app/target/desktop/src/main/resources/imgs/github.svg similarity index 100% rename from target/desktop/src/main/resources/imgs/github.svg rename to app/target/desktop/src/main/resources/imgs/github.svg diff --git a/target/desktop/src/main/resources/imgs/logo.png b/app/target/desktop/src/main/resources/imgs/logo.png similarity index 100% rename from target/desktop/src/main/resources/imgs/logo.png rename to app/target/desktop/src/main/resources/imgs/logo.png diff --git a/target/desktop/src/main/resources/imgs/logo.svg b/app/target/desktop/src/main/resources/imgs/logo.svg similarity index 100% rename from target/desktop/src/main/resources/imgs/logo.svg rename to app/target/desktop/src/main/resources/imgs/logo.svg diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/HwInfoData.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/HwInfoData.kt deleted file mode 100644 index 9395f07..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/HwInfoData.kt +++ /dev/null @@ -1,74 +0,0 @@ -package app.cleanmeter.core.common.hwinfo - -import kotlinx.serialization.Serializable - -@Serializable -data class HwInfoData( - val header: SensorSharedMem, - val sensors: List, - val readings: List -) - -private fun HwInfoData.readings(namePart: String): List { - val indexes = sensors.mapIndexed { index, sensorElement -> if (sensorElement.szSensorNameOrig.contains(namePart)) index else null }.filterNotNull() - return readings.filter { indexes.contains(it.dwSensorIndex) } -} - -fun HwInfoData.cpuReadings() = readings("CPU") -fun HwInfoData.gpuReadings() = readings("GPU") -fun HwInfoData.getReading(readingId: Int) = readings.firstOrNull { it.dwReadingID == readingId } -fun HwInfoData.getReading(readingId: String) = readings.firstOrNull() - -val HwInfoData.FPS: Int - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Framerate (Presented)" }?.value?.toInt() - ?: 0).coerceAtMost(480) - -val HwInfoData.Frametime: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Frame Time" }?.value?.toFloat() - ?: 0f).coerceAtLeast(0f).coerceAtMost(99f) - -val HwInfoData.GpuTemp: Int - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Temp && it.szLabelOrig == "GPU Temperature" }?.value?.toInt() - ?: 0).coerceAtLeast(1) - -val HwInfoData.GpuTempUnit: String - get() = readings.firstOrNull { it.readingType == SensorReadingType.Temp && it.szLabelOrig == "GPU Temperature" }?.szUnit.orEmpty() - -val HwInfoData.GpuUsage: Int - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Usage && it.szLabelOrig == "GPU Core Load" }?.value?.toInt() - ?: 0).coerceAtLeast(1) - -val HwInfoData.VramUsage: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "GPU Memory Allocated" }?.value?.toFloat() - ?: 0f).coerceAtLeast(1f) - -val HwInfoData.VramUsagePercent: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Usage && it.szLabelOrig == "GPU Memory Usage" }?.value?.toFloat() - ?: 0f).coerceAtLeast(0f).coerceAtMost(100f) - -val HwInfoData.CpuUsage: Int - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Usage && it.szLabelOrig == "Total CPU Usage" }?.value?.toInt() - ?: 0).coerceAtLeast(1) - -val HwInfoData.RamUsage: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Physical Memory Used" }?.value?.toFloat() - ?: 0f).coerceAtLeast(1f) - -val HwInfoData.RamUsagePercent: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Physical Memory Load" }?.value?.toFloat() - ?: 0f).coerceAtLeast(0f).coerceAtMost(100f) - -val HwInfoData.UpRate: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Current UP rate" }?.value?.toFloat() - ?: 0f).coerceAtLeast(0f) - -val HwInfoData.UpRateUnit - get() = readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Current UP rate" }?.szUnit.orEmpty() - -val HwInfoData.DlRate: Float - get() = (readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Current DL rate" }?.value?.toFloat() - ?: 0f).coerceAtLeast(0f) - -val HwInfoData.DlRateUnit - get() = readings.firstOrNull { it.readingType == SensorReadingType.Other && it.szLabelOrig == "Current DL rate" }?.szUnit.orEmpty() - diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorElement.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorElement.kt deleted file mode 100644 index 8b1d865..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorElement.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.cleanmeter.core.common.hwinfo - -import kotlinx.serialization.Serializable - -@Serializable -data class SensorElement( - val dwSensorId: Long, - val dwSensorInst: Long, - val szSensorNameOrig: String, - val szSensorNameUser: String, -) \ No newline at end of file diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorReadingElement.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorReadingElement.kt deleted file mode 100644 index ea7f415..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorReadingElement.kt +++ /dev/null @@ -1,17 +0,0 @@ -package app.cleanmeter.core.common.hwinfo - -import kotlinx.serialization.Serializable - -@Serializable -data class SensorReadingElement( - val readingType: SensorReadingType, - val dwSensorIndex: Int, - val dwReadingID: Int, - val szLabelOrig: String, - val szLabelUser: String, - val szUnit: String, - val value: Float, - val valueMin: Float, - val valueMax: Float, - val valueAvg: Float, -) diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorReadingType.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorReadingType.kt deleted file mode 100644 index 0a597f4..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorReadingType.kt +++ /dev/null @@ -1,18 +0,0 @@ -package app.cleanmeter.core.common.hwinfo - -enum class SensorReadingType(val value: Int) { - None(0), - Temp(1), - Volt(2), - Fan(3), - Current(4), - Power(5), - Clock(6), - Usage(7), - Other(8), - ; - - companion object { - fun getByValue(value: Int): SensorReadingType = entries.firstOrNull { it.value == value } ?: None - } -} \ No newline at end of file diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorSharedMem.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorSharedMem.kt deleted file mode 100644 index abffdde..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/hwinfo/SensorSharedMem.kt +++ /dev/null @@ -1,17 +0,0 @@ -package app.cleanmeter.core.common.hwinfo - -import kotlinx.serialization.Serializable - -@Serializable -data class SensorSharedMem( - val dwSignature: Int, - val dwVersion: Int, - val dwRevision: Int, - val pollTime: Long, - val dwOffsetOfSensorSection: Int, - val dwSizeOfSensorElement: Int, - val dwNumSensorElements: Int, - val dwOffsetOfReadingSection: Int, - val dwSizeOfReadingElement: Int, - val dwNumReadingElements: Int, -) \ No newline at end of file diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Data.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Data.kt deleted file mode 100644 index 2aa3ae3..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Data.kt +++ /dev/null @@ -1,56 +0,0 @@ -package app.cleanmeter.core.common.mahm - -import kotlinx.serialization.Serializable - -@Serializable -data class Data( - val header: Header, - val entries: List, - val gpuEntries: List -) - -val Data.FPS: Int - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_FRAMERATE }?.data?.toInt() - ?: 0).coerceAtMost(480) - -val Data.Frametime: Float - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_FRAMETIME }?.data - ?: 0f).coerceAtLeast(0f).coerceAtMost(99f) - -val Data.GpuTemp: Int - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_GPU_TEMPERATURE }?.data?.toInt() - ?: 0).coerceAtLeast(1) - -val Data.GpuTempUnit: String - get() = entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_GPU_TEMPERATURE }?.szLocalisedSrcUnits ?: "c" - -val Data.GpuUsage: Int - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_GPU_USAGE }?.data?.toInt() - ?: 0).coerceAtLeast(1) - -val Data.VramUsagePercent: Float - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_PLUGIN_MISC && it.szSrcName == "GPU Memory Usage" }?.data - ?: 0f).coerceAtLeast(1f) - -val Data.VramUsage: Float - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_MEMORY_USAGE }?.data - ?: 0f).coerceAtLeast(1f) - -val Data.CpuTemp: Int - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_CPU_TEMPERATURE }?.data?.toInt() - ?: 0).coerceAtLeast(1) - -val Data.CpuTempUnit: String - get() = entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_CPU_TEMPERATURE }?.szLocalisedSrcUnits ?: "c" - -val Data.CpuUsage: Int - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_CPU_USAGE }?.data?.toInt() - ?: 0).coerceAtLeast(1) - -val Data.RamUsagePercent: Float - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_PLUGIN_MISC && it.szSrcName == "Physical Memory Load" }?.data - ?: 0f).coerceAtLeast(1f) - -val Data.RamUsage: Float - get() = (entries.firstOrNull { it.dwSrcId == SourceID.MONITORING_SOURCE_ID_RAM_USAGE }?.data - ?: 0f).coerceAtLeast(1f) \ No newline at end of file diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Entry.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Entry.kt deleted file mode 100644 index f82f523..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Entry.kt +++ /dev/null @@ -1,59 +0,0 @@ -package app.cleanmeter.core.common.mahm - -import kotlinx.serialization.Serializable - -@Serializable -data class Entry( - /** - * mahm.Data source name - * (eg: "Core Clock") - */ - val szSrcName: String, - /** - * mahm.Data source units - * (eg: "MHz") - */ - val szSrcUnits: String, - /** - * Localised data source name - * (eg: "„астота ¤дра" for Russian GUI) - */ - val szLocalisedSrcName: String, - /** - * Localised data source units - * (eg: "ћ√ц" for Russian GUI) - */ - val szLocalisedSrcUnits: String, - /** - * Recommended output format - * (eg: "%.3f" for "Core voltage" data source) - */ - val szRecommendedFormat: String, - /** - * Last polled data - * (eg: 500MHz) - * This field can be set to FLT_MAX if data is not available at the moment - */ - val data: Float, - /** - * Minimum limit for graphs - * (eg: 0MHz) - */ - val minLimit: Float, - /** - * Maximum limit for graphs - * (eg: 2000MHz) - */ - val maxLimit: Float, - /** - * Bitmask containing combination of MAHM_SHARED_MEMORY_FLAG - */ - val dwFlags: EntryFlag, - /** - * mahm.Data source GPU index (zero based) or 0xFFFFFFFF for global data sources (eg: Framerate) - */ - val dwGpu: Int, - val dwSrcId: SourceID -) { - override fun toString() = "$szSrcName = $data$szSrcUnits" -} diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/EntryFlag.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/EntryFlag.kt deleted file mode 100644 index 2fab797..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/EntryFlag.kt +++ /dev/null @@ -1,15 +0,0 @@ -package app.cleanmeter.core.common.mahm - -import kotlinx.serialization.Serializable - -@Serializable -enum class EntryFlag(val value: Int) { - None(0), - MAHM_SHARED_MEMORY_ENTRY_FLAG_SHOW_IN_OSD(0x00000001), - MAHM_SHARED_MEMORY_ENTRY_FLAG_SHOW_IN_LCD(0x00000002), - MAHM_SHARED_MEMORY_ENTRY_FLAG_SHOW_IN_TRAY(0x00000004); - - companion object { - fun fromInt(value: Int) = values().firstOrNull { it.value == value } ?: None - } -} diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/GPUEntry.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/GPUEntry.kt deleted file mode 100644 index ce53d6a..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/GPUEntry.kt +++ /dev/null @@ -1,37 +0,0 @@ -package app.cleanmeter.core.common.mahm - -import kotlinx.serialization.Serializable - -@Serializable -data class GPUEntry( - /** - * GPU identifier represented in VEN_%04X&DEV_%04X&SUBSYS_%08X&REV_%02X&BUS_%d&DEV_%d&FN_%d format - * (eg: VEN_10DE&DEV_0A20&SUBSYS_071510DE&BUS_1&DEV_0&FN_0) - */ - val szGpuId: String, - /** - * GPU family (Can be empty if data is not available) - * (eg: GT216) - */ - val szFamily: String, - /** - * Display device description (Can be empty if data is not available) - * (eg: GeForce GT 220) - */ - val szDevice: String, - /** - * Display driver description (Can be empty if data is not available) - * (eg: 6.14.11.9621, ForceWare 196.21) - */ - val szDriver: String, - /** - * BIOS version (Can be empty if data is not available) - * (eg: 70.16.24.00.00) - */ - val szBios: String, - /** - * On-board memory amount in KB (Can be empty if data is not available) - * (eg: 1048576) - */ - val dwMemAmount: Int -) diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Header.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Header.kt deleted file mode 100644 index aa61a98..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/Header.kt +++ /dev/null @@ -1,48 +0,0 @@ -package app.cleanmeter.core.common.mahm - -import kotlinx.serialization.Serializable - -@Serializable -data class Header( - /** - * Allows applications to verify status of shared memory - * MAHM: Hardware monitoring memory is initialised and contains valid data - * 0xDEAD: Hardware monitoring memory is marked for deallocation and no longer contains valid data - * Any other value means the memory isn't initialised - */ - val dwSignature: Int, - /** - * mahm.Header Version ((major << 16) + minor) - * Must be set to 0x00020000 for v2.0 - */ - val dwVersion: Int, - /** - * mahm.Header Size - */ - val dwHeaderSize: Int, - /** - * Number of subsequent MAHM_SHARED_MEMORY_ENTRY entries - */ - val dwNumEntries: Int, - /** - * Size of each entry in subsequent MAHM_SHARED_MEMORY_ENTRY array - */ - val dwEntrySize: Int, - /** - * Number of subsequent MAHM_SHARED_MEMORY_GPU_ENTRY entries - */ - val dwNumGpuEntries: Int, - /** - * Size of each entry in subsequent MAHM_SHARED_MEMORY_GPU_ENTRY array - */ - val dwGpuEntrySize: Int, - val lastCheck: Int -) { - companion object Signatures { - const val INITIALISED = "MAHM" - const val DEAD = "0xDEAD" - } - - val totalSize: Int - get() = (dwNumEntries * dwEntrySize) + (dwNumGpuEntries * dwGpuEntrySize) + dwHeaderSize -} diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/SourceID.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/SourceID.kt deleted file mode 100644 index 7d99230..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/SourceID.kt +++ /dev/null @@ -1,80 +0,0 @@ -package app.cleanmeter.core.common.mahm - -import kotlinx.serialization.Serializable - -@Serializable -enum class SourceID(val value: Int) { - MONITORING_SOURCE_ID_UNKNOWN(-1), - MONITORING_SOURCE_ID_GPU_TEMPERATURE(0x00000000), - MONITORING_SOURCE_ID_PCB_TEMPERATURE(0x00000001), - MONITORING_SOURCE_ID_MEM_TEMPERATURE(0x00000002), - MONITORING_SOURCE_ID_VRM_TEMPERATURE(0x00000003), - MONITORING_SOURCE_ID_FAN_SPEED(0x00000010), - MONITORING_SOURCE_ID_FAN_TACHOMETER(0x00000011), - MONITORING_SOURCE_ID_FAN_SPEED2(0x00000012), - MONITORING_SOURCE_ID_FAN_TACHOMETER2(0x00000013), - MONITORING_SOURCE_ID_FAN_SPEED3(0x00000014), - MONITORING_SOURCE_ID_FAN_TACHOMETER3(0x00000015), - MONITORING_SOURCE_ID_CORE_CLOCK(0x00000020), - MONITORING_SOURCE_ID_SHADER_CLOCK(0x00000021), - MONITORING_SOURCE_ID_MEMORY_CLOCK(0x00000022), - MONITORING_SOURCE_ID_GPU_USAGE(0x00000030), - MONITORING_SOURCE_ID_MEMORY_USAGE(0x00000031), - MONITORING_SOURCE_ID_FB_USAGE(0x00000032), - MONITORING_SOURCE_ID_VID_USAGE(0x00000033), - MONITORING_SOURCE_ID_BUS_USAGE(0x00000034), - MONITORING_SOURCE_ID_GPU_VOLTAGE(0x00000040), - MONITORING_SOURCE_ID_AUX_VOLTAGE(0x00000041), - MONITORING_SOURCE_ID_MEMORY_VOLTAGE(0x00000042), - MONITORING_SOURCE_ID_AUX2_VOLTAGE(0x00000043), - MONITORING_SOURCE_ID_FRAMERATE(0x00000050), - MONITORING_SOURCE_ID_FRAMETIME(0x00000051), - MONITORING_SOURCE_ID_FRAMERATE_MIN(0x00000052), - MONITORING_SOURCE_ID_FRAMERATE_AVG(0x00000053), - MONITORING_SOURCE_ID_FRAMERATE_MAX(0x00000054), - MONITORING_SOURCE_ID_FRAMERATE_1DOT0_PERCENT_LOW(0x00000055), - MONITORING_SOURCE_ID_FRAMERATE_0DOT1_PERCENT_LOW(0x00000056), - MONITORING_SOURCE_ID_GPU_POWER(0x00000060), - MONITORING_SOURCE_ID_GPU_TEMP_LIMIT(0x00000070), - MONITORING_SOURCE_ID_GPU_POWER_LIMIT(0x00000071), - MONITORING_SOURCE_ID_GPU_VOLTAGE_LIMIT(0x00000072), - MONITORING_SOURCE_ID_GPU_UTIL_LIMIT(0x00000074), - MONITORING_SOURCE_ID_GPU_SLI_SYNC_LIMIT(0x00000075), - MONITORING_SOURCE_ID_CPU_TEMPERATURE(0x00000080), - MONITORING_SOURCE_ID_CPU_USAGE(0x00000090), - MONITORING_SOURCE_ID_RAM_USAGE(0x00000091), - MONITORING_SOURCE_ID_PAGEFILE_USAGE(0x00000092), - MONITORING_SOURCE_ID_CPU_CLOCK(0x000000A0), - MONITORING_SOURCE_ID_GPU_TEMPERATURE2(0x000000B0), - MONITORING_SOURCE_ID_PCB_TEMPERATURE2(0x000000B1), - MONITORING_SOURCE_ID_MEM_TEMPERATURE2(0x000000B2), - MONITORING_SOURCE_ID_VRM_TEMPERATURE2(0x000000B3), - MONITORING_SOURCE_ID_GPU_TEMPERATURE3(0x000000C0), - MONITORING_SOURCE_ID_PCB_TEMPERATURE3(0x000000C1), - MONITORING_SOURCE_ID_MEM_TEMPERATURE3(0x000000C2), - MONITORING_SOURCE_ID_VRM_TEMPERATURE3(0x000000C3), - MONITORING_SOURCE_ID_GPU_TEMPERATURE4(0x000000D0), - MONITORING_SOURCE_ID_PCB_TEMPERATURE4(0x000000D1), - MONITORING_SOURCE_ID_MEM_TEMPERATURE4(0x000000D2), - MONITORING_SOURCE_ID_VRM_TEMPERATURE4(0x000000D3), - MONITORING_SOURCE_ID_GPU_TEMPERATURE5(0x000000E0), - MONITORING_SOURCE_ID_PCB_TEMPERATURE5(0x000000E1), - MONITORING_SOURCE_ID_MEM_TEMPERATURE5(0x000000E2), - MONITORING_SOURCE_ID_VRM_TEMPERATURE5(0x000000E3), - MONITORING_SOURCE_ID_PLUGIN_GPU(0x000000F0), - MONITORING_SOURCE_ID_PLUGIN_CPU(0x000000F1), - MONITORING_SOURCE_ID_PLUGIN_MOBO(0x000000F2), - MONITORING_SOURCE_ID_PLUGIN_RAM(0x000000F3), - MONITORING_SOURCE_ID_PLUGIN_HDD(0x000000F4), - MONITORING_SOURCE_ID_PLUGIN_NET(0x000000F5), - MONITORING_SOURCE_ID_PLUGIN_PSU(0x000000F6), - MONITORING_SOURCE_ID_PLUGIN_UPS(0x000000F7), - MONITORING_SOURCE_ID_PLUGIN_MISC(0x000000FF), - MONITORING_SOURCE_ID_CPU_POWER(0x00000100); - - companion object { - fun fromInt(value: Int) = values().firstOrNull { it.value == value } ?: MONITORING_SOURCE_ID_UNKNOWN - - fun fromString(value: String) = values().firstOrNull { it.name == value } ?: MONITORING_SOURCE_ID_UNKNOWN - } -} diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/dto/DataDTO.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/dto/DataDTO.kt deleted file mode 100644 index a6a9d5d..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/mahm/dto/DataDTO.kt +++ /dev/null @@ -1,32 +0,0 @@ -package app.cleanmeter.core.common.mahm.dto - -import app.cleanmeter.core.common.mahm.Data -import app.cleanmeter.core.common.mahm.Entry -import app.cleanmeter.core.common.mahm.SourceID -import kotlinx.serialization.Serializable - -@Serializable -data class DataDTO( - val entries: Map -) { - @Serializable - data class EntryDTO( - val data: Float, - val minLimit: Float, - val maxLimit: Float, - ) -} - -fun Data.toDTO(): DataDTO { - return DataDTO( - entries = entries.associate { it.dwSrcId to it.toDTO() } - ) -} - -fun Entry.toDTO(): DataDTO.EntryDTO { - return DataDTO.EntryDTO( - data = data, - minLimit = minLimit, - maxLimit = maxLimit, - ) -} diff --git a/core/common/src/main/kotlin/app/cleanmeter/core/common/process/SingleInstance.kt b/core/common/src/main/kotlin/app/cleanmeter/core/common/process/SingleInstance.kt deleted file mode 100644 index fdff970..0000000 --- a/core/common/src/main/kotlin/app/cleanmeter/core/common/process/SingleInstance.kt +++ /dev/null @@ -1,40 +0,0 @@ -package app.cleanmeter.core.common.process - -import app.cleanmeter.core.common.reporting.ApplicationParams -import app.cleanmeter.core.common.reporting.setDefaultUncaughtExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.net.ServerSocket -import kotlin.system.exitProcess - -fun singleInstance(args: Array, block: () -> Unit) { - if(isAppAlreadyRunning()) { - exitProcess(0) - } - - ApplicationParams.parse(args) - - setDefaultUncaughtExceptionHandler() - - block() -} - -private fun isAppAlreadyRunning() = try { - ServerSocket(42069).apply { - Runtime.getRuntime().addShutdownHook(Thread { - close() - }) - - CoroutineScope(Dispatchers.IO).launch { - try { - accept() - } catch (_: Exception) { - // consume the exception of accept since we do not really care if the socket was shutdown - } - } - } - false -} catch (ex: Exception) { - true -} diff --git a/core/native/build.gradle.kts b/core/native/build.gradle.kts deleted file mode 100644 index b4ee20f..0000000 --- a/core/native/build.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - kotlin("jvm") - kotlin("plugin.serialization") -} - -dependencies { - api(libs.jna) - - implementation(libs.kotlinx.coroutines.core) - implementation(libs.kotlinx.serialization) - implementation("io.github.z4kn4fein:semver:2.0.0") - - implementation(projects.core.common) -} - -sourceSets { - main { - java { - srcDir("src/main/kotlin") - } - } -} \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt deleted file mode 100644 index d6f8c67..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/PreferencesRepository.kt +++ /dev/null @@ -1,26 +0,0 @@ -package app.cleanmeter.core.os - -import java.util.prefs.Preferences - -const val OVERLAY_SETTINGS_PREFERENCE_KEY = "OVERLAY_SETTINGS_PREFERENCE_KEY" -const val PREFERENCE_START_MINIMIZED = "PREFERENCE_START_MINIMIZED" -const val PREFERENCE_PERMISSION_CONSENT = "PREFERENCE_PERMISSION_CONSENT" - -object PreferencesRepository { - - private val prefs = Preferences.userNodeForPackage(PreferencesRepository::class.java) - - fun getPreferenceString(key: String): String? = prefs.get(key, null) - fun getPreferenceBoolean(key: String, defaultValue: Boolean = false): Boolean = prefs.getBoolean(key, defaultValue) - fun getPreferenceBooleanNullable(key: String): Boolean? { - return if (prefs.keys().any { it == key }) { - prefs.getBoolean(key, false) - } else { - null - } - } - - fun setPreference(key: String, value: String) = prefs.put(key, value) - fun setPreferenceBoolean(key: String, value: Boolean) = prefs.putBoolean(key, value) - fun clear() = prefs.clear() -} \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/ProcessManager.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/ProcessManager.kt deleted file mode 100644 index 0b89f61..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/ProcessManager.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.cleanmeter.core.os - -import app.cleanmeter.core.os.hardwaremonitor.HardwareMonitorProcessManager - -object ProcessManager { - - fun start() { - HardwareMonitorProcessManager.start() - } - - fun stop() { - HardwareMonitorProcessManager.stop() - } -} \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt deleted file mode 100644 index 8f63d70..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/hardwaremonitor/HardwareMonitorProcessManager.kt +++ /dev/null @@ -1,135 +0,0 @@ -package app.cleanmeter.core.os.hardwaremonitor - -import app.cleanmeter.core.os.util.isDev -import io.github.z4kn4fein.semver.Version -import io.github.z4kn4fein.semver.toVersion -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.nio.file.Path -import java.util.* - -object HardwareMonitorProcessManager { - private var process: Process? = null - - suspend fun checkRuntime(): Boolean { - return try { - val process = ProcessBuilder().apply { - command("cmd.exe", "/c", "dotnet", "--list-runtimes") - }.start() - - val scannerIn = Scanner(process.inputStream) - val scannerErr = Scanner(process.errorStream) - - val stdOutput = emptyList().toMutableList() - val errOutput = emptyList().toMutableList() - - CoroutineScope(Dispatchers.IO).launch { - try { - while (scannerIn.hasNextLine()) { - stdOutput.add(scannerIn.nextLine()) - } - } catch (e: Exception) { - return@launch - } - - } - CoroutineScope(Dispatchers.IO).launch { - try { - while (scannerErr.hasNextLine()) { - errOutput.add(scannerIn.nextLine()) - } - } catch (e: Exception) { - return@launch - } - } - - return withContext(Dispatchers.IO) { - val exitCode = process.waitFor() - if (exitCode != 0) { - return@withContext false - } - - val hasAtLeastEight = stdOutput - .map { it.split(" ").take(2).let { Pair(it[0], it[1].toVersion()) } } // transform into pairs of [name, version] - .filter { it.first.contains(".NETCore", true) } - .any { it.second >= Version(8,0,0) } - - hasAtLeastEight - } - } catch (ex: Exception) { - return false - } - } - - fun start() { - val currentDir = Path.of("").toAbsolutePath().toString() - val file = if (isDev()) { - "$currentDir\\HardwareMonitor\\HardwareMonitor\\bin\\Release\\net8.0\\win-x64\\native\\HardwareMonitor.exe" - } else { - "$currentDir\\app\\resources\\HardwareMonitor.exe" - } - - process = ProcessBuilder().apply { - command("cmd.exe", "/c", file) - }.start() - - val scannerIn = Scanner(process!!.inputStream) - val scannerErr = Scanner(process!!.errorStream) - - CoroutineScope(Dispatchers.IO).launch { - while (scannerIn.hasNextLine()) { - System.out.println(scannerIn.nextLine()) - } - } - CoroutineScope(Dispatchers.IO).launch { - while (scannerErr.hasNextLine()) { - System.err.println(scannerErr.nextLine()) - } - } - } - - fun stop() { - process?.apply { - descendants().forEach(ProcessHandle::destroy) - destroy() - } - process = null - } - - fun createService() { - val currentDir = Path.of("").toAbsolutePath().toString() - val file = "$currentDir\\app\\resources\\HardwareMonitor.exe" - val command = listOf( - "cmd.exe", - "/c", - "sc create svcleanmeter displayname= \"CleanMeter Service\" binPath= $file start= auto group= LocalServiceNoNetworkFirewall" - ) - ProcessBuilder().apply { - command(command) - }.start() - } - - fun stopService() { - ProcessBuilder().apply { - command( - "cmd.exe", - "/c", - "sc stop svcleanmeter" - ) - }.start() - } - - fun deleteService() { - ProcessBuilder().apply { - command( - "cmd.exe", - "/c", - "sc delete svcleanmeter" - ) - }.start() - } - -} \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/hwinfo/HwInfoProcessManager.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/hwinfo/HwInfoProcessManager.kt deleted file mode 100644 index 89103d7..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/hwinfo/HwInfoProcessManager.kt +++ /dev/null @@ -1,86 +0,0 @@ -package app.cleanmeter.core.os.hwinfo - -import app.cleanmeter.core.os.resource.NativeResourceLoader -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.cancellable -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import java.io.File -import java.nio.file.Path -import java.util.concurrent.TimeUnit - -object HwInfoProcessManager { - private var process: Process? = null - private const val MAX_RETRIES = 1 - private var currentRetries = 0 - private var pollingJob: Job? = null - - init { - currentRetries = 0 - } - - fun start() { - val currentDir = Path.of("").toAbsolutePath().toString() - val file = "$currentDir\\cleanmeter\\app\\resources\\HWiNFO64.exe" - - overwriteSettings() - - process = ProcessBuilder().apply { - command("cmd.exe", "/c", file) - }.start() - - pollingJob?.cancel() - pollingJob = observeHwInfoPollingTime() - } - - fun stop() { - process?.apply { - descendants().forEach(ProcessHandle::destroy) - destroy() - } - process = null - } - - private fun restart() { - stop() - start() - } - - private fun overwriteSettings() { - try { - val sourceSettings = NativeResourceLoader.load("/hwinfo/HWiNFO64.INI.src") - File("cleanmeter/app/resources/HWiNFO64.INI").printWriter().use { it.print(sourceSettings) } - } catch (e: Exception) { - e.printStackTrace() - } - } - - private fun observeHwInfoPollingTime() = CoroutineScope(Dispatchers.IO).launch { - var lastPollTime = 0L - var accumulator = 0L - - HwInfoReader - .currentData - .cancellable() - .map { it.header } - .collectLatest { - accumulator += 500 - if (accumulator < TimeUnit.SECONDS.toMillis(5)) return@collectLatest - - accumulator = 0 - when { - lastPollTime != it.pollTime -> { - lastPollTime = it.pollTime - currentRetries = 0 - } - lastPollTime == it.pollTime && currentRetries < MAX_RETRIES -> { - currentRetries++ - restart() - } - } - } - } -} \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/hwinfo/HwInfoReader.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/hwinfo/HwInfoReader.kt deleted file mode 100644 index 842d3f8..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/hwinfo/HwInfoReader.kt +++ /dev/null @@ -1,144 +0,0 @@ -package app.cleanmeter.core.os.hwinfo - -import app.cleanmeter.core.common.hwinfo.HwInfoData -import app.cleanmeter.core.common.hwinfo.SensorElement -import app.cleanmeter.core.common.hwinfo.SensorReadingElement -import app.cleanmeter.core.common.hwinfo.SensorReadingType -import app.cleanmeter.core.common.hwinfo.SensorSharedMem -import app.cleanmeter.core.os.util.getByteBuffer -import app.cleanmeter.core.os.util.readString -import app.cleanmeter.core.os.win32.WindowsService -import com.sun.jna.Pointer -import com.sun.jna.platform.win32.WinNT -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flow -import java.nio.ByteBuffer -import java.nio.ByteOrder -import kotlin.coroutines.cancellation.CancellationException - -private const val MEMORY_MAP_FILE_NAME = "Global\\HWiNFO_SENS_SM2" -private const val SENSOR_STRING_LEN = 128 -private const val UNIT_STRING_LEN = 16 -private const val HEADER_SIZE = 44 - -object HwInfoReader { - - private val windowsService = WindowsService() - private var memoryMapFile: WinNT.HANDLE? = null - private var pointer: Pointer? = null - - var pollingInterval = 500L - val currentData = flow { - while (pointer == null) { - tryOpenMemoryFile() - delay(2000L) - } - pointer?.let { pointer -> - val header = readHeader(pointer) - val sensors = readSensors(pointer, header) - val data = HwInfoData( - header = header, - sensors = sensors, - readings = emptyList(), - ) - - while (true) { - try { - val newHeader = readHeader(pointer) - emit(data.copy( - readings = readSensorReading(pointer, newHeader), - header = newHeader, - )) - delay(pollingInterval) - } catch (e: CancellationException) { - break - } - } - } - } - - private fun tryOpenMemoryFile() { - if (memoryMapFile == null) { - windowsService.openMemoryMapFile(MEMORY_MAP_FILE_NAME)?.let { handle -> - memoryMapFile = handle - pointer = windowsService.mapViewOfFile(handle) - } - } - } - - private fun readHeader(pointer: Pointer): SensorSharedMem { - val buffer = getByteBuffer(pointer, HEADER_SIZE) - - return SensorSharedMem( - dwSignature = buffer.int, - dwVersion = buffer.int, - dwRevision = buffer.int, - pollTime = buffer.long, - dwOffsetOfSensorSection = buffer.int, - dwSizeOfSensorElement = buffer.int, - dwNumSensorElements = buffer.int, - dwOffsetOfReadingSection = buffer.int, - dwSizeOfReadingElement = buffer.int, - dwNumReadingElements = buffer.int - ) - } - - private fun readSensors(pointer: Pointer, header: SensorSharedMem): List { - val longBuffer = ByteArray(8) - return buildList { - for (i in 0 until header.dwNumSensorElements) { - val buffer = pointer.getByteArray( - header.dwOffsetOfSensorSection + (header.dwSizeOfSensorElement.toLong() * i), - header.dwSizeOfSensorElement - ).let { - ByteBuffer.wrap(it).order( - ByteOrder.LITTLE_ENDIAN - ) - } - - add( - SensorElement( - dwSensorId = buffer.get(longBuffer).let { - ByteBuffer.wrap(longBuffer).order(ByteOrder.LITTLE_ENDIAN).long - }, - dwSensorInst = buffer.get(longBuffer).let { - ByteBuffer.wrap(longBuffer).order(ByteOrder.LITTLE_ENDIAN).long - }, - szSensorNameOrig = buffer.readString(SENSOR_STRING_LEN), - szSensorNameUser = buffer.readString(SENSOR_STRING_LEN) - ) - ) - } - } - } - - private fun readSensorReading(pointer: Pointer, header: SensorSharedMem): List { - return buildList { - for (i in 0 until header.dwNumReadingElements) { - val buffer = pointer.getByteArray( - header.dwOffsetOfReadingSection + (header.dwSizeOfReadingElement.toLong() * i), - header.dwSizeOfReadingElement - ).let { - ByteBuffer.wrap(it).order( - ByteOrder.LITTLE_ENDIAN - ) - } - - add( - SensorReadingElement( - readingType = SensorReadingType.getByValue(buffer.int), - dwSensorIndex = buffer.int, - dwReadingID = buffer.int, - szLabelOrig = buffer.readString(SENSOR_STRING_LEN), - szLabelUser = buffer.readString(SENSOR_STRING_LEN), - szUnit = buffer.readString(UNIT_STRING_LEN), - value = buffer.double.toFloat(), - valueMin = buffer.double.toFloat(), - valueMax = buffer.double.toFloat(), - valueAvg = buffer.double.toFloat() - ) - ) - } - } - } -} \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/mahm/MahmReader.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/mahm/MahmReader.kt deleted file mode 100644 index 47ae57e..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/mahm/MahmReader.kt +++ /dev/null @@ -1,127 +0,0 @@ -package app.cleanmeter.core.os.mahm - -import app.cleanmeter.core.common.mahm.Data -import app.cleanmeter.core.common.mahm.Entry -import app.cleanmeter.core.common.mahm.EntryFlag -import app.cleanmeter.core.common.mahm.GPUEntry -import app.cleanmeter.core.common.mahm.Header -import app.cleanmeter.core.common.mahm.SourceID -import app.cleanmeter.core.os.mahm.MAHMSizes.MAX_STRING_LENGTH -import app.cleanmeter.core.os.util.getByteBuffer -import app.cleanmeter.core.os.util.readString -import app.cleanmeter.core.os.win32.WindowsService -import com.sun.jna.Pointer -import com.sun.jna.platform.win32.WinNT -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.flow -import java.nio.ByteBuffer - -object MAHMSizes { - const val HEADER_SIZE = 32 - const val MAX_STRING_LENGTH = 260 -} - -private const val MEMORY_MAP_FILE_NAME = "MAHMSharedMemory" - -class MahmReader { - - private val windowsService = WindowsService() - private var pollingJob: Job? = null - - private var memoryMapFile: WinNT.HANDLE? = null - private var pointer: Pointer? = null - - var pollingInterval = 1000L - val currentData = flow { - tryOpenMemoryFile() - pointer ?: return@flow - - while (true) { - try { - emit(readData(pointer!!)) - delay(pollingInterval) - } catch (e: CancellationException) { - break - } - } - } - - fun stopPolling() { - pollingJob?.cancel() - pollingJob = null - pointer?.let { windowsService.unmapViewOfFile(it) } - memoryMapFile?.let { windowsService.closeHandle(it) } - } - - fun tryOpenMemoryFile() { - windowsService.openMemoryMapFile(MEMORY_MAP_FILE_NAME)?.let { handle -> - memoryMapFile = handle - pointer = windowsService.mapViewOfFile(handle) ?: throw Error("Something went wrong: Could not create pointer") - } ?: throw Error("Could not read AfterBurner data. Is it running?") - } - - private fun readHeader(pointer: Pointer): Header { - val buffer = getByteBuffer(pointer, MAHMSizes.HEADER_SIZE) - - return Header( - dwSignature = buffer.int, - dwVersion = buffer.int, - dwHeaderSize = buffer.int, - dwNumEntries = buffer.int, - dwEntrySize = buffer.int, - lastCheck = buffer.int, - dwNumGpuEntries = buffer.int, - dwGpuEntrySize = buffer.int - ) - } - - private fun readData(pointer: Pointer): Data { - val header = readHeader(pointer) - val buffer = getByteBuffer(pointer, header.totalSize, header.dwHeaderSize) - val entries = readCpuEntries(buffer, header.dwNumEntries) - val gpuEntries = readGpuEntries(buffer, header.dwNumGpuEntries) - - return Data( - header = header, - entries = entries, - gpuEntries = gpuEntries - ) - } - - private fun readGpuEntries(buffer: ByteBuffer, numEntries: Int) = buildList { - for (i in 0 until numEntries) { - add( - GPUEntry( - szGpuId = buffer.readString(MAX_STRING_LENGTH), - szFamily = buffer.readString(MAX_STRING_LENGTH), - szDevice = buffer.readString(MAX_STRING_LENGTH), - szDriver = buffer.readString(MAX_STRING_LENGTH), - szBios = buffer.readString(MAX_STRING_LENGTH), - dwMemAmount = buffer.int - ) - ) - } - } - - private fun readCpuEntries(buffer: ByteBuffer, numEntries: Int) = buildList { - for (i in 0 until numEntries) { - add( - Entry( - szSrcName = buffer.readString(MAX_STRING_LENGTH), - szSrcUnits = buffer.readString(MAX_STRING_LENGTH), - szLocalisedSrcName = buffer.readString(MAX_STRING_LENGTH), - szLocalisedSrcUnits = buffer.readString(MAX_STRING_LENGTH), - szRecommendedFormat = buffer.readString(MAX_STRING_LENGTH), - data = buffer.float, - minLimit = buffer.float, - maxLimit = buffer.float, - dwFlags = EntryFlag.fromInt(buffer.int), - dwGpu = buffer.int, - dwSrcId = SourceID.fromInt(buffer.int) - ) - ) - } - } -} diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/util/env.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/util/env.kt deleted file mode 100644 index f92ca5f..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/util/env.kt +++ /dev/null @@ -1,3 +0,0 @@ -package app.cleanmeter.core.os.util - -fun isDev() = System.getenv("env") == "dev" \ No newline at end of file diff --git a/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/WindowsService.kt b/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/WindowsService.kt deleted file mode 100644 index babf2af..0000000 --- a/core/native/src/main/kotlin/app/cleanmeter/core/os/win32/WindowsService.kt +++ /dev/null @@ -1,91 +0,0 @@ -package app.cleanmeter.core.os.win32 - -import app.cleanmeter.core.os.util.isDev -import com.sun.jna.Native -import com.sun.jna.Pointer -import com.sun.jna.platform.win32.Kernel32 -import com.sun.jna.platform.win32.User32 -import com.sun.jna.platform.win32.WinBase -import com.sun.jna.platform.win32.WinDef.HWND -import com.sun.jna.platform.win32.WinNT -import com.sun.jna.platform.win32.WinNT.HANDLE -import com.sun.jna.platform.win32.WinUser -import java.awt.Component -import java.io.File -import java.nio.file.Path -import kotlin.system.exitProcess - -class WindowsService { - - var lastError: Int = 0 - private set - - fun openMemoryMapFile(filename: String): WinNT.HANDLE? { - val memMapFile = Kernel32Impl.INSTANCE.OpenFileMapping(WinNT.SECTION_MAP_READ, false, filename) - lastError = Kernel32Impl.INSTANCE.GetLastError() - - return memMapFile - } - - fun openEventFile(filename: String): WinNT.HANDLE? { - val memMapFile = Kernel32Impl.INSTANCE.OpenEvent(WinNT.SYNCHRONIZE, false, filename) - lastError = Kernel32Impl.INSTANCE.GetLastError() - return memMapFile - } - - fun waitForEvent(handle: HANDLE): Boolean { - return Kernel32Impl.INSTANCE.WaitForSingleObject(handle, 500) == WinBase.WAIT_OBJECT_0 - } - - fun closeHandle(handle: WinNT.HANDLE) { - Kernel32Impl.INSTANCE.CloseHandle(handle) - } - - fun mapViewOfFile(handle: WinNT.HANDLE?): Pointer? { - handle ?: return null - - return Kernel32.INSTANCE.MapViewOfFile(handle, WinNT.SECTION_MAP_READ, 0, 0, 0) - } - - fun unmapViewOfFile(pointer: Pointer) { - Kernel32Impl.INSTANCE.UnmapViewOfFile(pointer) - lastError = Kernel32Impl.INSTANCE.GetLastError() - } - - companion object { - fun changeWindowTransparency(w: Component, isTransparent: Boolean) { - val hwnd = HWND().apply { pointer = Native.getComponentPointer(w) } - val wl = if (isTransparent) { - User32.INSTANCE.GetWindowLong( - hwnd, - WinUser.GWL_EXSTYLE - ) or WinUser.WS_EX_LAYERED or WinUser.WS_EX_TRANSPARENT - } else { - User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE) or WinUser.WS_EX_LAYERED and WinUser.WS_EX_TRANSPARENT.inv() - } - User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, wl) - } - - fun isProcessElevated(): Boolean { - try { - File.createTempFile("cleanmeter", ".lock", File("C:/")).delete() - } catch (ex: Exception) { - return false - } - return true - } - - fun tryElevateProcess(isAutostart: Boolean) { - if (isAutostart) return - if (!isDev() && !WindowsService.isProcessElevated()) { - elevateProcess() - } - } - - fun elevateProcess() { - val currentDir = Path.of("").toAbsolutePath().toString() - Shell32Impl.INSTANCE.ShellExecuteW(null, "runas", "$currentDir\\cleanmeter.exe", "", "", 10) - exitProcess(0) - } - } -}